restertet yazi setup
new file: herdr/.config/herdr/sessions/home/session-history.json new file: herdr/.config/herdr/sessions/home/session.json new file: kanata/.config/systemd/user/default.target.wants/duplicati.service new file: pi/.pi/agent/extensions/atuin.ts new file: yazi/.config/yazi/bookmark modified: yazi/.config/yazi/init.lua modified: yazi/.config/yazi/keymap.toml modified: yazi/.config/yazi/package.toml renamed: yazi/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE -> yazi/.config/yazi/plugins/chmod.yazi/LICENSE new file: yazi/.config/yazi/plugins/chmod.yazi/README.md new file: yazi/.config/yazi/plugins/chmod.yazi/main.lua new file: yazi/.config/yazi/plugins/eza-preview.yazi/LICENSE new file: yazi/.config/yazi/plugins/eza-preview.yazi/README.md new file: yazi/.config/yazi/plugins/eza-preview.yazi/main.lua modified: yazi/.config/yazi/plugins/fchar.yazi/README.md modified: yazi/.config/yazi/plugins/fchar.yazi/main.lua deleted: yazi/.config/yazi/plugins/file-extra-metadata.yazi/main.lua modified: yazi/.config/yazi/plugins/mediainfo.yazi/LICENSE modified: yazi/.config/yazi/plugins/mediainfo.yazi/README.md new file: yazi/.config/yazi/plugins/mediainfo.yazi/adobe-old.lua new file: yazi/.config/yazi/plugins/mediainfo.yazi/adobe-stable.lua new file: yazi/.config/yazi/plugins/mediainfo.yazi/audio-old.lua new file: yazi/.config/yazi/plugins/mediainfo.yazi/audio-stable.lua new file: yazi/.config/yazi/plugins/mediainfo.yazi/const.lua new file: yazi/.config/yazi/plugins/mediainfo.yazi/image-old.lua new file: yazi/.config/yazi/plugins/mediainfo.yazi/image-stable.lua modified: yazi/.config/yazi/plugins/mediainfo.yazi/main.lua new file: yazi/.config/yazi/plugins/mediainfo.yazi/none-media-preview-old.lua new file: yazi/.config/yazi/plugins/mediainfo.yazi/none-media-preview-stable.lua new file: yazi/.config/yazi/plugins/mediainfo.yazi/utils.lua new file: yazi/.config/yazi/plugins/mediainfo.yazi/video-old.lua new file: yazi/.config/yazi/plugins/mediainfo.yazi/video-stable.lua modified: yazi/.config/yazi/plugins/mime-ext.yazi/README.md new file: yazi/.config/yazi/plugins/mime-ext.yazi/local.lua modified: yazi/.config/yazi/plugins/mime-ext.yazi/main.lua new file: yazi/.config/yazi/plugins/mime-ext.yazi/remote.lua modified: yazi/.config/yazi/plugins/rich-preview.yazi/main.lua modified: yazi/.config/yazi/plugins/smart-enter.yazi/README.md deleted: yazi/.config/yazi/plugins/whoosh.yazi/.luarc.json modified: yazi/.config/yazi/plugins/whoosh.yazi/LICENSE deleted: yazi/.config/yazi/plugins/whoosh.yazi/README-RU.md modified: yazi/.config/yazi/plugins/whoosh.yazi/README.md modified: yazi/.config/yazi/plugins/whoosh.yazi/bookmarks deleted: yazi/.config/yazi/plugins/whoosh.yazi/image/history.png deleted: yazi/.config/yazi/plugins/whoosh.yazi/image/plugin.png modified: yazi/.config/yazi/plugins/whoosh.yazi/main.lua modified: yazi/.config/yazi/yazi.toml renamed: yazi/.config/yazi/plugins/full-border.yazi/LICENSE -> yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE renamed: yazi/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE-tmtheme renamed: yazi/.config/yazi/flavors/catppuccin-mocha.yazi/README.md -> yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/README.md renamed: yazi/.config/yazi/flavors/catppuccin-mocha.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/flavor.toml renamed: yazi/.config/yazi/flavors/catppuccin-mocha.yazi/preview.png -> yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/preview.png renamed: yazi/.config/yazi/flavors/catppuccin-mocha.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/tmtheme.xml renamed: yazi/.config/yazi/flavors/dracula.yazi -> yazi_1/.config/yazi/flavors/dracula.yazi renamed: yazi/.config/yazi/flavors/everforest-medium.yazi/LICENSE -> yazi_1/.config/yazi/flavors/everforest-medium.yazi/LICENSE renamed: yazi/.config/yazi/flavors/everforest-medium.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/everforest-medium.yazi/LICENSE-tmtheme renamed: yazi/.config/yazi/flavors/everforest-medium.yazi/README.md -> yazi_1/.config/yazi/flavors/everforest-medium.yazi/README.md renamed: yazi/.config/yazi/flavors/everforest-medium.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/everforest-medium.yazi/flavor.toml renamed: yazi/.config/yazi/flavors/everforest-medium.yazi/preview.png -> yazi_1/.config/yazi/flavors/everforest-medium.yazi/preview.png renamed: yazi/.config/yazi/flavors/everforest-medium.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/everforest-medium.yazi/tmtheme.xml new file: yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/LICENSE new file: yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/LICENSE-tmtheme new file: yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/README.md new file: yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/flavor.toml new file: yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/preview.png new file: yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/tmtheme.xml renamed: yazi/.config/yazi/flavors/gruvbox-material.yazi/LICENSE -> yazi_1/.config/yazi/flavors/gruvbox-material.yazi/LICENSE renamed: yazi/.config/yazi/flavors/gruvbox-material.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/gruvbox-material.yazi/LICENSE-tmtheme renamed: yazi/.config/yazi/flavors/gruvbox-material.yazi/README.md -> yazi_1/.config/yazi/flavors/gruvbox-material.yazi/README.md renamed: yazi/.config/yazi/flavors/gruvbox-material.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/gruvbox-material.yazi/flavor.toml renamed: yazi/.config/yazi/flavors/gruvbox-material.yazi/preview.png -> yazi_1/.config/yazi/flavors/gruvbox-material.yazi/preview.png renamed: yazi/.config/yazi/flavors/gruvbox-material.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/gruvbox-material.yazi/tmtheme.xml renamed: yazi/.config/yazi/flavors/kanagawa.yazi/LICENSE -> yazi_1/.config/yazi/flavors/kanagawa.yazi/LICENSE renamed: yazi/.config/yazi/flavors/kanagawa.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/kanagawa.yazi/LICENSE-tmtheme renamed: yazi/.config/yazi/flavors/kanagawa.yazi/README.md -> yazi_1/.config/yazi/flavors/kanagawa.yazi/README.md renamed: yazi/.config/yazi/flavors/kanagawa.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/kanagawa.yazi/flavor.toml renamed: yazi/.config/yazi/flavors/kanagawa.yazi/preview.png -> yazi_1/.config/yazi/flavors/kanagawa.yazi/preview.png renamed: yazi/.config/yazi/flavors/kanagawa.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/kanagawa.yazi/tmtheme.xml renamed: yazi/.config/yazi/flavors/monokai.yazi/LICENSE -> yazi_1/.config/yazi/flavors/monokai.yazi/LICENSE renamed: yazi/.config/yazi/flavors/monokai.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/monokai.yazi/LICENSE-tmtheme renamed: yazi/.config/yazi/flavors/monokai.yazi/README.md -> yazi_1/.config/yazi/flavors/monokai.yazi/README.md renamed: yazi/.config/yazi/flavors/monokai.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/monokai.yazi/flavor.toml renamed: yazi/.config/yazi/flavors/monokai.yazi/preview.png -> yazi_1/.config/yazi/flavors/monokai.yazi/preview.png renamed: yazi/.config/yazi/flavors/monokai.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/monokai.yazi/tmtheme.xml renamed: yazi/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE -> yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE renamed: yazi/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE-tmtheme renamed: yazi/.config/yazi/flavors/rose-pine-moon.yazi/README.md -> yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/README.md renamed: yazi/.config/yazi/flavors/rose-pine-moon.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/flavor.toml renamed: yazi/.config/yazi/flavors/rose-pine-moon.yazi/preview.png -> yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/preview.png renamed: yazi/.config/yazi/flavors/rose-pine-moon.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/tmtheme.xml renamed: yazi/.config/yazi/flavors/tokyo-night.yazi/LICENSE -> yazi_1/.config/yazi/flavors/tokyo-night.yazi/LICENSE renamed: yazi/.config/yazi/flavors/tokyo-night.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/tokyo-night.yazi/LICENSE-tmtheme renamed: yazi/.config/yazi/flavors/tokyo-night.yazi/README.md -> yazi_1/.config/yazi/flavors/tokyo-night.yazi/README.md renamed: yazi/.config/yazi/flavors/tokyo-night.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/tokyo-night.yazi/flavor.toml renamed: yazi/.config/yazi/flavors/tokyo-night.yazi/preview.png -> yazi_1/.config/yazi/flavors/tokyo-night.yazi/preview.png renamed: yazi/.config/yazi/flavors/tokyo-night.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/tokyo-night.yazi/tmtheme.xml new file: yazi_1/.config/yazi/init.lua new file: yazi_1/.config/yazi/keymap.toml renamed: yazi/.config/yazi/omp/omp.json -> yazi_1/.config/yazi/omp/omp.json renamed: yazi/.config/yazi/omp/omp.toml -> yazi_1/.config/yazi/omp/omp.toml renamed: yazi/.config/yazi/omp/omp2.toml -> yazi_1/.config/yazi/omp/omp2.toml renamed: yazi/.config/yazi/omp/omp_rose-pine.toml -> yazi_1/.config/yazi/omp/omp_rose-pine.toml new file: yazi_1/.config/yazi/package.toml renamed: yazi/.config/yazi/plugins/bunny.yazi/LICENSE -> yazi_1/.config/yazi/plugins/bunny.yazi/LICENSE renamed: yazi/.config/yazi/plugins/bunny.yazi/README.md -> yazi_1/.config/yazi/plugins/bunny.yazi/README.md renamed: yazi/.config/yazi/plugins/bunny.yazi/main.lua -> yazi_1/.config/yazi/plugins/bunny.yazi/main.lua renamed: yazi/.config/yazi/plugins/bypass.yazi/LICENSE -> yazi_1/.config/yazi/plugins/bypass.yazi/LICENSE renamed: yazi/.config/yazi/plugins/bypass.yazi/README.md -> yazi_1/.config/yazi/plugins/bypass.yazi/README.md renamed: yazi/.config/yazi/plugins/bypass.yazi/main.lua -> yazi_1/.config/yazi/plugins/bypass.yazi/main.lua renamed: yazi/.config/yazi/plugins/dupes.yazi/LICENSE -> yazi_1/.config/yazi/plugins/dupes.yazi/LICENSE renamed: yazi/.config/yazi/plugins/dupes.yazi/README.md -> yazi_1/.config/yazi/plugins/dupes.yazi/README.md renamed: yazi/.config/yazi/plugins/dupes.yazi/main.lua -> yazi_1/.config/yazi/plugins/dupes.yazi/main.lua renamed: yazi/.config/yazi/plugins/fazif.yazi/LICENSE -> yazi_1/.config/yazi/plugins/fazif.yazi/LICENSE renamed: yazi/.config/yazi/plugins/fazif.yazi/README.md -> yazi_1/.config/yazi/plugins/fazif.yazi/README.md renamed: yazi/.config/yazi/plugins/fazif.yazi/faziffd -> yazi_1/.config/yazi/plugins/fazif.yazi/faziffd renamed: yazi/.config/yazi/plugins/fazif.yazi/faziffdr -> yazi_1/.config/yazi/plugins/fazif.yazi/faziffdr renamed: yazi/.config/yazi/plugins/fazif.yazi/fazifrg -> yazi_1/.config/yazi/plugins/fazif.yazi/fazifrg renamed: yazi/.config/yazi/plugins/fazif.yazi/fazifrga -> yazi_1/.config/yazi/plugins/fazif.yazi/fazifrga renamed: yazi/.config/yazi/plugins/fazif.yazi/main.lua -> yazi_1/.config/yazi/plugins/fazif.yazi/main.lua new file: yazi_1/.config/yazi/plugins/fchar.yazi/LICENSE new file: yazi_1/.config/yazi/plugins/fchar.yazi/README.md new file: yazi_1/.config/yazi/plugins/fchar.yazi/main.lua renamed: yazi/.config/yazi/plugins/file-extra-metadata.yazi/LICENSE -> yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/LICENSE renamed: yazi/.config/yazi/plugins/file-extra-metadata.yazi/README.md -> yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/README.md new file: yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/main.lua renamed: yazi/.config/yazi/plugins/git.yazi/LICENSE -> yazi_1/.config/yazi/plugins/full-border.yazi/LICENSE renamed: yazi/.config/yazi/plugins/full-border.yazi/README.md -> yazi_1/.config/yazi/plugins/full-border.yazi/README.md renamed: yazi/.config/yazi/plugins/full-border.yazi/main.lua -> yazi_1/.config/yazi/plugins/full-border.yazi/main.lua renamed: yazi/.config/yazi/plugins/mount.yazi/LICENSE -> yazi_1/.config/yazi/plugins/git.yazi/LICENSE renamed: yazi/.config/yazi/plugins/git.yazi/README.md -> yazi_1/.config/yazi/plugins/git.yazi/README.md renamed: yazi/.config/yazi/plugins/git.yazi/main.lua -> yazi_1/.config/yazi/plugins/git.yazi/main.lua renamed: yazi/.config/yazi/plugins/git.yazi/types.lua -> yazi_1/.config/yazi/plugins/git.yazi/types.lua renamed: yazi/.config/yazi/plugins/lazygit.yazi/LICENSE -> yazi_1/.config/yazi/plugins/lazygit.yazi/LICENSE renamed: yazi/.config/yazi/plugins/lazygit.yazi/README.md -> yazi_1/.config/yazi/plugins/lazygit.yazi/README.md renamed: yazi/.config/yazi/plugins/lazygit.yazi/main.lua -> yazi_1/.config/yazi/plugins/lazygit.yazi/main.lua renamed: yazi/.config/yazi/plugins/open-with-cmd.yazi/LICENSE -> yazi_1/.config/yazi/plugins/mediainfo.yazi/LICENSE new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/README.md new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/adobe-old.lua new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/adobe-stable.lua new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-09-14-23.png new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-09-15-39.png new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-16-51-11.png new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-16-52-39.png new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/audio-old.lua new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/audio-stable.lua new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/const.lua new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/image-old.lua new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/image-stable.lua new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/main.lua new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/none-media-preview-old.lua new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/none-media-preview-stable.lua new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/utils.lua new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/video-old.lua new file: yazi_1/.config/yazi/plugins/mediainfo.yazi/video-stable.lua renamed: yazi/.config/yazi/plugins/piper.yazi/LICENSE -> yazi_1/.config/yazi/plugins/mime-ext.yazi/LICENSE new file: yazi_1/.config/yazi/plugins/mime-ext.yazi/README.md new file: yazi_1/.config/yazi/plugins/mime-ext.yazi/local.lua new file: yazi_1/.config/yazi/plugins/mime-ext.yazi/main.lua new file: yazi_1/.config/yazi/plugins/mime-ext.yazi/remote.lua renamed: yazi/.config/yazi/plugins/rsync.yazi/LICENSE -> yazi_1/.config/yazi/plugins/mount.yazi/LICENSE renamed: yazi/.config/yazi/plugins/mount.yazi/README.md -> yazi_1/.config/yazi/plugins/mount.yazi/README.md new file: yazi_1/.config/yazi/plugins/mount.yazi/cross.lua renamed: yazi/.config/yazi/plugins/mount.yazi/main.lua -> yazi_1/.config/yazi/plugins/mount.yazi/main.lua new file: yazi_1/.config/yazi/plugins/mount.yazi/sudo.lua renamed: yazi/.config/yazi/plugins/omp.yazi -> yazi_1/.config/yazi/plugins/omp.yazi renamed: yazi/.config/yazi/plugins/open-git-remote.yazi/LICENSE -> yazi_1/.config/yazi/plugins/open-git-remote.yazi/LICENSE renamed: yazi/.config/yazi/plugins/open-git-remote.yazi/README.md -> yazi_1/.config/yazi/plugins/open-git-remote.yazi/README.md renamed: yazi/.config/yazi/plugins/open-git-remote.yazi/main.lua -> yazi_1/.config/yazi/plugins/open-git-remote.yazi/main.lua new file: yazi_1/.config/yazi/plugins/open-with-cmd.yazi/LICENSE renamed: yazi/.config/yazi/plugins/open-with-cmd.yazi/README.md -> yazi_1/.config/yazi/plugins/open-with-cmd.yazi/README.md renamed: yazi/.config/yazi/plugins/open-with-cmd.yazi/main.lua -> yazi_1/.config/yazi/plugins/open-with-cmd.yazi/main.lua renamed: yazi/.config/yazi/plugins/ouch.yazi/LICENSE -> yazi_1/.config/yazi/plugins/ouch.yazi/LICENSE renamed: yazi/.config/yazi/plugins/ouch.yazi/README.md -> yazi_1/.config/yazi/plugins/ouch.yazi/README.md renamed: yazi/.config/yazi/plugins/ouch.yazi/main.lua -> yazi_1/.config/yazi/plugins/ouch.yazi/main.lua renamed: yazi/.config/yazi/plugins/toggle-pane.yazi/LICENSE -> yazi_1/.config/yazi/plugins/piper.yazi/LICENSE renamed: yazi/.config/yazi/plugins/piper.yazi/README.md -> yazi_1/.config/yazi/plugins/piper.yazi/README.md renamed: yazi/.config/yazi/plugins/piper.yazi/main.lua -> yazi_1/.config/yazi/plugins/piper.yazi/main.lua renamed: yazi/.config/yazi/plugins/projects.yazi/LICENSE -> yazi_1/.config/yazi/plugins/projects.yazi/LICENSE renamed: yazi/.config/yazi/plugins/projects.yazi/README.md -> yazi_1/.config/yazi/plugins/projects.yazi/README.md renamed: yazi/.config/yazi/plugins/projects.yazi/json.lua -> yazi_1/.config/yazi/plugins/projects.yazi/json.lua renamed: yazi/.config/yazi/plugins/projects.yazi/main.lua -> yazi_1/.config/yazi/plugins/projects.yazi/main.lua renamed: yazi/.config/yazi/plugins/recycle-bin.yazi/LICENSE -> yazi_1/.config/yazi/plugins/recycle-bin.yazi/LICENSE renamed: yazi/.config/yazi/plugins/recycle-bin.yazi/README.md -> yazi_1/.config/yazi/plugins/recycle-bin.yazi/README.md renamed: yazi/.config/yazi/plugins/recycle-bin.yazi/main.lua -> yazi_1/.config/yazi/plugins/recycle-bin.yazi/main.lua new file: yazi_1/.config/yazi/plugins/rich-preview.yazi/LICENSE new file: yazi_1/.config/yazi/plugins/rich-preview.yazi/README.md new file: yazi_1/.config/yazi/plugins/rich-preview.yazi/main.lua renamed: yazi/.config/yazi/plugins/vcs-files.yazi/LICENSE -> yazi_1/.config/yazi/plugins/rsync.yazi/LICENSE renamed: yazi/.config/yazi/plugins/rsync.yazi/README.md -> yazi_1/.config/yazi/plugins/rsync.yazi/README.md renamed: yazi/.config/yazi/plugins/rsync.yazi/assets/demo.gif -> yazi_1/.config/yazi/plugins/rsync.yazi/assets/demo.gif renamed: yazi/.config/yazi/plugins/rsync.yazi/assets/demo.webm -> yazi_1/.config/yazi/plugins/rsync.yazi/assets/demo.webm renamed: yazi/.config/yazi/plugins/rsync.yazi/main.lua -> yazi_1/.config/yazi/plugins/rsync.yazi/main.lua renamed: yazi/.config/yazi/plugins/simple-tag.yazi/LICENSE -> yazi_1/.config/yazi/plugins/simple-tag.yazi/LICENSE renamed: yazi/.config/yazi/plugins/simple-tag.yazi/README.md -> yazi_1/.config/yazi/plugins/simple-tag.yazi/README.md renamed: yazi/.config/yazi/plugins/simple-tag.yazi/main.lua -> yazi_1/.config/yazi/plugins/simple-tag.yazi/main.lua new file: yazi_1/.config/yazi/plugins/smart-enter.yazi/LICENSE new file: yazi_1/.config/yazi/plugins/smart-enter.yazi/README.md new file: yazi_1/.config/yazi/plugins/smart-enter.yazi/main.lua renamed: yazi/.config/yazi/plugins/smart-paste.yazi/LICENSE -> yazi_1/.config/yazi/plugins/smart-paste.yazi/LICENSE renamed: yazi/.config/yazi/plugins/smart-paste.yazi/README.md -> yazi_1/.config/yazi/plugins/smart-paste.yazi/README.md renamed: yazi/.config/yazi/plugins/smart-paste.yazi/main.lua -> yazi_1/.config/yazi/plugins/smart-paste.yazi/main.lua new file: yazi_1/.config/yazi/plugins/toggle-pane.yazi/LICENSE renamed: yazi/.config/yazi/plugins/toggle-pane.yazi/README.md -> yazi_1/.config/yazi/plugins/toggle-pane.yazi/README.md renamed: yazi/.config/yazi/plugins/toggle-pane.yazi/main.lua -> yazi_1/.config/yazi/plugins/toggle-pane.yazi/main.lua new file: yazi_1/.config/yazi/plugins/vcs-files.yazi/LICENSE renamed: yazi/.config/yazi/plugins/vcs-files.yazi/README.md -> yazi_1/.config/yazi/plugins/vcs-files.yazi/README.md renamed: yazi/.config/yazi/plugins/vcs-files.yazi/main.lua -> yazi_1/.config/yazi/plugins/vcs-files.yazi/main.lua renamed: yazi/.config/yazi/plugins/what-size.yazi/LICENSE -> yazi_1/.config/yazi/plugins/what-size.yazi/LICENSE renamed: yazi/.config/yazi/plugins/what-size.yazi/README.md -> yazi_1/.config/yazi/plugins/what-size.yazi/README.md renamed: yazi/.config/yazi/plugins/what-size.yazi/main.lua -> yazi_1/.config/yazi/plugins/what-size.yazi/main.lua new file: yazi_1/.config/yazi/plugins/whoosh.yazi new file: yazi_1/.config/yazi/plugins/wise-enter.yazi/LICENSE new file: yazi_1/.config/yazi/plugins/wise-enter.yazi/README.md new file: yazi_1/.config/yazi/plugins/wise-enter.yazi/main.lua renamed: yazi/.config/yazi/plugins/yafg.yazi/LICENSE -> yazi_1/.config/yazi/plugins/yafg.yazi/LICENSE renamed: yazi/.config/yazi/plugins/yafg.yazi/README.md -> yazi_1/.config/yazi/plugins/yafg.yazi/README.md renamed: yazi/.config/yazi/plugins/yafg.yazi/main.lua -> yazi_1/.config/yazi/plugins/yafg.yazi/main.lua renamed: yazi/.config/yazi/plugins/yaziline.yazi/LICENSE -> yazi_1/.config/yazi/plugins/yaziline.yazi/LICENSE renamed: yazi/.config/yazi/plugins/yaziline.yazi/README.md -> yazi_1/.config/yazi/plugins/yaziline.yazi/README.md renamed: yazi/.config/yazi/plugins/yaziline.yazi/main.lua -> yazi_1/.config/yazi/plugins/yaziline.yazi/main.lua renamed: yazi/.config/yazi/tags/home/liph/.local/share/Trash/files/tags.json -> yazi_1/.config/yazi/tags/home/liph/.local/share/Trash/files/tags.json renamed: yazi/.config/yazi/tags/home/liph/Downloads/tags.json -> yazi_1/.config/yazi/tags/home/liph/Downloads/tags.json renamed: yazi/.config/yazi/tags/home/liph/programming/webdev/html/static/tags.json -> yazi_1/.config/yazi/tags/home/liph/programming/webdev/html/static/tags.json new file: yazi_1/.config/yazi/tags/home/liph/scripts/tags.json renamed: yazi/.config/yazi/tags/home/liph/tags.json -> yazi_1/.config/yazi/tags/home/liph/tags.json new file: yazi_1/.config/yazi/yazi.toml
This commit is contained in:
1 parent
d6fd64c8af
commit
a8a1b5a718
226 files changed
+17541
-3631
No files matched your search
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"workspaces": [
|
||||||
|
{
|
||||||
|
"tabs": [
|
||||||
|
{
|
||||||
|
"panes": {
|
||||||
|
"1": {
|
||||||
|
"ansi": "\u001b[0m\u001b[38;5;5m❯ \u001b[0m\u001b[38;5;2msed\u001b[0m -n \u001b[0m\u001b[38;5;3m'103,111p'\u001b[0m \u001b[0m\u001b[4m~/.config/yazi/init.lua\u001b[0m\r\nrequire(\"mime-ext.local\"):setup({\r\n with_exts = {\r\n tex = \"text/makefile\",\r\n },\r\n})\r\n\r\nrequire(\"full-border\"):setup({\r\n type = ui.Border.ROUNDED,\r\n})\r\n\u001b[0m\u001b[38;5;5m❯ \u001b[0m\r\n\u001b[0m\u001b[38;5;5m❯ \u001b[0m\u001b[38;5;2my\u001b[0m\r\nError: Lua runtime failed\r\n\r\nCaused by:\r\n Plugin `mime-ext.local` requires at least Yazi 26.8.15, but your current version is Yazi 26.5.6 (Arch Linux 2026-06-15).\r\n stack traceback:\r\n [C]: in local 'poll'\r\n [string \"?\"]:28: in global 'require'\r\n [string \"init.lua\"]:103: in main chunk\r\n \r\n\u001b[0m\u001b[38;5;5m❯ \u001b[0m\u001b[4m\u001b[38;5;2msudo\u001b[0m \u001b[0m\u001b[38;5;2mpacman\u001b[0m -S unarchiver\r\n[sudo] password for liph: \r\n\u001b[0m\u001b[1m\u001b[38;5;1merror: \u001b[0mfailed to init transaction (unable to lock database)\r\n\u001b[0m\u001b[1m\u001b[38;5;1merror: \u001b[0mcould not lock database: File exists\r\n if you're sure a package manager is not already\r\n running, you can remove /var/lib/pacman/db.lck\r\n\u001b[0m\u001b[38;5;1m❯ \u001b[0m\u001b[38;5;2my\u001b[0m\r\nError: Lua runtime failed\r\n\r\nCaused by:\r\n Plugin `mime-ext.local` requires at least Yazi 26.8.15, but your current version is Yazi 26.5.6 (Arch Linux 2026-06-15).\r\n stack traceback:\r\n [C]: in local 'poll'\r\n [string \"?\"]:28: in global 'require'\r\n [string \"init.lua\"]:103: in main chunk\r\n \r\n\u001b[0m\u001b[38;5;5m❯ \u001b[0m\u001b[38;5;2mcd\u001b[0m yazi\r\n\u001b[0m\u001b[38;5;5m❯ \u001b[0m\u001b[38;5;2mn\u001b[0m \u001b[0m\u001b[4mkeymap.toml\u001b[0m\r\n\r\n\u001b[0m\u001b[38;5;5march liph \u001b[0m\u001b[38;5;4m~/dotfiles/yazi/.config/yazi\u001b[0m\u001b[38;5;3m main* \u001b[0m\r\n\u001b[0m\u001b[38;5;5m❯\u001b[0m ",
|
||||||
|
"lines": 42
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"ansi": "\r\n\u001b[0m\u001b[38;5;5march liph \u001b[0m\u001b[38;5;4m~/Pictures\u001b[0m\r\n\u001b[0m\u001b[38;5;5m❯\u001b[0m ",
|
||||||
|
"lines": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panes": {
|
||||||
|
"3": {
|
||||||
|
"ansi": "\u001b[0m\u001b[38;2;144;140;170m\u001b[48;2;42;39;63m \u001b[0m\u001b[38;2;81;160;207m\u001b[48;2;42;39;63m\u001b[0m\u001b[38;2;144;140;170m\u001b[48;2;42;39;63m \u001b[0m\u001b[1m\u001b[38;2;144;140;170m\u001b[48;2;42;39;63mmain.lua\u001b[0m\u001b[38;2;144;140;170m\u001b[48;2;42;39;63m \u001b[0m\u001b[38;2;235;111;146m╭──────────── Error ────────────╮\u001b[0m\u001b[38;2;144;140;170m\u001b[48;2;42;39;63m \u001b[0m\r\n 88 \u001b[0m\u001b[4m\u001b[38;2;62;143;176mlocal\u001b[0m\u001b[4m \u001b[0m\u001b[4m\u001b[38;2;62;143;176mfunction\u001b[0m\u001b[4m \u001b[0m\u001b[3m\u001b[4m\u001b[38;2;234;154;151mget_filesystem_extra\u001b[0m\u001b[4m\u001b[38;2;235;111;146m(\u001b[0m\u001b[3m\u001b[4m\u001b[38;2;196;167;231mfile\u001b[0m\u001b[4m\u001b[38;2;235;111;146m)\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m \u001b[0m\u001b[1m\u001b[38;2;235;111;146mLua callback: ...y/rainbow-delim\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m \r\n 89 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m \u001b[0m\u001b[38;2;62;143;176mlocal\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mresult\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[38;2;235;111;146m{\u001b[0m \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m \u001b[0m\u001b[1m\u001b[38;2;235;111;146mstack traceback:\u001b[0m\u001b[38;2;110;106;134m \u001b[0m \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m \r\n 90 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ \u001b[0m\u001b[3m\u001b[38;2;156;207;216mfilesystem\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[38;2;246;193;119m\"\"\u001b[0m\u001b[38;2;144;140;170m,\u001b[0m \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m \u001b[0m\u001b[1m\u001b[38;2;110;106;134m \u001b[0m\u001b[1m\u001b[38;2;235;111;146m...y/rainbow-delimiters.nvim/l\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m \r\n 91 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ \u001b[0m\u001b[3m\u001b[38;2;156;207;216mdevice\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[38;2;246;193;119m\"\"\u001b[0m\u001b[38;2;144;140;170m,\u001b[0m \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m \u001b[0m\u001b[1m\u001b[38;2;110;106;134m \u001b[0m\u001b[1m\u001b[38;2;235;111;146m...zy/rainbow-delimiters.nvim/\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m \r\n 92 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ \u001b[0m\u001b[3m\u001b[38;2;156;207;216mtype\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[38;2;246;193;119m\"\"\u001b[0m\u001b[38;2;144;140;170m,\u001b[0m \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m \u001b[0m\u001b[1m\u001b[38;2;110;106;134m \u001b[0m\u001b[1m\u001b[38;2;235;111;146mC: in function 'nvim_set_optio\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 93 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ \u001b[0m\u001b[3m\u001b[38;2;156;207;216mused_space\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[38;2;246;193;119m\"\"\u001b[0m\u001b[38;2;144;140;170m,\u001b[0m \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m \u001b[0m\u001b[1m\u001b[38;2;110;106;134m \u001b[0m\u001b[1m\u001b[38;2;235;111;146m...t/.local/share/nvim/lazy/nu\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 94 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ \u001b[0m\u001b[3m\u001b[38;2;156;207;216mavail_space\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[38;2;246;193;119m\"\"\u001b[0m\u001b[38;2;144;140;170m, \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m \u001b[0m\u001b[1m\u001b[38;2;110;106;134m \u001b[0m\u001b[1m\u001b[38;2;235;111;146m...t/.local/share/nvim/lazy/nu\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 95 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ \u001b[0m\u001b[3m\u001b[38;2;156;207;216mtotal_space\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[38;2;246;193;119m\"\"\u001b[0m\u001b[38;2;144;140;170m, \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m \u001b[0m\u001b[1m\u001b[38;2;110;106;134m \u001b[0m\u001b[1m\u001b[38;2;235;111;146m...t/.local/share/nvim/lazy/nu\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 96 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ \u001b[0m\u001b[3m\u001b[38;2;156;207;216mused_space_percent\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[38;Line truncated
|
||||||
|
"lines": 33
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"ansi": "\u001b[0m\u001b[38;5;6m~/.abook\u001b[0m\r\n\u001b[0m\u001b[38;5;7m╭───────────────┬──────────────────────────┬────────────────────────────────────────╮\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m\u001b[0m\u001b[7m\u001b[38;5;4m .abook \u001b[0m\u001b[38;5;4m\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[0m\u001b[7m addressbook \u001b[0m\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4mMetadata \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.agents \u001b[0m\u001b[38;5;7m│ \u001b[0m\u001b[38;2;255;255;255m \u001b[0maddressbook~ \u001b[0m\u001b[38;5;7m│\u001b[0m File: \u001b[0m\u001b[38;5;4maddressbook \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.ansible \u001b[0m\u001b[38;5;7m│ │\u001b[0m Mimetype: \u001b[0m\u001b[38;5;4mapplication/octet-stream \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.aspnet \u001b[0m\u001b[38;5;7m│ │\u001b[0m Location: \u001b[0m\u001b[38;5;4m/home/liph/.abook \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.atuin \u001b[0m\u001b[38;5;7m│ │\u001b[0m Mode: \u001b[0m\u001b[38;5;4m-rw-r--r-- \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.bun \u001b[0m\u001b[38;5;7m│ │\u001b[0m Attributes: \u001b[0m\u001b[38;5;4m---------------------- \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.cache \u001b[0m\u001b[38;5;7m│ │\u001b[0m Links: \u001b[0m\u001b[38;5;4m1 \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.calcurse \u001b[0m\u001b[38;5;7m│ │\u001b[0m Owner: \u001b[0m\u001b[38;5;4mliph/liph \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.cargo \u001b[0m\u001b[38;5;7m│ │\u001b[0m Size: \u001b[0m\u001b[38;5;4m89.9K \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.claude \u001b[0m\u001b[38;5;7m│ │\u001b[0m Created: \u001b[0m\u001b[38;5;4m2026-08-24 16:28 \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;255;152;0m \u001b[0m\u001b[38;5;4m.config \u001b[0m\u001b[38;5;7m│ │\u001b[0m Modified: \u001b[0m\u001b[38;5;4m2026-08-24 16:28 \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.dotnet \u001b[0m\u001b[38;5;7m│ │\u001b[0m Accessed: \u001b[0m\u001b[38;5;4m2026-08-24 16:28 \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.electron-g… \u001b[0m\u001b[38;5;7m│ │\u001b[0m Filesystem: \u001b[0m\u001b[38;5;4m/home \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.Garmin \u001b[0m\u001b[38;5;7m│ │\u001b[0m Device: \u001b[0m\u001b[38;5;4m/dev/sda2 \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.gnupg \u001b[0m\u001b[38;5;7m│ │\u001b[0m Type: \u001b[0m\u001b[38;5;4mbtrfs \u001b[0m\u001b[38;5;7m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;3;169;244m \u001b[0m\u001b[38;5;4m.herdr \u001b[0m\u001b[38;5;7m│ │\u001b[0m Free space: \u001b[0m\u001b[38;5;4mLine truncated
|
||||||
|
"lines": 35
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panes": {
|
||||||
|
"6": {
|
||||||
|
"ansi": "\u001b[0m\u001b[48;2;35;33;54m phil spam proton \u001b[0m\u001b[1m\u001b[38;2;234;154;151m\u001b[48;2;35;33;54m Complete code challenge \u001b[0m\u001b[48;2;35;33;54m \u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;2;196;167;231mFrom:\u001b[0m\u001b[38;2;156;207;216m\u001b[48;2;35;33;54m Ollama <no-reply@mail.ollama.com> \u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;2;196;167;231mTo:\u001b[0m\u001b[38;2;156;207;216m\u001b[48;2;35;33;54m <phil@liphlink.xyz> \u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;2;196;167;231mSubject:\u001b[0m\u001b[38;2;156;207;216m\u001b[48;2;35;33;54m Complete code challenge \u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;2;196;167;231mDate:\u001b[0m\u001b[38;2;156;207;216m\u001b[48;2;35;33;54m 2026 Aug 24, 16:27 GMT+0200 \u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;2;35;33;54m\u001b[48;2;35;33;54m───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\u001b[0m\u001b[38;2;224;222;244mComplete code challenge\u001b[0m Line truncated
|
||||||
|
"lines": 39
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tabs": [
|
||||||
|
{
|
||||||
|
"panes": {
|
||||||
|
"7": {
|
||||||
|
"ansi": "yazi\r\n\u001b[0m\u001b[38;5;5m❯ \u001b[0m\u001b[38;5;2myazi\u001b[0m\r\n\r\n\u001b[0m\u001b[38;5;5march liph \u001b[0m\u001b[38;5;4m~/dotfiles\u001b[0m\u001b[38;5;3m main* \u001b[0m\r\n\u001b[0m\u001b[38;5;5m❯\u001b[0m \u001b[0m\u001b[1m\u001b[7m%\u001b[0m \u001b[0m\u001b[38;5;5march liph \u001b[0m\u001b[38;5;4m~/dotfiles\u001b[0m\u001b[38;5;3m main* \u001b[0m\r\n\u001b[0m\u001b[38;5;5m❯\u001b[0m \u001b[0m\u001b[1m\u001b[7m%\u001b[0m \u001b[0m\u001b[38;5;5march liph \u001b[0m\u001b[38;5;4m~/dotfiles\u001b[0m\u001b[38;5;3m main* \u001b[0m\r\n\u001b[0m\u001b[38;5;5m❯\u001b[0m ",
|
||||||
|
"lines": 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"workspaces": [
|
||||||
|
{
|
||||||
|
"id": "w2",
|
||||||
|
"custom_name": "Home",
|
||||||
|
"identity_cwd": "/home/liph/dotfiles/yazi/.config/yazi",
|
||||||
|
"public_pane_numbers": {
|
||||||
|
"3": 8,
|
||||||
|
"1": 2,
|
||||||
|
"5": 7,
|
||||||
|
"2": 5,
|
||||||
|
"6": 4
|
||||||
|
},
|
||||||
|
"next_public_pane_number": 9,
|
||||||
|
"public_tab_numbers": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"next_public_tab_number": 4,
|
||||||
|
"tabs": [
|
||||||
|
{
|
||||||
|
"custom_name": "Base",
|
||||||
|
"layout": {
|
||||||
|
"Split": {
|
||||||
|
"direction": "Horizontal",
|
||||||
|
"ratio": 0.46022728,
|
||||||
|
"first": {
|
||||||
|
"Pane": 1
|
||||||
|
},
|
||||||
|
"second": {
|
||||||
|
"Pane": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"panes": {
|
||||||
|
"1": {
|
||||||
|
"cwd": "/home/liph/dotfiles/yazi/.config/yazi"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"cwd": "/home/liph/Pictures"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zoomed": false,
|
||||||
|
"focused": 1,
|
||||||
|
"root_pane": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"custom_name": "Music",
|
||||||
|
"layout": {
|
||||||
|
"Split": {
|
||||||
|
"direction": "Horizontal",
|
||||||
|
"ratio": 0.5,
|
||||||
|
"first": {
|
||||||
|
"Pane": 3
|
||||||
|
},
|
||||||
|
"second": {
|
||||||
|
"Pane": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"panes": {
|
||||||
|
"5": {
|
||||||
|
"cwd": "/home/liph/.abook"
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"cwd": "/home/liph/dotfiles/yazi/.config/yazi/plugins/file-extra-metadata.yazi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zoomed": false,
|
||||||
|
"focused": 3,
|
||||||
|
"root_pane": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"custom_name": "Aerc",
|
||||||
|
"layout": {
|
||||||
|
"Pane": 6
|
||||||
|
},
|
||||||
|
"panes": {
|
||||||
|
"6": {
|
||||||
|
"cwd": "/home/liph"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zoomed": false,
|
||||||
|
"focused": 6,
|
||||||
|
"root_pane": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"active_tab": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "w3",
|
||||||
|
"custom_name": "Dotfiles",
|
||||||
|
"identity_cwd": "/home/liph/dotfiles",
|
||||||
|
"public_pane_numbers": {
|
||||||
|
"7": 1
|
||||||
|
},
|
||||||
|
"next_public_pane_number": 2,
|
||||||
|
"public_tab_numbers": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"next_public_tab_number": 2,
|
||||||
|
"tabs": [
|
||||||
|
{
|
||||||
|
"custom_name": "Dotfiles",
|
||||||
|
"layout": {
|
||||||
|
"Pane": 7
|
||||||
|
},
|
||||||
|
"panes": {
|
||||||
|
"7": {
|
||||||
|
"cwd": "/home/liph/dotfiles"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zoomed": false,
|
||||||
|
"focused": 7,
|
||||||
|
"root_pane": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"active_tab": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"active": 0,
|
||||||
|
"selected": 0,
|
||||||
|
"sidebar_width": 26,
|
||||||
|
"sidebar_section_split": 0.5,
|
||||||
|
"collapsed_space_keys": []
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/usr/lib/systemd/user/duplicati.service
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* Atuin extension for pi.
|
||||||
|
*
|
||||||
|
* Tracks bash commands executed by pi in Atuin history with author `pi`.
|
||||||
|
*
|
||||||
|
* Install with:
|
||||||
|
* atuin hook install pi
|
||||||
|
*
|
||||||
|
* Then restart pi or run /reload.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
const ATUIN_AUTHOR = "pi";
|
||||||
|
const ATUIN_TIMEOUT_MS = 10_000;
|
||||||
|
|
||||||
|
async function startHistory(
|
||||||
|
pi: ExtensionAPI,
|
||||||
|
cwd: string,
|
||||||
|
command: string,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const result = await pi.exec(
|
||||||
|
"atuin",
|
||||||
|
["history", "start", "--author", ATUIN_AUTHOR, "--", command],
|
||||||
|
{ cwd, timeout: ATUIN_TIMEOUT_MS },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.code !== 0) return undefined;
|
||||||
|
|
||||||
|
const id = result.stdout.trim();
|
||||||
|
return id.length > 0 ? id : undefined;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function endHistory(
|
||||||
|
pi: ExtensionAPI,
|
||||||
|
cwd: string,
|
||||||
|
historyId: string,
|
||||||
|
exitCode: number,
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
await pi.exec(
|
||||||
|
"atuin",
|
||||||
|
["history", "end", historyId, "--exit", String(exitCode)],
|
||||||
|
{ cwd, timeout: ATUIN_TIMEOUT_MS },
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// Ignore Atuin failures so command execution is never blocked.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The bash tool reports failures by appending a status line to the result
|
||||||
|
// text rather than exposing a numeric exit code, so recover it from there.
|
||||||
|
function exitCodeFromResult(result: unknown, isError: boolean): number {
|
||||||
|
if (!isError) return 0;
|
||||||
|
|
||||||
|
const content = (result as { content?: unknown } | undefined)?.content;
|
||||||
|
const text = Array.isArray(content)
|
||||||
|
? content
|
||||||
|
.map((part) => {
|
||||||
|
const t = (part as { text?: unknown } | undefined)?.text;
|
||||||
|
return typeof t === "string" ? t : "";
|
||||||
|
})
|
||||||
|
.join("\n")
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const exited = text.match(/Command exited with code (\d+)\s*$/);
|
||||||
|
if (exited) return Number(exited[1]);
|
||||||
|
if (/Command aborted\s*$/.test(text)) return 130;
|
||||||
|
if (/Command timed out after \S+ seconds\s*$/.test(text)) return 124;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function atuinPiExtension(pi: ExtensionAPI) {
|
||||||
|
// Atuin history IDs for in-flight bash tool calls, keyed by tool call ID.
|
||||||
|
const pending = new Map<string, string>();
|
||||||
|
|
||||||
|
// Observe bash executions through events instead of registering a bash
|
||||||
|
// tool: registering one conflicts with other extensions that provide
|
||||||
|
// their own bash tool (sandboxes, RTK, remote runners), while events
|
||||||
|
// fire no matter which extension's bash tool ends up executing the
|
||||||
|
// command.
|
||||||
|
pi.on("tool_call", async (event, ctx: ExtensionContext) => {
|
||||||
|
if (event.toolName !== "bash") return;
|
||||||
|
|
||||||
|
const command = (event.input as { command?: unknown }).command;
|
||||||
|
if (typeof command !== "string" || command.length === 0) return;
|
||||||
|
|
||||||
|
const historyId = await startHistory(pi, ctx.cwd, command);
|
||||||
|
if (historyId) pending.set(event.toolCallId, historyId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// tool_execution_end also fires when another extension blocks the call,
|
||||||
|
// unlike tool_result, so entries started above are always closed.
|
||||||
|
pi.on("tool_execution_end", async (event, ctx: ExtensionContext) => {
|
||||||
|
const historyId = pending.get(event.toolCallId);
|
||||||
|
if (!historyId) return;
|
||||||
|
pending.delete(event.toolCallId);
|
||||||
|
|
||||||
|
await endHistory(pi, ctx.cwd, historyId, exitCodeFromResult(event.result, event.isError));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Desktop /home/liph/Desktop/ d
|
||||||
+62
-269
@@ -1,140 +1,32 @@
|
|||||||
-- Change this to switch themes
|
--- whoosh ---
|
||||||
-- local THEME = "kanagawa"
|
--
|
||||||
-- local THEME = "catppuccin-mocha"
|
local bookmarks = {
|
||||||
-- local THEME = "everforest-medium"
|
{ tag = "Desktop", path = "~/Desktop", key = "d" },
|
||||||
-- local THEME = "gruvbox-material"
|
{ tag = "Documents", path = "~/Documents", key = "D" },
|
||||||
-- local THEME = "tokyo-night"
|
{ tag = "Downloads", path = "~/Downloads", key = "o" },
|
||||||
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" },
|
|
||||||
-- }
|
|
||||||
|
|
||||||
-- You can also configure bookmarks with key arrays
|
-- You can also configure bookmarks with key arrays
|
||||||
-- local bookmarks = {
|
local bookmarks = {
|
||||||
-- { tag = "Desktop", path = "~/Desktop", key = { "d", "D" } },
|
{ tag = "Desktop", path = "~/Desktop", key = { "d", "D" } },
|
||||||
-- { tag = "Documents", path = "~/Documents", 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" },
|
-- Windows-specific bookmarks
|
||||||
-- { tag = "Downloads", path = "~/Downloads", key = "o" },
|
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({
|
require("whoosh"):setup({
|
||||||
-- Configuration bookmarks (cannot be deleted through plugin)
|
-- Configuration bookmarks (cannot be deleted through plugin)
|
||||||
@@ -153,6 +45,7 @@ require("whoosh"):setup({
|
|||||||
fuzzy_search = "<Space>", -- Launch fuzzy search (fzf)
|
fuzzy_search = "<Space>", -- Launch fuzzy search (fzf)
|
||||||
history = "<Tab>", -- Open directory history
|
history = "<Tab>", -- Open directory history
|
||||||
previous_dir = "<Backspace>", -- Jump back to the previous directory
|
previous_dir = "<Backspace>", -- Jump back to the previous directory
|
||||||
|
project_root = "-", -- Jump to the current Git repository root
|
||||||
},
|
},
|
||||||
|
|
||||||
-- File path for storing user bookmarks
|
-- 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)
|
history_fzf_path_max_folder_name_length = 30, -- Maximum length for folder names in history (default 30)
|
||||||
})
|
})
|
||||||
|
|
||||||
-- projetc plugin
|
--- mime-ext ---
|
||||||
require("projects"):setup({
|
require("mime-ext.local"):setup({
|
||||||
event = {
|
-- Expand the default filename database (lowercase), for example:
|
||||||
save = {
|
with_files = {
|
||||||
enable = true,
|
makefile = "text/makefile",
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
save = {
|
|
||||||
method = "yazi", -- yazi | lua
|
-- Expand the default extension database (lowercase), for example:
|
||||||
yazi_load_event = "@projects-load", -- event name when loading projects in `yazi` method
|
with_exts = {
|
||||||
lua_save_path = "", -- path of saved file in `lua` method, comment out or assign explicitly
|
mk = "text/makefile",
|
||||||
-- 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",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
-- 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
|
-- fchar --
|
||||||
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",
|
|
||||||
-- })
|
|
||||||
|
|
||||||
-- default config
|
-- default config
|
||||||
require("fchar"):setup({
|
require("fchar"):setup({
|
||||||
-- if true: f -> file, File, FILE
|
-- if true: f -> file, File, FILE
|
||||||
@@ -302,11 +110,18 @@ require("fchar"):setup({
|
|||||||
skip_symbols = true,
|
skip_symbols = true,
|
||||||
-- if {"yazi-"}: f -> file, yazi-file
|
-- if {"yazi-"}: f -> file, yazi-file
|
||||||
skip_prefix = {},
|
skip_prefix = {},
|
||||||
-- start: f -> file
|
|
||||||
-- word: f -> file, also-file
|
|
||||||
-- all: f -> file, also-file, alsofile, elf
|
|
||||||
search_location = "start",
|
|
||||||
aliases = {},
|
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
|
-- aliases for German
|
||||||
@@ -319,14 +134,14 @@ require("fchar"):setup({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
-- you may want to turn off the search regex from
|
--[[ you may want to turn off the search regex from showing up
|
||||||
-- showing up in the header by doing this
|
in the header by putting this in `~/.config/yazi/init.lua` ]]
|
||||||
function Header:flags()
|
function Header:flags()
|
||||||
local cwd = self._current.cwd
|
local cwd = self._current.cwd
|
||||||
local filter = self._current.files.filter
|
local filter = self._current.files.filter
|
||||||
|
|
||||||
local t = {}
|
local t = {}
|
||||||
if cwd.is_search then
|
if cwd.spec.is_search then
|
||||||
t[#t + 1] = string.format("search: %s", cwd.domain)
|
t[#t + 1] = string.format("search: %s", cwd.domain)
|
||||||
end
|
end
|
||||||
if filter then
|
if filter then
|
||||||
@@ -334,25 +149,3 @@ function Header:flags()
|
|||||||
end
|
end
|
||||||
return #t == 0 and "" or " (" .. table.concat(t, ", ") .. ")"
|
return #t == 0 and "" or " (" .. table.concat(t, ", ") .. ")"
|
||||||
end
|
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
File diff suppressed because it is too large.
Load diff
+24
-156
@@ -1,174 +1,42 @@
|
|||||||
|
[[plugin.deps]]
|
||||||
|
use = "AnirudhG07/rich-preview"
|
||||||
|
rev = "02597c4"
|
||||||
|
hash = "33982a6e2d6df07619a2a0fc163a0950"
|
||||||
|
|
||||||
[[plugin.deps]]
|
[[plugin.deps]]
|
||||||
use = "yazi-rs/plugins:smart-enter"
|
use = "yazi-rs/plugins:smart-enter"
|
||||||
rev = "4e55902"
|
rev = "c591a36"
|
||||||
hash = "56fdabc96fc1f4d53c96eb884b02a5be"
|
hash = "187cc58ba7ac3befd49c342129e6f1b6"
|
||||||
|
|
||||||
[[plugin.deps]]
|
|
||||||
use = "yazi-rs/plugins:git"
|
|
||||||
rev = "4e55902"
|
|
||||||
hash = "36a484acf6a0a0219c543ccb4cee218f"
|
|
||||||
|
|
||||||
[[plugin.deps]]
|
|
||||||
use = "yazi-rs/plugins:mount"
|
|
||||||
rev = "4e55902"
|
|
||||||
hash = "e2d4bfccf31e5ab05f46fee4797c5215"
|
|
||||||
|
|
||||||
[[plugin.deps]]
|
[[plugin.deps]]
|
||||||
use = "yazi-rs/plugins:mime-ext"
|
use = "yazi-rs/plugins:mime-ext"
|
||||||
rev = "4e55902"
|
rev = "c591a36"
|
||||||
hash = "48a77ab785d1ee73bc2d7bbe2a4c2090"
|
hash = "3bcc00df11efc3ae5d15f2dba860be7"
|
||||||
|
|
||||||
[[plugin.deps]]
|
|
||||||
use = "ndtoan96/ouch"
|
|
||||||
rev = "cfb9140"
|
|
||||||
hash = "b5067143415bd2d46c0dfa57319ddcef"
|
|
||||||
|
|
||||||
[[plugin.deps]]
|
|
||||||
use = "yazi-rs/plugins:piper"
|
|
||||||
rev = "4e55902"
|
|
||||||
hash = "2dd68af59418b631b9703a03f606b31f"
|
|
||||||
|
|
||||||
[[plugin.deps]]
|
[[plugin.deps]]
|
||||||
use = "boydaihungst/mediainfo"
|
use = "boydaihungst/mediainfo"
|
||||||
rev = "90a1652"
|
rev = "73a3658"
|
||||||
hash = "7a83bfab51d1d2ba6558cc56bd52539a"
|
hash = "fa787a8ef785f2976ea2861f62b8c6c4"
|
||||||
|
|
||||||
[[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"
|
|
||||||
|
|
||||||
[[plugin.deps]]
|
[[plugin.deps]]
|
||||||
use = "AminurAlam/yazi-plugins:fchar"
|
use = "AminurAlam/yazi-plugins:fchar"
|
||||||
rev = "658a099"
|
rev = "8c2ce96"
|
||||||
hash = "b484ff674a30268991b67f819f4625da"
|
hash = "9f2598542a3263b5a03d7fb832b407c1"
|
||||||
|
|
||||||
[[plugin.deps]]
|
[[plugin.deps]]
|
||||||
use = "mshnwq/dupes"
|
use = "ahkohd/eza-preview"
|
||||||
rev = "18a9395"
|
rev = "e8fb6c8"
|
||||||
hash = "730ddf5e1179f34ccb81f8d5ed564823"
|
hash = "c238595c801caa4f28553b9af8cf8804"
|
||||||
|
|
||||||
[[plugin.deps]]
|
[[plugin.deps]]
|
||||||
use = "uhs-robert/recycle-bin"
|
use = "WhoSowSee/whoosh"
|
||||||
rev = "69d7d4c"
|
rev = "43f3202"
|
||||||
hash = "38dac2fc8f4b50bbdaa46e699720a54a"
|
hash = "da14dfe482886e756150ae4f5c2bb23"
|
||||||
|
|
||||||
[[plugin.deps]]
|
[[plugin.deps]]
|
||||||
use = "Rolv-Apneseth/bypass"
|
use = "yazi-rs/plugins:chmod"
|
||||||
rev = "c1e5fcf"
|
rev = "c591a36"
|
||||||
hash = "208e77480824f62558a5b037c8569d61"
|
hash = "87472b05a8c420100f6a2b9cde1e7746"
|
||||||
|
|
||||||
[[plugin.deps]]
|
[flavor]
|
||||||
use = "Ape/open-with-cmd"
|
deps = []
|
||||||
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"
|
|
||||||
File renamed without changes.
@@ -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,
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
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
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# eza-preview.yazi
|
||||||
|
|
||||||
|
[Yazi](https://github.com/sxyazi/yazi) plugin to preview directories using [eza](https://github.com/eza-community/eza), can be switched between list and tree modes.
|
||||||
|
|
||||||
|
List mode:
|
||||||
|

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

|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- [yazi (26.1.4+) or nightly](https://github.com/sxyazi/yazi)
|
||||||
|
- [eza (0.20+)](https://github.com/eza-community/eza)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ya pkg add ahkohd/eza-preview
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic Setup
|
||||||
|
|
||||||
|
Add `eza-preview` to previewers in `yazi.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[plugin.prepend_previewers]]
|
||||||
|
url = "*/"
|
||||||
|
run = "eza-preview"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Bindings
|
||||||
|
|
||||||
|
Set key bindings to control the preview in `keymap.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[mgr]
|
||||||
|
prepend_keymap = [
|
||||||
|
{ on = [ "e", "t" ], run = "plugin eza-preview", desc = "Toggle tree/list dir preview" },
|
||||||
|
{ on = [ "e", "-" ], run = "plugin eza-preview inc-level", desc = "Increment tree level" },
|
||||||
|
{ on = [ "e", "_" ], run = "plugin eza-preview dec-level", desc = "Decrement tree level" },
|
||||||
|
{ on = [ "e", "$" ], run = "plugin eza-preview toggle-follow-symlinks", desc = "Toggle tree follow symlinks" },
|
||||||
|
{ on = [ "e", "*" ], run = "plugin eza-preview toggle-hidden", desc = "Toggle hidden files" },
|
||||||
|
{ on = [ "e", "g", "i" ], run = "plugin eza-preview toggle-git-ignore", desc = "Toggle .gitignore files" },
|
||||||
|
{ on = [ "e", "g", "s" ], run = "plugin eza-preview toggle-git-status", desc = "Toggle showing git status" },
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Configure the plugin in `init.lua`:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
require("eza-preview"):setup({
|
||||||
|
-- Set the tree preview to be default (default: true)
|
||||||
|
default_tree = true,
|
||||||
|
|
||||||
|
-- Directory depth level for tree preview (default: 3)
|
||||||
|
level = 3,
|
||||||
|
|
||||||
|
-- Show file icons
|
||||||
|
icons = true,
|
||||||
|
|
||||||
|
-- Follow symlinks when previewing directories (default: true)
|
||||||
|
follow_symlinks = true,
|
||||||
|
|
||||||
|
-- Show target file info instead of symlink info (default: false)
|
||||||
|
dereference = false,
|
||||||
|
|
||||||
|
-- Show hidden files (default: true)
|
||||||
|
all = true,
|
||||||
|
|
||||||
|
-- Ignore files matching patterns (default: {})
|
||||||
|
-- ignore_glob = "*.log"
|
||||||
|
-- ignore_glob = { "*.tmp", "node_modules", ".git", ".DS_Store" }
|
||||||
|
-- SEE: https://www.linuxjournal.com/content/pattern-matching-bash to learn about glob patterns
|
||||||
|
ignore_glob = {},
|
||||||
|
|
||||||
|
-- Ignore files mentioned in '.gitignore' (default: true)
|
||||||
|
git_ignore = true,
|
||||||
|
|
||||||
|
-- Show git status (default: false)
|
||||||
|
git_status = false
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Or use default settings
|
||||||
|
require("eza-preview"):setup({})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Commands
|
||||||
|
|
||||||
|
- `plugin eza-preview` - Toggle between tree and list modes
|
||||||
|
- `plugin eza-preview inc-level` - Increase tree depth level
|
||||||
|
- `plugin eza-preview dec-level` - Decrease tree depth level
|
||||||
|
- `plugin eza-preview toggle-follow-symlinks` - Toggle symlink following
|
||||||
|
- `plugin eza-preview toggle-hidden` - Toggle hidden file visibility
|
||||||
|
- `plugin eza-preview toggle-git-ignore` - Toggle ignore files mentioned in '.gitignore'
|
||||||
|
- `plugin eza-preview toggle-git-status` - Toggle showing git status
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Feel free to contribute by opening issues or submitting pull requests!
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
--- @since 25.12.29
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
-- Batch getter - returns multiple simple values (avoids multiple sync boundary crossings)
|
||||||
|
local get_all_opts = ya.sync(function(st)
|
||||||
|
return st.tree ~= false,
|
||||||
|
st.level or 3,
|
||||||
|
st.follow_symlinks ~= false,
|
||||||
|
st.dereference == true,
|
||||||
|
st.all ~= false,
|
||||||
|
st.git_ignore ~= false,
|
||||||
|
st.git_status == true,
|
||||||
|
st.icons ~= false,
|
||||||
|
st.ignore_glob or ""
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Individual getters for entry point (need current value before toggling)
|
||||||
|
local get_tree = ya.sync(function(st) return st.tree ~= false end)
|
||||||
|
local get_level = ya.sync(function(st) return st.level or 3 end)
|
||||||
|
local get_follow_symlinks = ya.sync(function(st) return st.follow_symlinks ~= false end)
|
||||||
|
local get_all = ya.sync(function(st) return st.all ~= false end)
|
||||||
|
local get_git_ignore = ya.sync(function(st) return st.git_ignore ~= false end)
|
||||||
|
local get_git_status = ya.sync(function(st) return st.git_status == true end)
|
||||||
|
|
||||||
|
-- Sync setters (also trigger preview refresh)
|
||||||
|
local set_tree = ya.sync(function(st, val)
|
||||||
|
st.tree = val
|
||||||
|
local h = cx.active.current.hovered
|
||||||
|
if h then ya.emit("peek", { 0, only_if = h.url, force = true }) end
|
||||||
|
end)
|
||||||
|
local set_level = ya.sync(function(st, val)
|
||||||
|
st.level = val
|
||||||
|
local h = cx.active.current.hovered
|
||||||
|
if h then ya.emit("peek", { 0, only_if = h.url, force = true }) end
|
||||||
|
end)
|
||||||
|
local set_follow_symlinks = ya.sync(function(st, val)
|
||||||
|
st.follow_symlinks = val
|
||||||
|
local h = cx.active.current.hovered
|
||||||
|
if h then ya.emit("peek", { 0, only_if = h.url, force = true }) end
|
||||||
|
end)
|
||||||
|
local set_all = ya.sync(function(st, val)
|
||||||
|
st.all = val
|
||||||
|
local h = cx.active.current.hovered
|
||||||
|
if h then ya.emit("peek", { 0, only_if = h.url, force = true }) end
|
||||||
|
end)
|
||||||
|
local set_git_ignore = ya.sync(function(st, val)
|
||||||
|
st.git_ignore = val
|
||||||
|
local h = cx.active.current.hovered
|
||||||
|
if h then ya.emit("peek", { 0, only_if = h.url, force = true }) end
|
||||||
|
end)
|
||||||
|
local set_git_status = ya.sync(function(st, val)
|
||||||
|
st.git_status = val
|
||||||
|
local h = cx.active.current.hovered
|
||||||
|
if h then ya.emit("peek", { 0, only_if = h.url, force = true }) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Setup from user config
|
||||||
|
local apply_config = ya.sync(function(st, cfg)
|
||||||
|
cfg = cfg or {}
|
||||||
|
if cfg.default_tree ~= nil then st.tree = cfg.default_tree end
|
||||||
|
if cfg.level ~= nil then st.level = cfg.level end
|
||||||
|
if cfg.follow_symlinks ~= nil then st.follow_symlinks = cfg.follow_symlinks end
|
||||||
|
if cfg.dereference ~= nil then st.dereference = cfg.dereference end
|
||||||
|
if cfg.all ~= nil then st.all = cfg.all end
|
||||||
|
if cfg.git_ignore ~= nil then st.git_ignore = cfg.git_ignore end
|
||||||
|
if cfg.git_status ~= nil then st.git_status = cfg.git_status end
|
||||||
|
if cfg.icons ~= nil then st.icons = cfg.icons end
|
||||||
|
if cfg.ignore_glob ~= nil then
|
||||||
|
if type(cfg.ignore_glob) == "table" then
|
||||||
|
st.ignore_glob = table.concat(cfg.ignore_glob, "|")
|
||||||
|
else
|
||||||
|
st.ignore_glob = cfg.ignore_glob
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
function M:setup(cfg)
|
||||||
|
apply_config(cfg)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:entry(job)
|
||||||
|
local args = string.gsub(job.args[1] or "", "^%s*(.-)%s*$", "%1")
|
||||||
|
if args == "inc-level" then
|
||||||
|
set_level(get_level() + 1)
|
||||||
|
elseif args == "dec-level" then
|
||||||
|
local lvl = get_level()
|
||||||
|
if lvl > 1 then set_level(lvl - 1) end
|
||||||
|
elseif args == "toggle-follow-symlinks" then
|
||||||
|
set_follow_symlinks(not get_follow_symlinks())
|
||||||
|
elseif args == "toggle-hidden" then
|
||||||
|
set_all(not get_all())
|
||||||
|
elseif args == "toggle-git-ignore" then
|
||||||
|
set_git_ignore(not get_git_ignore())
|
||||||
|
elseif args == "toggle-git-status" then
|
||||||
|
set_git_status(not get_git_status())
|
||||||
|
else
|
||||||
|
set_tree(not get_tree())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:peek(job)
|
||||||
|
-- Single sync call to get all options (avoids 9 separate boundary crossings)
|
||||||
|
local is_tree, level, follow_symlinks, dereference, all, git_ignore, git_status, icons, ignore_glob = get_all_opts()
|
||||||
|
|
||||||
|
local args = {
|
||||||
|
"--color=always",
|
||||||
|
"--group-directories-first",
|
||||||
|
"--no-quotes",
|
||||||
|
tostring(job.file.url),
|
||||||
|
}
|
||||||
|
if is_tree then
|
||||||
|
table.insert(args, "--tree")
|
||||||
|
table.insert(args, string.format("--level=%d", level))
|
||||||
|
end
|
||||||
|
if icons then
|
||||||
|
table.insert(args, "--icons=always")
|
||||||
|
end
|
||||||
|
if follow_symlinks then
|
||||||
|
table.insert(args, "--follow-symlinks")
|
||||||
|
end
|
||||||
|
if all then
|
||||||
|
table.insert(args, "--all")
|
||||||
|
end
|
||||||
|
if dereference then
|
||||||
|
table.insert(args, "--dereference")
|
||||||
|
end
|
||||||
|
if git_status then
|
||||||
|
table.insert(args, "--long")
|
||||||
|
table.insert(args, "--no-permissions")
|
||||||
|
table.insert(args, "--no-user")
|
||||||
|
table.insert(args, "--no-time")
|
||||||
|
table.insert(args, "--no-filesize")
|
||||||
|
table.insert(args, "--git")
|
||||||
|
table.insert(args, "--git-repos")
|
||||||
|
end
|
||||||
|
if git_ignore then
|
||||||
|
table.insert(args, "--git-ignore")
|
||||||
|
end
|
||||||
|
if ignore_glob ~= "" then
|
||||||
|
table.insert(args, "-I")
|
||||||
|
table.insert(args, ignore_glob)
|
||||||
|
end
|
||||||
|
|
||||||
|
local child, err = Command("eza"):arg(args):stdout(Command.PIPED):stderr(Command.PIPED):spawn()
|
||||||
|
if not child then
|
||||||
|
return ya.preview_widget(job, ui.Text("eza: " .. (err or "spawn failed")):area(job.area))
|
||||||
|
end
|
||||||
|
|
||||||
|
local limit = job.area.h
|
||||||
|
local lines = ""
|
||||||
|
local line_count = 0
|
||||||
|
local skipped = 0
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local line, event = child:read_line()
|
||||||
|
if event == 1 then
|
||||||
|
-- stderr, skip
|
||||||
|
elseif event ~= 0 then
|
||||||
|
break
|
||||||
|
elseif skipped < job.skip then
|
||||||
|
skipped = skipped + 1
|
||||||
|
else
|
||||||
|
lines = lines .. line
|
||||||
|
line_count = line_count + 1
|
||||||
|
end
|
||||||
|
until line_count >= limit
|
||||||
|
|
||||||
|
child:start_kill()
|
||||||
|
|
||||||
|
-- tree mode outputs dir name as first line, so empty = 1 line; list mode empty = 0 lines
|
||||||
|
local empty_output = (is_tree and line_count <= 1) or (not is_tree and line_count == 0)
|
||||||
|
|
||||||
|
if job.skip > 0 and line_count < limit then
|
||||||
|
ya.emit("peek", {
|
||||||
|
math.max(0, job.skip - (limit - line_count)),
|
||||||
|
only_if = job.file.url,
|
||||||
|
upper_bound = true,
|
||||||
|
})
|
||||||
|
elseif empty_output then
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Text({ ui.Line("No items") }):area(job.area):align(ui.Align.CENTER),
|
||||||
|
})
|
||||||
|
else
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Text.parse(lines):area(job.area),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:seek(job)
|
||||||
|
local h = cx.active.current.hovered
|
||||||
|
if h and h.url == job.file.url then
|
||||||
|
local step = math.floor(job.units * job.area.h / 10)
|
||||||
|
ya.emit("peek", {
|
||||||
|
math.max(0, cx.active.preview.skip + step),
|
||||||
|
only_if = job.file.url,
|
||||||
|
force = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -15,15 +15,6 @@ ya pkg add AminurAlam/yazi-plugins:fchar
|
|||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
in `~/.config/yazi/keymap.toml`
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[mgr]
|
|
||||||
prepend_keymap = [
|
|
||||||
{ on = "f", run = "plugin fchar", desc = "Jump to char" },
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
in `~/.config/yazi/init.lua`
|
in `~/.config/yazi/init.lua`
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
@@ -35,11 +26,18 @@ require('fchar'):setup {
|
|||||||
skip_symbols = true,
|
skip_symbols = true,
|
||||||
-- if {"yazi-"}: f -> file, yazi-file
|
-- if {"yazi-"}: f -> file, yazi-file
|
||||||
skip_prefix = {},
|
skip_prefix = {},
|
||||||
-- start: f -> file
|
|
||||||
-- word: f -> file, also-file
|
|
||||||
-- all: f -> file, also-file, alsofile, elf
|
|
||||||
search_location = 'start',
|
|
||||||
aliases = {},
|
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
|
-- aliases for German
|
||||||
@@ -79,8 +77,37 @@ require('fchar'):setup {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
-- you may want to turn off the search regex from
|
-- aliases for Chinese, taken from https://github.com/DreamMaoMao/sjch.yazi
|
||||||
-- showing up in the header by doing this
|
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()
|
function Header:flags()
|
||||||
local cwd = self._current.cwd
|
local cwd = self._current.cwd
|
||||||
local filter = self._current.files.filter
|
local filter = self._current.files.filter
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
--- @since 25.5.31
|
--- @since 26.8.15
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
@@ -23,11 +23,9 @@ local get_config = ya.sync(function(st)
|
|||||||
skip_symbols = true,
|
skip_symbols = true,
|
||||||
-- if {"yazi-"}: f -> file, yazi-file
|
-- if {"yazi-"}: f -> file, yazi-file
|
||||||
skip_prefix = {},
|
skip_prefix = {},
|
||||||
-- start: f -> file
|
fallback = true,
|
||||||
-- word: f -> file, also-file
|
|
||||||
-- all: f -> file, also-file, twofile, elf
|
|
||||||
search_location = 'start',
|
|
||||||
aliases = {},
|
aliases = {},
|
||||||
|
keys = {},
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -47,10 +45,19 @@ end
|
|||||||
---@type fun(self, config: FCharConf): nil
|
---@type fun(self, config: FCharConf): nil
|
||||||
function M:setup(config)
|
function M:setup(config)
|
||||||
set_config(tbl_deep_extend(get_config(), 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
|
end
|
||||||
|
|
||||||
-- TODO: process `--flags`
|
---@param job Job
|
||||||
function M:entry()
|
function M:entry(job)
|
||||||
|
if not job.args[1] then
|
||||||
|
ya.err('type of search not specified')
|
||||||
|
end
|
||||||
|
|
||||||
local cands = {
|
local cands = {
|
||||||
{ on = '0' },
|
{ on = '0' },
|
||||||
{ on = '1' },
|
{ on = '1' },
|
||||||
@@ -132,6 +139,7 @@ function M:entry()
|
|||||||
|
|
||||||
local loc = {
|
local loc = {
|
||||||
start = '^',
|
start = '^',
|
||||||
|
ext = '\\.',
|
||||||
word = '(^|\\W)',
|
word = '(^|\\W)',
|
||||||
all = '',
|
all = '',
|
||||||
}
|
}
|
||||||
@@ -142,13 +150,15 @@ function M:entry()
|
|||||||
prefixes = prefixes .. '(' .. prefix .. ')?'
|
prefixes = prefixes .. '(' .. prefix .. ')?'
|
||||||
end
|
end
|
||||||
|
|
||||||
local re = loc[opts.search_location]
|
local re = loc[job.args[1]]
|
||||||
.. ((opts.search_location == 'start' and opts.skip_symbols) and [[\W?]] or '')
|
.. ((job.args[1] == 'start' and opts.skip_symbols) and [[\W?]] or '')
|
||||||
.. prefixes
|
.. prefixes
|
||||||
.. '['
|
.. '['
|
||||||
.. cands[idx].on
|
.. cands[idx].on
|
||||||
.. (opts.aliases[cands[idx].on] or '')
|
.. (opts.aliases[cands[idx].on] or '')
|
||||||
.. ']'
|
.. ']'
|
||||||
|
|
||||||
|
-- TODO: fallback to word/all if no match was found
|
||||||
if changed(re) then
|
if changed(re) then
|
||||||
ya.emit('find_do', { re, insensitive = opts.insensitive })
|
ya.emit('find_do', { re, insensitive = opts.insensitive })
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -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,4 +1,5 @@
|
|||||||
Copyright (c) 2024 Lauri Niskanen
|
Copyright (c) 2024 Lauri Niskanen
|
||||||
|
Copyright (c) 2025 Huy Hoang
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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`.
|
using `ffmpeg` if available and media metadata using `mediainfo`.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> Minimum version: yazi v25.5.31.
|
> Minimum version: yazi v26.1.22.
|
||||||
|
> Check it via command `yazi --debug`
|
||||||
|
|
||||||
## Preview
|
## Preview
|
||||||
|
|
||||||
@@ -70,29 +71,58 @@ Config folder for each OS: https://yazi-rs.github.io/docs/configuration/overview
|
|||||||
|
|
||||||
Create `.../yazi/yazi.toml` and add:
|
Create `.../yazi/yazi.toml` and add:
|
||||||
|
|
||||||
> [!IMPORTANT]
|
`prepend_preloaders` are optional, but highly recommended for faster preview.
|
||||||
>
|
|
||||||
> For yazi (>=v25.12.29) replace `name` with `url`
|
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[plugin]
|
[plugin]
|
||||||
prepend_preloaders = [
|
prepend_preloaders = [
|
||||||
# Replace magick, image, video with mediainfo
|
# Replace magick, image, video with mediainfo
|
||||||
{ mime = "{audio,video,image}/*", run = "mediainfo" },
|
{ mime = "{audio,video,image}/*", run = "mediainfo" },
|
||||||
{ mime = "application/subrip", run = "mediainfo" },
|
# .srt subtitle, adobe illustrator, adobe photoshop file
|
||||||
# Adobe Illustrator, Adobe Photoshop is image/adobe.photoshop, already handled above
|
{ mime = "application/{subrip,postscript,illustrator,dvb.ait,vnd.adobe.illustrator,eps}", run = "mediainfo" },
|
||||||
{ mime = "application/postscript", 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 = [
|
prepend_previewers = [
|
||||||
# Replace magick, image, video with mediainfo
|
# Replace magick, image, video with mediainfo
|
||||||
{ mime = "{audio,video,image}/*", run = "mediainfo"},
|
{ mime = "{audio,video,image}/*", run = "mediainfo"},
|
||||||
{ mime = "application/subrip", run = "mediainfo" },
|
# .srt subtitle, adobe illustrator, adobe photoshop file
|
||||||
# Adobe Illustrator, Adobe Photoshop is image/adobe.photoshop, already handled above
|
{ mime = "application/{subrip,postscript,illustrator,dvb.ait,vnd.adobe.illustrator,eps}", run = "mediainfo" },
|
||||||
{ mime = "application/postscript", 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.
|
# Just add file's MIME type to `previewers`, `preloaders` above.
|
||||||
# https://mediaarea.net/en/MediaInfo/Support/Formats
|
# 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
|
# For a large file like Adobe Illustrator, Adobe Photoshop, etc
|
||||||
# you may need to increase the memory limit if no image is rendered.
|
# 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`:
|
Edit or add `yazi/theme.toml`:
|
||||||
|
|
||||||
@@ -119,7 +149,39 @@ title = { fg = "green" }
|
|||||||
tbl_col = { fg = "blue" }
|
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]
|
> [!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).
|
> 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
|
```toml
|
||||||
[mgr]
|
[mgr]
|
||||||
prepend_keymap = [
|
prepend_keymap = [
|
||||||
{ on = "<F9>", run = "plugin mediainfo -- toggle-metadata", desc = "Toggle media preview metadata" },
|
{ on = "<F3>", run = "plugin mediainfo -- toggle-metadata", desc = "Toggle media preview metadata" },
|
||||||
|
{ on = "<F4>", run = "plugin mediainfo -- toggle-preview", desc = "Toggle media preview image" },
|
||||||
|
|
||||||
|
# Hide and show metadata, image
|
||||||
|
{ on = "<F6>", run = "plugin mediainfo -- hide-metadata", desc = "Hide media preview metadata" },
|
||||||
|
{ on = "<F7>", run = "plugin mediainfo -- hide-preview", desc = "Hide media preview image" },
|
||||||
|
|
||||||
|
{ on = "<F8>", run = "plugin mediainfo -- show-metadata", desc = "Show media preview metadata" },
|
||||||
|
{ on = "<F9>", run = "plugin mediainfo -- show-preview", desc = "Show media preview image" },
|
||||||
|
|
||||||
|
# Reset to defautl settings in yazi.toml file
|
||||||
|
{ on = "<F5>", run = "plugin mediainfo -- reset", desc = "Reset media preview to default settings" },
|
||||||
|
|
||||||
|
# Use multiple actions
|
||||||
|
{ on = "<F10>", run = "plugin mediainfo -- --toggle-preview --toggle-metadata", desc = "Toggle both media preview image and metadata" },
|
||||||
|
{ on = "<F11>", run = "plugin mediainfo -- --show-preview --hide-metadata", desc = "Show media preview image and hide metadata" },
|
||||||
|
# You can also use all of them together (7 actions above). Priority is reset > toggle > hide > show
|
||||||
|
{ on = "<F12>", run = "plugin mediainfo -- --show-preview --hide-metadata --reset --show-metadata --hide-preview", desc = "Show media preview image and hide metadata" },
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
--- @since 26.1.22
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
local const = require(".const")
|
||||||
|
local utils = require(".utils")
|
||||||
|
|
||||||
|
local function image_layer_count(job)
|
||||||
|
local cache = ya.file_cache({ file = job.file, skip = 0 })
|
||||||
|
if not cache then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local layer_count = utils.get_state("f" .. tostring(cache))
|
||||||
|
if layer_count then
|
||||||
|
return layer_count
|
||||||
|
end
|
||||||
|
local output, err = Command("identify")
|
||||||
|
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||||
|
:output()
|
||||||
|
if err or not output then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
layer_count = 0
|
||||||
|
for line in output.stdout:gmatch("[^\r\n]+") do
|
||||||
|
if line:match("%S") then
|
||||||
|
layer_count = layer_count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
utils.set_state("f" .. tostring(cache), layer_count)
|
||||||
|
return layer_count
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:peek(job)
|
||||||
|
local preload_status, preload_err = self:preload(job)
|
||||||
|
-- Stop if preload failed
|
||||||
|
if not preload_status then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local cache_img_url = ya.file_cache(job)
|
||||||
|
|
||||||
|
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||||
|
|
||||||
|
local no_metadata = job.args.no_metadata
|
||||||
|
local mediainfo_job_skip = job.skip
|
||||||
|
::recalc_mediainfo_job_skip::
|
||||||
|
local mediainfo_height = 0
|
||||||
|
local lines = {}
|
||||||
|
local limit = job.area.h
|
||||||
|
local last_line = 0
|
||||||
|
local EOF_mediainfo = true
|
||||||
|
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||||
|
|
||||||
|
if not no_metadata then
|
||||||
|
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||||
|
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||||
|
if output then
|
||||||
|
local max_width = math.max(1, job.area.w)
|
||||||
|
if output:match("^Error:") then
|
||||||
|
job.args.force_reload_mediainfo = true
|
||||||
|
preload_status, preload_err = self:preload(job)
|
||||||
|
if not preload_status or preload_err then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
output = output:gsub("\n+$", "")
|
||||||
|
|
||||||
|
local iter = output:gmatch("[^\n]*")
|
||||||
|
local str = iter()
|
||||||
|
|
||||||
|
while str ~= nil do
|
||||||
|
local next_str = iter()
|
||||||
|
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||||
|
local line
|
||||||
|
if label then
|
||||||
|
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||||
|
line = ui.Line({
|
||||||
|
ui.Span(label .. ": "):style(ui.Style():fg("reset"):bold()),
|
||||||
|
ui.Span(value):style(th.spot.tbl_col or ui.Style():fg("blue")),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||||
|
line = ui.Line({ ui.Span(str):style(th.spot.title or ui.Style():fg("green")) })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if line then
|
||||||
|
local line_height = ui.height
|
||||||
|
and ui.height(str, { width = max_width, ansi = true, wrap = rt.preview.wrap })
|
||||||
|
or (math.max(1, is_wrap and math.ceil(ui.width(line) / max_width) or 1))
|
||||||
|
if next_str == nil and line_height == 1 then
|
||||||
|
EOF_mediainfo = true
|
||||||
|
end
|
||||||
|
if (last_line + line_height) > mediainfo_job_skip then
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
if (last_line + line_height) >= mediainfo_job_skip + limit then
|
||||||
|
last_line = mediainfo_job_skip + limit
|
||||||
|
EOF_mediainfo = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
last_line = last_line + line_height
|
||||||
|
end
|
||||||
|
str = next_str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mediainfo_height = math.min(limit, last_line)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not no_metadata then
|
||||||
|
if EOF_mediainfo and #lines == 0 and mediainfo_job_skip > 0 then
|
||||||
|
if
|
||||||
|
image_layer_count(job)
|
||||||
|
< (
|
||||||
|
1
|
||||||
|
+ math.floor(
|
||||||
|
math.max(
|
||||||
|
0,
|
||||||
|
utils.get_state(const.STATE_KEY.units)
|
||||||
|
and (math.abs(job.skip / utils.get_state(const.STATE_KEY.units)))
|
||||||
|
or 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
then
|
||||||
|
ya.emit("peek", {
|
||||||
|
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||||
|
only_if = job.file.url,
|
||||||
|
upper_bound = true,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
else
|
||||||
|
local last_valid_mediainfo_skip = utils.get_state(const.STATE_KEY.last_valid_mediainfo_skip)
|
||||||
|
mediainfo_job_skip = last_valid_mediainfo_skip
|
||||||
|
and last_valid_mediainfo_skip[tostring(cache_img_url_no_skip)]
|
||||||
|
or math.max(0, mediainfo_job_skip - (utils.get_state(const.STATE_KEY.units) or 0))
|
||||||
|
|
||||||
|
goto recalc_mediainfo_job_skip
|
||||||
|
end
|
||||||
|
else
|
||||||
|
utils.set_state(
|
||||||
|
const.STATE_KEY.last_valid_mediainfo_skip,
|
||||||
|
{ [tostring(cache_img_url_no_skip)] = mediainfo_job_skip }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||||
|
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||||
|
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||||
|
if
|
||||||
|
old_metadata_area.win_x == job.area.x
|
||||||
|
and old_metadata_area.win_y == job.area.y
|
||||||
|
and old_metadata_area.win_w == job.area.w
|
||||||
|
and old_metadata_area.win_h == job.area.h
|
||||||
|
then
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Clear(ui.Rect({
|
||||||
|
x = old_metadata_area.x,
|
||||||
|
y = old_metadata_area.y,
|
||||||
|
w = old_metadata_area.w,
|
||||||
|
h = old_metadata_area.h,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
utils.force_render()
|
||||||
|
|
||||||
|
local rendered_img_rect = cache_img_url
|
||||||
|
and fs.cha(cache_img_url)
|
||||||
|
and ya.image_show(
|
||||||
|
cache_img_url,
|
||||||
|
ui.Rect({
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y,
|
||||||
|
w = job.area.w,
|
||||||
|
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
or nil
|
||||||
|
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||||
|
|
||||||
|
-- Handle image preload error
|
||||||
|
if preload_err then
|
||||||
|
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||||
|
end
|
||||||
|
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Text(lines)
|
||||||
|
:area(ui.Rect({
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y + image_height,
|
||||||
|
w = job.area.w,
|
||||||
|
h = job.area.h - image_height,
|
||||||
|
}))
|
||||||
|
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||||
|
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y + image_height,
|
||||||
|
w = job.area.w,
|
||||||
|
h = job.area.h - image_height,
|
||||||
|
win_x = job.area.x,
|
||||||
|
win_y = job.area.y,
|
||||||
|
win_w = job.area.w,
|
||||||
|
win_h = job.area.h,
|
||||||
|
} or nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:preload(job)
|
||||||
|
local cmd = "mediainfo"
|
||||||
|
local err_msg = ""
|
||||||
|
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||||
|
|
||||||
|
-- NOTE: Preload image
|
||||||
|
|
||||||
|
local cache_img_url = ya.file_cache(job)
|
||||||
|
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||||
|
|
||||||
|
-- NOTE: Only generate preview image when cache image is not exist
|
||||||
|
if not cache_img_url_cha or cache_img_url_cha.len <= 0 then
|
||||||
|
local cache_img_status, image_preload_err
|
||||||
|
local layer_index = 0
|
||||||
|
local units = utils.get_state(const.STATE_KEY.units)
|
||||||
|
if units ~= nil then
|
||||||
|
local max_layer = image_layer_count(job)
|
||||||
|
layer_index = math.floor(math.max(0, math.abs(job.skip / units)))
|
||||||
|
if layer_index + 1 > max_layer then
|
||||||
|
layer_index = math.max(0, max_layer - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local cache_img_url_tmp = Url(cache_img_url .. ".tmp")
|
||||||
|
if fs.cha(cache_img_url_tmp) then
|
||||||
|
fs.remove("file", cache_img_url_tmp)
|
||||||
|
end
|
||||||
|
local tmp_file_path, _ = type(fs.unique) == "function" and fs.unique("file", cache_img_url_tmp)
|
||||||
|
or fs.unique_name(cache_img_url_tmp)
|
||||||
|
cache_img_status, image_preload_err = require("magick")
|
||||||
|
.with_limit()
|
||||||
|
:arg({
|
||||||
|
"-background",
|
||||||
|
"none",
|
||||||
|
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) .. "[" .. tostring(
|
||||||
|
layer_index
|
||||||
|
) .. "]",
|
||||||
|
"-auto-orient",
|
||||||
|
"-strip",
|
||||||
|
"-resize",
|
||||||
|
string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height),
|
||||||
|
"-quality",
|
||||||
|
rt.preview.image_quality,
|
||||||
|
string.format("PNG32:%s", tostring(tmp_file_path)),
|
||||||
|
})
|
||||||
|
:status()
|
||||||
|
if cache_img_status then
|
||||||
|
os.rename(tostring(tmp_file_path), tostring(cache_img_url))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not cache_img_status and image_preload_err then
|
||||||
|
ya.dbg("mediainfo", image_preload_err)
|
||||||
|
err_msg = err_msg .. (image_preload_err and (tostring(image_preload_err)) or "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NOTE: Get mediainfo and save to cache folder
|
||||||
|
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||||
|
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||||
|
-- Case peek function called preload to refetch mediainfo
|
||||||
|
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||||
|
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local output, err
|
||||||
|
if is_valid_utf8_path then
|
||||||
|
output, err = Command(cmd)
|
||||||
|
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||||
|
:output()
|
||||||
|
else
|
||||||
|
cmd = "cd "
|
||||||
|
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||||
|
.. " && "
|
||||||
|
.. cmd
|
||||||
|
.. " "
|
||||||
|
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||||
|
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||||
|
end
|
||||||
|
if err then
|
||||||
|
ya.dbg("mediainfo", tostring(err))
|
||||||
|
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
return fs.write(
|
||||||
|
cache_mediainfo_url,
|
||||||
|
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -0,0 +1,338 @@
|
|||||||
|
--- @since 26.5.6
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
local const = require(".const")
|
||||||
|
local utils = require(".utils")
|
||||||
|
|
||||||
|
local function image_layer_count(job)
|
||||||
|
local cache = ya.file_cache({ file = job.file, skip = 0 })
|
||||||
|
if not cache then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local layer_count = utils.get_state("f" .. tostring(cache))
|
||||||
|
if layer_count then
|
||||||
|
return layer_count
|
||||||
|
end
|
||||||
|
local output, err = Command("identify")
|
||||||
|
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||||
|
:output()
|
||||||
|
if err or not output then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
layer_count = 0
|
||||||
|
for line in output.stdout:gmatch("[^\r\n]+") do
|
||||||
|
if line:match("%S") then
|
||||||
|
layer_count = layer_count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
utils.set_state("f" .. tostring(cache), layer_count)
|
||||||
|
return layer_count
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:peek(job)
|
||||||
|
local preload_status, preload_err = self:preload(job)
|
||||||
|
-- Stop if preload failed
|
||||||
|
if not preload_status then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local cache_img_url = ya.file_cache(job)
|
||||||
|
|
||||||
|
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||||
|
|
||||||
|
local no_metadata = job.args.no_metadata
|
||||||
|
local mediainfo_job_skip = job.skip
|
||||||
|
::recalc_mediainfo_job_skip::
|
||||||
|
local mediainfo_height = 0
|
||||||
|
local lines = {}
|
||||||
|
local limit = job.area.h
|
||||||
|
local last_line = 0
|
||||||
|
local EOF_mediainfo = true
|
||||||
|
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||||
|
|
||||||
|
if not no_metadata then
|
||||||
|
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||||
|
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||||
|
if output then
|
||||||
|
local max_width = math.max(1, job.area.w)
|
||||||
|
if output:match("^Error:") then
|
||||||
|
job.args.force_reload_mediainfo = true
|
||||||
|
preload_status, preload_err = self:preload(job)
|
||||||
|
if not preload_status or preload_err then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
output = output:gsub("\n+$", "")
|
||||||
|
|
||||||
|
local iter = output:gmatch("[^\n]*")
|
||||||
|
local str = iter()
|
||||||
|
local opt = { ansi = true, tab_size = rt.preview.tab_size, wrap = rt.preview.wrap, width = max_width }
|
||||||
|
while str ~= nil do
|
||||||
|
local next_str = iter()
|
||||||
|
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||||
|
|
||||||
|
local line
|
||||||
|
if label then
|
||||||
|
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||||
|
line = label .. ": " .. value
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||||
|
line = str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if line then
|
||||||
|
local wrapped = ui.lines(line, opt)
|
||||||
|
local line_height = #wrapped
|
||||||
|
local from = 1
|
||||||
|
local to = math.min(line_height, mediainfo_job_skip + limit - last_line)
|
||||||
|
|
||||||
|
local total_rendered_text_len = 1
|
||||||
|
local total_label_rendered_len = 1
|
||||||
|
local label_total_len = label and utf8.len(label .. ": ") or 0
|
||||||
|
for j = from, to do
|
||||||
|
local current_line_components = {}
|
||||||
|
local wrapped_line_len = wrapped[j]:width() or 0
|
||||||
|
local wrapped_raw =
|
||||||
|
utils.utf8_sub(line, total_rendered_text_len, total_rendered_text_len + wrapped_line_len)
|
||||||
|
wrapped_line_len = utf8.len(wrapped_raw)
|
||||||
|
total_rendered_text_len = total_rendered_text_len + wrapped_line_len
|
||||||
|
|
||||||
|
if last_line + 1 > mediainfo_job_skip then
|
||||||
|
local label_raw = label_total_len - total_label_rendered_len <= 0 and ""
|
||||||
|
or utils.utf8_sub(wrapped_raw, 1, label_total_len - total_label_rendered_len)
|
||||||
|
local label_raw_len = utf8.len(label_raw)
|
||||||
|
if label_raw_len > 0 then
|
||||||
|
table.insert(
|
||||||
|
current_line_components,
|
||||||
|
ui.Span(label_raw):style(ui.Style():fg("reset"):bold())
|
||||||
|
)
|
||||||
|
total_label_rendered_len = total_label_rendered_len + label_raw_len
|
||||||
|
wrapped_raw = wrapped_raw:gsub("^" .. utils.is_literal_string(label_raw), "", 1)
|
||||||
|
end
|
||||||
|
if total_label_rendered_len >= label_total_len then
|
||||||
|
local value_raw = wrapped_raw
|
||||||
|
table.insert(
|
||||||
|
current_line_components,
|
||||||
|
ui.Span(value_raw or ""):style(
|
||||||
|
label and (th.spot.tbl_col or ui.Style():fg("blue"))
|
||||||
|
or (th.spot.title or ui.Style():fg("green"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
table.insert(lines, ui.Line(current_line_components))
|
||||||
|
-- last_line = last_line + 1
|
||||||
|
else
|
||||||
|
total_label_rendered_len = total_label_rendered_len + total_rendered_text_len
|
||||||
|
end
|
||||||
|
last_line = last_line + 1
|
||||||
|
if next_str == nil and not wrapped[j + 1] then
|
||||||
|
EOF_mediainfo = true
|
||||||
|
end
|
||||||
|
if last_line >= mediainfo_job_skip + limit then
|
||||||
|
last_line = mediainfo_job_skip + limit
|
||||||
|
EOF_mediainfo = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
str = next_str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mediainfo_height = math.min(limit, last_line)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not no_metadata then
|
||||||
|
if EOF_mediainfo and #lines == 0 and mediainfo_job_skip > 0 then
|
||||||
|
if
|
||||||
|
image_layer_count(job)
|
||||||
|
< (
|
||||||
|
1
|
||||||
|
+ math.floor(
|
||||||
|
math.max(
|
||||||
|
0,
|
||||||
|
utils.get_state(const.STATE_KEY.units)
|
||||||
|
and (math.abs(job.skip / utils.get_state(const.STATE_KEY.units)))
|
||||||
|
or 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
then
|
||||||
|
ya.emit("peek", {
|
||||||
|
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||||
|
only_if = job.file.url,
|
||||||
|
upper_bound = true,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
else
|
||||||
|
local last_valid_mediainfo_skip = utils.get_state(const.STATE_KEY.last_valid_mediainfo_skip)
|
||||||
|
mediainfo_job_skip = last_valid_mediainfo_skip
|
||||||
|
and last_valid_mediainfo_skip[tostring(cache_img_url_no_skip)]
|
||||||
|
or math.max(0, mediainfo_job_skip - (utils.get_state(const.STATE_KEY.units) or 0))
|
||||||
|
|
||||||
|
goto recalc_mediainfo_job_skip
|
||||||
|
end
|
||||||
|
else
|
||||||
|
utils.set_state(
|
||||||
|
const.STATE_KEY.last_valid_mediainfo_skip,
|
||||||
|
{ [tostring(cache_img_url_no_skip)] = mediainfo_job_skip }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||||
|
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||||
|
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||||
|
if
|
||||||
|
old_metadata_area.win_x == job.area.x
|
||||||
|
and old_metadata_area.win_y == job.area.y
|
||||||
|
and old_metadata_area.win_w == job.area.w
|
||||||
|
and old_metadata_area.win_h == job.area.h
|
||||||
|
then
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Clear(ui.Rect({
|
||||||
|
x = old_metadata_area.x,
|
||||||
|
y = old_metadata_area.y,
|
||||||
|
w = old_metadata_area.w,
|
||||||
|
h = old_metadata_area.h,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
utils.force_render()
|
||||||
|
|
||||||
|
local rendered_img_rect = cache_img_url
|
||||||
|
and fs.cha(cache_img_url)
|
||||||
|
and ya.image_show(
|
||||||
|
cache_img_url,
|
||||||
|
ui.Rect({
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y,
|
||||||
|
w = job.area.w,
|
||||||
|
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
or nil
|
||||||
|
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||||
|
|
||||||
|
-- Handle image preload error
|
||||||
|
if preload_err then
|
||||||
|
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||||
|
end
|
||||||
|
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Text(lines)
|
||||||
|
:area(ui.Rect({
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y + image_height,
|
||||||
|
w = job.area.w,
|
||||||
|
h = job.area.h - image_height,
|
||||||
|
}))
|
||||||
|
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||||
|
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y + image_height,
|
||||||
|
w = job.area.w,
|
||||||
|
h = job.area.h - image_height,
|
||||||
|
win_x = job.area.x,
|
||||||
|
win_y = job.area.y,
|
||||||
|
win_w = job.area.w,
|
||||||
|
win_h = job.area.h,
|
||||||
|
} or nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:preload(job)
|
||||||
|
local cmd = "mediainfo"
|
||||||
|
local err_msg = ""
|
||||||
|
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||||
|
|
||||||
|
-- NOTE: Preload image
|
||||||
|
|
||||||
|
local cache_img_url = ya.file_cache(job)
|
||||||
|
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||||
|
|
||||||
|
-- NOTE: Only generate preview image when cache image is not exist
|
||||||
|
if not cache_img_url_cha or cache_img_url_cha.len <= 0 then
|
||||||
|
local cache_img_status, image_preload_err
|
||||||
|
local layer_index = 0
|
||||||
|
local units = utils.get_state(const.STATE_KEY.units)
|
||||||
|
if units ~= nil then
|
||||||
|
local max_layer = image_layer_count(job)
|
||||||
|
layer_index = math.floor(math.max(0, math.abs(job.skip / units)))
|
||||||
|
if layer_index + 1 > max_layer then
|
||||||
|
layer_index = math.max(0, max_layer - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local cache_img_url_tmp = Url(cache_img_url .. ".tmp")
|
||||||
|
if fs.cha(cache_img_url_tmp) then
|
||||||
|
fs.remove("file", cache_img_url_tmp)
|
||||||
|
end
|
||||||
|
local tmp_file_path, _ = type(fs.unique) == "function" and fs.unique("file", cache_img_url_tmp)
|
||||||
|
or fs.unique_name(cache_img_url_tmp)
|
||||||
|
cache_img_status, image_preload_err = require("magick")
|
||||||
|
.with_limit()
|
||||||
|
:arg({
|
||||||
|
"-background",
|
||||||
|
"none",
|
||||||
|
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) .. "[" .. tostring(
|
||||||
|
layer_index
|
||||||
|
) .. "]",
|
||||||
|
"-auto-orient",
|
||||||
|
"-strip",
|
||||||
|
"-resize",
|
||||||
|
string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height),
|
||||||
|
"-quality",
|
||||||
|
rt.preview.image_quality,
|
||||||
|
string.format("PNG32:%s", tostring(tmp_file_path)),
|
||||||
|
})
|
||||||
|
:status()
|
||||||
|
if cache_img_status then
|
||||||
|
os.rename(tostring(tmp_file_path), tostring(cache_img_url))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not cache_img_status and image_preload_err then
|
||||||
|
ya.dbg("mediainfo", image_preload_err)
|
||||||
|
err_msg = err_msg .. (image_preload_err and (tostring(image_preload_err)) or "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NOTE: Get mediainfo and save to cache folder
|
||||||
|
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||||
|
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||||
|
-- Case peek function called preload to refetch mediainfo
|
||||||
|
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||||
|
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local output, err
|
||||||
|
if is_valid_utf8_path then
|
||||||
|
output, err = Command(cmd)
|
||||||
|
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||||
|
:output()
|
||||||
|
else
|
||||||
|
cmd = "cd "
|
||||||
|
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||||
|
.. " && "
|
||||||
|
.. cmd
|
||||||
|
.. " "
|
||||||
|
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||||
|
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||||
|
end
|
||||||
|
if err then
|
||||||
|
ya.dbg("mediainfo", tostring(err))
|
||||||
|
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
return fs.write(
|
||||||
|
cache_mediainfo_url,
|
||||||
|
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
--- @since 26.1.22
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
local const = require(".const")
|
||||||
|
local utils = require(".utils")
|
||||||
|
|
||||||
|
local function get_cover_layers(job)
|
||||||
|
local cache = ya.file_cache({ file = job.file, skip = 0 })
|
||||||
|
if not cache then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
local covers = utils.get_state("f" .. tostring(cache))
|
||||||
|
if covers and type(covers) == "table" then
|
||||||
|
return covers
|
||||||
|
end
|
||||||
|
local output, err = Command("ffprobe"):arg({
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-select_streams",
|
||||||
|
"v",
|
||||||
|
"-show_entries",
|
||||||
|
"stream=index:stream_disposition=attached_pic",
|
||||||
|
"-of",
|
||||||
|
"json",
|
||||||
|
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||||
|
}):output()
|
||||||
|
if err or not output then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
covers = {}
|
||||||
|
local data = ya.json_decode(output.stdout)
|
||||||
|
if type(data.streams) == "table" then
|
||||||
|
for _, stream in ipairs(data.streams) do
|
||||||
|
if stream.disposition and stream.disposition.attached_pic then
|
||||||
|
covers[#covers + 1] = stream.index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
utils.set_state("f" .. tostring(cache), covers)
|
||||||
|
return covers
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:peek(job)
|
||||||
|
local preload_status, preload_err = self:preload(job)
|
||||||
|
-- Stop if preload failed
|
||||||
|
if not preload_status then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local cache_img_url = ya.file_cache(job)
|
||||||
|
|
||||||
|
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||||
|
|
||||||
|
local no_metadata = job.args.no_metadata
|
||||||
|
local mediainfo_job_skip = job.skip
|
||||||
|
::recalc_mediainfo_job_skip::
|
||||||
|
local mediainfo_height = 0
|
||||||
|
local lines = {}
|
||||||
|
local limit = job.area.h
|
||||||
|
local last_line = 0
|
||||||
|
local EOF_mediainfo = true
|
||||||
|
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||||
|
|
||||||
|
if not no_metadata then
|
||||||
|
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||||
|
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||||
|
if output then
|
||||||
|
local max_width = math.max(1, job.area.w)
|
||||||
|
if output:match("^Error:") then
|
||||||
|
job.args.force_reload_mediainfo = true
|
||||||
|
preload_status, preload_err = self:preload(job)
|
||||||
|
if not preload_status or preload_err then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
output = output:gsub("\n+$", "")
|
||||||
|
|
||||||
|
local iter = output:gmatch("[^\n]*")
|
||||||
|
local str = iter()
|
||||||
|
|
||||||
|
while str ~= nil do
|
||||||
|
local next_str = iter()
|
||||||
|
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||||
|
local line
|
||||||
|
if label then
|
||||||
|
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||||
|
line = ui.Line({
|
||||||
|
ui.Span(label .. ": "):style(ui.Style():fg("reset"):bold()),
|
||||||
|
ui.Span(value):style(th.spot.tbl_col or ui.Style():fg("blue")),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||||
|
line = ui.Line({ ui.Span(str):style(th.spot.title or ui.Style():fg("green")) })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if line then
|
||||||
|
local line_height = ui.height
|
||||||
|
and ui.height(str, { width = max_width, ansi = true, wrap = rt.preview.wrap })
|
||||||
|
or (math.max(1, is_wrap and math.ceil(ui.width(line) / max_width) or 1))
|
||||||
|
if next_str == nil and line_height == 1 then
|
||||||
|
EOF_mediainfo = true
|
||||||
|
end
|
||||||
|
if (last_line + line_height) > mediainfo_job_skip then
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
if (last_line + line_height) >= mediainfo_job_skip + limit then
|
||||||
|
last_line = mediainfo_job_skip + limit
|
||||||
|
EOF_mediainfo = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
last_line = last_line + line_height
|
||||||
|
end
|
||||||
|
str = next_str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mediainfo_height = math.min(limit, last_line)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not no_metadata then
|
||||||
|
if EOF_mediainfo and #lines == 0 and mediainfo_job_skip > 0 then
|
||||||
|
local covers = get_cover_layers(job)
|
||||||
|
local cover_index = math.floor(
|
||||||
|
math.max(
|
||||||
|
1,
|
||||||
|
utils.get_state(const.STATE_KEY.units)
|
||||||
|
and (math.abs(job.skip / utils.get_state(const.STATE_KEY.units)))
|
||||||
|
or 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not covers[cover_index] then
|
||||||
|
ya.emit("peek", {
|
||||||
|
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||||
|
only_if = job.file.url,
|
||||||
|
upper_bound = true,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
else
|
||||||
|
-- NOTE: Recalculate mediainfo using cached latest valid skip value when reach the end of mediainfo output
|
||||||
|
local last_valid_mediainfo_skip = utils.get_state(const.STATE_KEY.last_valid_mediainfo_skip)
|
||||||
|
mediainfo_job_skip = last_valid_mediainfo_skip
|
||||||
|
and last_valid_mediainfo_skip[tostring(cache_img_url_no_skip)]
|
||||||
|
or math.max(0, mediainfo_job_skip - (utils.get_state(const.STATE_KEY.units) or 0))
|
||||||
|
|
||||||
|
goto recalc_mediainfo_job_skip
|
||||||
|
end
|
||||||
|
else
|
||||||
|
utils.set_state(
|
||||||
|
const.STATE_KEY.last_valid_mediainfo_skip,
|
||||||
|
{ [tostring(cache_img_url_no_skip)] = mediainfo_job_skip }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||||
|
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||||
|
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||||
|
if
|
||||||
|
old_metadata_area.win_x == job.area.x
|
||||||
|
and old_metadata_area.win_y == job.area.y
|
||||||
|
and old_metadata_area.win_w == job.area.w
|
||||||
|
and old_metadata_area.win_h == job.area.h
|
||||||
|
then
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Clear(ui.Rect({
|
||||||
|
x = old_metadata_area.x,
|
||||||
|
y = old_metadata_area.y,
|
||||||
|
w = old_metadata_area.w,
|
||||||
|
h = old_metadata_area.h,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
utils.force_render()
|
||||||
|
|
||||||
|
local rendered_img_rect = cache_img_url
|
||||||
|
and fs.cha(cache_img_url)
|
||||||
|
and ya.image_show(
|
||||||
|
cache_img_url,
|
||||||
|
ui.Rect({
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y,
|
||||||
|
w = job.area.w,
|
||||||
|
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
or nil
|
||||||
|
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||||
|
|
||||||
|
-- Handle image preload error
|
||||||
|
if preload_err then
|
||||||
|
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||||
|
end
|
||||||
|
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Text(lines)
|
||||||
|
:area(ui.Rect({
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y + image_height,
|
||||||
|
w = job.area.w,
|
||||||
|
h = job.area.h - image_height,
|
||||||
|
}))
|
||||||
|
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||||
|
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y + image_height,
|
||||||
|
w = job.area.w,
|
||||||
|
h = job.area.h - image_height,
|
||||||
|
win_x = job.area.x,
|
||||||
|
win_y = job.area.y,
|
||||||
|
win_w = job.area.w,
|
||||||
|
win_h = job.area.h,
|
||||||
|
} or nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:preload(job)
|
||||||
|
local cmd = "mediainfo"
|
||||||
|
local err_msg = ""
|
||||||
|
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||||
|
|
||||||
|
-- NOTE: Preload image
|
||||||
|
|
||||||
|
local cache_img_url = ya.file_cache(job)
|
||||||
|
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||||
|
|
||||||
|
-- NOTE: Only generate preview image when cache image is not exist
|
||||||
|
if cache_img_url and (not cache_img_url_cha or cache_img_url_cha.len <= 0) then
|
||||||
|
local units = utils.get_state(const.STATE_KEY.units)
|
||||||
|
local covers = get_cover_layers(job)
|
||||||
|
local cover_index = covers[1] or 0
|
||||||
|
if units ~= nil then
|
||||||
|
cover_index = math.floor(math.max(1, math.abs(job.skip / units)))
|
||||||
|
if not covers[cover_index] then
|
||||||
|
cover_index = math.max(0, #covers - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local qv = 31 - math.floor(rt.preview.image_quality * 0.3)
|
||||||
|
local audio_preload_output, audio_preload_err = Command("ffmpeg"):arg({
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-threads",
|
||||||
|
1,
|
||||||
|
"-i",
|
||||||
|
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||||
|
"-map",
|
||||||
|
string.format("0:v:%d?", cover_index),
|
||||||
|
"-an",
|
||||||
|
"-sn",
|
||||||
|
"-dn",
|
||||||
|
"-vframes",
|
||||||
|
1,
|
||||||
|
"-q:v",
|
||||||
|
qv,
|
||||||
|
"-vf",
|
||||||
|
string.format("scale=-1:'min(%d,ih)':flags=fast_bilinear", rt.preview.max_height / 2),
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-y",
|
||||||
|
tostring(cache_img_url),
|
||||||
|
}):output()
|
||||||
|
-- NOTE: Some audio types doesn't have cover image -> error ""
|
||||||
|
if audio_preload_err then
|
||||||
|
ya.dbg("mediainfo", audio_preload_err)
|
||||||
|
err_msg = err_msg
|
||||||
|
.. string.format(
|
||||||
|
"Failed to start `%s`.\n Error: %s\n",
|
||||||
|
"ffmpeg",
|
||||||
|
tostring(audio_preload_err or (audio_preload_output and audio_preload_output.stderr or ""))
|
||||||
|
)
|
||||||
|
elseif
|
||||||
|
audio_preload_output
|
||||||
|
and type(audio_preload_output.stderr) == "string"
|
||||||
|
and audio_preload_output.stderr:find("does not contain any stream")
|
||||||
|
then
|
||||||
|
ya.dbg("mediainfo", audio_preload_output and audio_preload_output.stderr)
|
||||||
|
cache_img_url_cha, _ = fs.cha(cache_img_url)
|
||||||
|
if cache_img_url_cha then
|
||||||
|
fs.remove("file", Url(cache_img_url))
|
||||||
|
end
|
||||||
|
-- NOTE: Workaround case audio has no cover image. Prevent regenerate preview image
|
||||||
|
audio_preload_output, audio_preload_err = require("magick")
|
||||||
|
.with_limit()
|
||||||
|
:arg({
|
||||||
|
"-size",
|
||||||
|
"1x1",
|
||||||
|
"canvas:none",
|
||||||
|
string.format("PNG32:%s", cache_img_url),
|
||||||
|
})
|
||||||
|
:output()
|
||||||
|
if
|
||||||
|
(audio_preload_output and audio_preload_output.stderr ~= nil and audio_preload_output.stderr ~= "")
|
||||||
|
or audio_preload_err
|
||||||
|
then
|
||||||
|
ya.dbg("mediainfo", audio_preload_err or (audio_preload_output and audio_preload_output.stderr))
|
||||||
|
err_msg = err_msg
|
||||||
|
.. string.format(
|
||||||
|
"Failed to start `%s`.\n Error: %s\n",
|
||||||
|
"magick",
|
||||||
|
tostring(audio_preload_err or (audio_preload_output and audio_preload_output.stderr or ""))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NOTE: Get mediainfo and save to cache folder
|
||||||
|
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||||
|
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||||
|
-- Case peek function called preload to refetch mediainfo
|
||||||
|
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||||
|
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local output, err
|
||||||
|
if is_valid_utf8_path then
|
||||||
|
output, err = Command(cmd)
|
||||||
|
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||||
|
:output()
|
||||||
|
else
|
||||||
|
cmd = "cd "
|
||||||
|
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||||
|
.. " && "
|
||||||
|
.. cmd
|
||||||
|
.. " "
|
||||||
|
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||||
|
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||||
|
end
|
||||||
|
if err then
|
||||||
|
ya.dbg("mediainfo", tostring(err))
|
||||||
|
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
return fs.write(
|
||||||
|
cache_mediainfo_url,
|
||||||
|
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -0,0 +1,381 @@
|
|||||||
|
--- @since 26.5.6
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
local const = require(".const")
|
||||||
|
local utils = require(".utils")
|
||||||
|
|
||||||
|
local function get_cover_layers(job)
|
||||||
|
local cache = ya.file_cache({ file = job.file, skip = 0 })
|
||||||
|
if not cache then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
local covers = utils.get_state("f" .. tostring(cache))
|
||||||
|
if covers and type(covers) == "table" then
|
||||||
|
return covers
|
||||||
|
end
|
||||||
|
local output, err = Command("ffprobe"):arg({
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-select_streams",
|
||||||
|
"v",
|
||||||
|
"-show_entries",
|
||||||
|
"stream=index:stream_disposition=attached_pic",
|
||||||
|
"-of",
|
||||||
|
"json",
|
||||||
|
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||||
|
}):output()
|
||||||
|
if err or not output then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
covers = {}
|
||||||
|
local data = ya.json_decode(output.stdout)
|
||||||
|
if type(data.streams) == "table" then
|
||||||
|
for _, stream in ipairs(data.streams) do
|
||||||
|
if stream.disposition and stream.disposition.attached_pic then
|
||||||
|
covers[#covers + 1] = stream.index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
utils.set_state("f" .. tostring(cache), covers)
|
||||||
|
return covers
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:peek(job)
|
||||||
|
local preload_status, preload_err = self:preload(job)
|
||||||
|
-- Stop if preload failed
|
||||||
|
if not preload_status then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local cache_img_url = ya.file_cache(job)
|
||||||
|
|
||||||
|
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||||
|
|
||||||
|
local no_metadata = job.args.no_metadata
|
||||||
|
local mediainfo_job_skip = job.skip
|
||||||
|
::recalc_mediainfo_job_skip::
|
||||||
|
local mediainfo_height = 0
|
||||||
|
local lines = {}
|
||||||
|
local limit = job.area.h
|
||||||
|
local last_line = 0
|
||||||
|
local EOF_mediainfo = true
|
||||||
|
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||||
|
|
||||||
|
if not no_metadata then
|
||||||
|
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||||
|
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||||
|
if output then
|
||||||
|
local max_width = math.max(1, job.area.w)
|
||||||
|
if output:match("^Error:") then
|
||||||
|
job.args.force_reload_mediainfo = true
|
||||||
|
preload_status, preload_err = self:preload(job)
|
||||||
|
if not preload_status or preload_err then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
output = output:gsub("\n+$", "")
|
||||||
|
|
||||||
|
local iter = output:gmatch("[^\n]*")
|
||||||
|
local str = iter()
|
||||||
|
local opt = { ansi = true, tab_size = rt.preview.tab_size, wrap = rt.preview.wrap, width = max_width }
|
||||||
|
while str ~= nil do
|
||||||
|
local next_str = iter()
|
||||||
|
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||||
|
|
||||||
|
local line
|
||||||
|
if label then
|
||||||
|
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||||
|
line = label .. ": " .. value
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||||
|
line = str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if line then
|
||||||
|
local wrapped = ui.lines(line, opt)
|
||||||
|
local line_height = #wrapped
|
||||||
|
local from = 1
|
||||||
|
local to = math.min(line_height, mediainfo_job_skip + limit - last_line)
|
||||||
|
|
||||||
|
local total_rendered_text_len = 1
|
||||||
|
local total_label_rendered_len = 1
|
||||||
|
local label_total_len = label and utf8.len(label .. ": ") or 0
|
||||||
|
for j = from, to do
|
||||||
|
local current_line_components = {}
|
||||||
|
local wrapped_line_len = wrapped[j]:width() or 0
|
||||||
|
local wrapped_raw =
|
||||||
|
utils.utf8_sub(line, total_rendered_text_len, total_rendered_text_len + wrapped_line_len)
|
||||||
|
wrapped_line_len = utf8.len(wrapped_raw)
|
||||||
|
total_rendered_text_len = total_rendered_text_len + wrapped_line_len
|
||||||
|
|
||||||
|
if last_line + 1 > mediainfo_job_skip then
|
||||||
|
local label_raw = label_total_len - total_label_rendered_len <= 0 and ""
|
||||||
|
or utils.utf8_sub(wrapped_raw, 1, label_total_len - total_label_rendered_len)
|
||||||
|
local label_raw_len = utf8.len(label_raw)
|
||||||
|
if label_raw_len > 0 then
|
||||||
|
table.insert(
|
||||||
|
current_line_components,
|
||||||
|
ui.Span(label_raw):style(ui.Style():fg("reset"):bold())
|
||||||
|
)
|
||||||
|
total_label_rendered_len = total_label_rendered_len + label_raw_len
|
||||||
|
wrapped_raw = wrapped_raw:gsub("^" .. utils.is_literal_string(label_raw), "", 1)
|
||||||
|
end
|
||||||
|
if total_label_rendered_len >= label_total_len then
|
||||||
|
local value_raw = wrapped_raw
|
||||||
|
table.insert(
|
||||||
|
current_line_components,
|
||||||
|
ui.Span(value_raw or ""):style(
|
||||||
|
label and (th.spot.tbl_col or ui.Style():fg("blue"))
|
||||||
|
or (th.spot.title or ui.Style():fg("green"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
table.insert(lines, ui.Line(current_line_components))
|
||||||
|
-- last_line = last_line + 1
|
||||||
|
else
|
||||||
|
total_label_rendered_len = total_label_rendered_len + total_rendered_text_len
|
||||||
|
end
|
||||||
|
last_line = last_line + 1
|
||||||
|
if next_str == nil and not wrapped[j + 1] then
|
||||||
|
EOF_mediainfo = true
|
||||||
|
end
|
||||||
|
if last_line >= mediainfo_job_skip + limit then
|
||||||
|
last_line = mediainfo_job_skip + limit
|
||||||
|
EOF_mediainfo = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
str = next_str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mediainfo_height = math.min(limit, last_line)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not no_metadata then
|
||||||
|
if EOF_mediainfo and #lines == 0 and mediainfo_job_skip > 0 then
|
||||||
|
local covers = get_cover_layers(job)
|
||||||
|
local cover_index = math.floor(
|
||||||
|
math.max(
|
||||||
|
1,
|
||||||
|
utils.get_state(const.STATE_KEY.units)
|
||||||
|
and (math.abs(job.skip / utils.get_state(const.STATE_KEY.units)))
|
||||||
|
or 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not covers[cover_index] then
|
||||||
|
ya.emit("peek", {
|
||||||
|
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||||
|
only_if = job.file.url,
|
||||||
|
upper_bound = true,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
else
|
||||||
|
-- NOTE: Recalculate mediainfo using cached latest valid skip value when reach the end of mediainfo output
|
||||||
|
local last_valid_mediainfo_skip = utils.get_state(const.STATE_KEY.last_valid_mediainfo_skip)
|
||||||
|
mediainfo_job_skip = last_valid_mediainfo_skip
|
||||||
|
and last_valid_mediainfo_skip[tostring(cache_img_url_no_skip)]
|
||||||
|
or math.max(0, mediainfo_job_skip - (utils.get_state(const.STATE_KEY.units) or 0))
|
||||||
|
|
||||||
|
goto recalc_mediainfo_job_skip
|
||||||
|
end
|
||||||
|
else
|
||||||
|
utils.set_state(
|
||||||
|
const.STATE_KEY.last_valid_mediainfo_skip,
|
||||||
|
{ [tostring(cache_img_url_no_skip)] = mediainfo_job_skip }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||||
|
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||||
|
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||||
|
if
|
||||||
|
old_metadata_area.win_x == job.area.x
|
||||||
|
and old_metadata_area.win_y == job.area.y
|
||||||
|
and old_metadata_area.win_w == job.area.w
|
||||||
|
and old_metadata_area.win_h == job.area.h
|
||||||
|
then
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Clear(ui.Rect({
|
||||||
|
x = old_metadata_area.x,
|
||||||
|
y = old_metadata_area.y,
|
||||||
|
w = old_metadata_area.w,
|
||||||
|
h = old_metadata_area.h,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
utils.force_render()
|
||||||
|
|
||||||
|
local rendered_img_rect = cache_img_url
|
||||||
|
and fs.cha(cache_img_url)
|
||||||
|
and ya.image_show(
|
||||||
|
cache_img_url,
|
||||||
|
ui.Rect({
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y,
|
||||||
|
w = job.area.w,
|
||||||
|
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
or nil
|
||||||
|
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||||
|
|
||||||
|
-- Handle image preload error
|
||||||
|
if preload_err then
|
||||||
|
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||||
|
end
|
||||||
|
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Text(lines)
|
||||||
|
:area(ui.Rect({
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y + image_height,
|
||||||
|
w = job.area.w,
|
||||||
|
h = job.area.h - image_height,
|
||||||
|
}))
|
||||||
|
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||||
|
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y + image_height,
|
||||||
|
w = job.area.w,
|
||||||
|
h = job.area.h - image_height,
|
||||||
|
win_x = job.area.x,
|
||||||
|
win_y = job.area.y,
|
||||||
|
win_w = job.area.w,
|
||||||
|
win_h = job.area.h,
|
||||||
|
} or nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:preload(job)
|
||||||
|
local cmd = "mediainfo"
|
||||||
|
local err_msg = ""
|
||||||
|
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||||
|
|
||||||
|
-- NOTE: Preload image
|
||||||
|
|
||||||
|
local cache_img_url = ya.file_cache(job)
|
||||||
|
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||||
|
|
||||||
|
-- NOTE: Only generate preview image when cache image is not exist
|
||||||
|
if cache_img_url and (not cache_img_url_cha or cache_img_url_cha.len <= 0) then
|
||||||
|
local units = utils.get_state(const.STATE_KEY.units)
|
||||||
|
local covers = get_cover_layers(job)
|
||||||
|
local cover_index = covers[1] or 0
|
||||||
|
if units ~= nil then
|
||||||
|
cover_index = math.floor(math.max(1, math.abs(job.skip / units)))
|
||||||
|
if not covers[cover_index] then
|
||||||
|
cover_index = math.max(0, #covers - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local qv = 31 - math.floor(rt.preview.image_quality * 0.3)
|
||||||
|
local audio_preload_output, audio_preload_err = Command("ffmpeg"):arg({
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-threads",
|
||||||
|
1,
|
||||||
|
"-i",
|
||||||
|
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||||
|
"-map",
|
||||||
|
string.format("0:v:%d?", cover_index),
|
||||||
|
"-an",
|
||||||
|
"-sn",
|
||||||
|
"-dn",
|
||||||
|
"-vframes",
|
||||||
|
1,
|
||||||
|
"-q:v",
|
||||||
|
qv,
|
||||||
|
"-vf",
|
||||||
|
string.format("scale=-1:'min(%d,ih)':flags=fast_bilinear", rt.preview.max_height / 2),
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-y",
|
||||||
|
tostring(cache_img_url),
|
||||||
|
}):output()
|
||||||
|
-- NOTE: Some audio types doesn't have cover image -> error ""
|
||||||
|
if audio_preload_err then
|
||||||
|
ya.dbg("mediainfo", audio_preload_err)
|
||||||
|
err_msg = err_msg
|
||||||
|
.. string.format(
|
||||||
|
"Failed to start `%s`.\n Error: %s\n",
|
||||||
|
"ffmpeg",
|
||||||
|
tostring(audio_preload_err or (audio_preload_output and audio_preload_output.stderr or ""))
|
||||||
|
)
|
||||||
|
elseif
|
||||||
|
audio_preload_output
|
||||||
|
and type(audio_preload_output.stderr) == "string"
|
||||||
|
and audio_preload_output.stderr:find("does not contain any stream")
|
||||||
|
then
|
||||||
|
ya.dbg("mediainfo", audio_preload_output and audio_preload_output.stderr)
|
||||||
|
cache_img_url_cha, _ = fs.cha(cache_img_url)
|
||||||
|
if cache_img_url_cha then
|
||||||
|
fs.remove("file", Url(cache_img_url))
|
||||||
|
end
|
||||||
|
-- NOTE: Workaround case audio has no cover image. Prevent regenerate preview image
|
||||||
|
audio_preload_output, audio_preload_err = require("magick")
|
||||||
|
.with_limit()
|
||||||
|
:arg({
|
||||||
|
"-size",
|
||||||
|
"1x1",
|
||||||
|
"canvas:none",
|
||||||
|
string.format("PNG32:%s", cache_img_url),
|
||||||
|
})
|
||||||
|
:output()
|
||||||
|
if
|
||||||
|
(audio_preload_output and audio_preload_output.stderr ~= nil and audio_preload_output.stderr ~= "")
|
||||||
|
or audio_preload_err
|
||||||
|
then
|
||||||
|
ya.dbg("mediainfo", audio_preload_err or (audio_preload_output and audio_preload_output.stderr))
|
||||||
|
err_msg = err_msg
|
||||||
|
.. string.format(
|
||||||
|
"Failed to start `%s`.\n Error: %s\n",
|
||||||
|
"magick",
|
||||||
|
tostring(audio_preload_err or (audio_preload_output and audio_preload_output.stderr or ""))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NOTE: Get mediainfo and save to cache folder
|
||||||
|
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||||
|
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||||
|
-- Case peek function called preload to refetch mediainfo
|
||||||
|
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||||
|
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local output, err
|
||||||
|
if is_valid_utf8_path then
|
||||||
|
output, err = Command(cmd)
|
||||||
|
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||||
|
:output()
|
||||||
|
else
|
||||||
|
cmd = "cd "
|
||||||
|
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||||
|
.. " && "
|
||||||
|
.. cmd
|
||||||
|
.. " "
|
||||||
|
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||||
|
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||||
|
end
|
||||||
|
if err then
|
||||||
|
ya.dbg("mediainfo", tostring(err))
|
||||||
|
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
return fs.write(
|
||||||
|
cache_mediainfo_url,
|
||||||
|
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
--- @since 26.1.22
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.skip_labels = {
|
||||||
|
["Complete name"] = true,
|
||||||
|
["CompleteName_Last"] = true,
|
||||||
|
["Unique ID"] = true,
|
||||||
|
["File size"] = true,
|
||||||
|
["Format/Info"] = true,
|
||||||
|
["Codec ID/Info"] = true,
|
||||||
|
["MD5 of the unencoded content"] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
M.ENTRY_ACTION = {
|
||||||
|
toggle_metadata = "toggle-metadata",
|
||||||
|
toggle_preview = "toggle-preview",
|
||||||
|
hide_metadata = "hide-metadata",
|
||||||
|
hide_preview = "hide-preview",
|
||||||
|
show_metadata = "show-metadata",
|
||||||
|
show_preview = "show-preview",
|
||||||
|
reset = "reset",
|
||||||
|
}
|
||||||
|
|
||||||
|
M.STATE_KEY = {
|
||||||
|
skip_labels = "skip_labels",
|
||||||
|
skip_section_labels = "skip_section_labels",
|
||||||
|
units = "units",
|
||||||
|
no_metadata = "no_metadata",
|
||||||
|
no_preview = "no_preview",
|
||||||
|
prev_metadata_area = "prev_metadata_area",
|
||||||
|
prev_image_height = "prev_image_height",
|
||||||
|
last_valid_mediainfo_skip = "last_valid_mediainfo_skip",
|
||||||
|
cached_mediainfo = "cached_mediainfo",
|
||||||
|
cached_job_args = "cached_job_args",
|
||||||
|
}
|
||||||
|
|
||||||
|
M.magick_image_mimes = {
|
||||||
|
avif = true,
|
||||||
|
hei = true,
|
||||||
|
heic = true,
|
||||||
|
heif = true,
|
||||||
|
["heif-sequence"] = true,
|
||||||
|
["heic-sequence"] = true,
|
||||||
|
jxl = true,
|
||||||
|
tiff = true,
|
||||||
|
xml = true,
|
||||||
|
-- ["svg+xml"] = true,
|
||||||
|
["canon-cr2"] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
M.seekable_mimes = {
|
||||||
|
-- NOTE: Adobe illustrator photoshop mimetypes
|
||||||
|
["application/postscript"] = true,
|
||||||
|
["application/dvb.ait"] = true,
|
||||||
|
["application/illustrator"] = true,
|
||||||
|
["application/vnd.adobe.illustrator"] = true,
|
||||||
|
["image/x-eps"] = true,
|
||||||
|
["application/eps"] = true,
|
||||||
|
["application/pdf"] = true,
|
||||||
|
|
||||||
|
["image/adobe.photoshop"] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
M.suffix = "_mediainfo"
|
||||||
|
M.SHELL = os.getenv("SHELL") or ""
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
--- @since 26.1.22
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
local const = require(".const")
|
||||||
|
local utils = require(".utils")
|
||||||
|
|
||||||
|
function M:peek(job)
|
||||||
|
local preload_status, preload_err = self:preload(job)
|
||||||
|
-- Stop if preload failed
|
||||||
|
if not preload_status then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local cache_img_url = ya.file_cache({
|
||||||
|
skip = 0,
|
||||||
|
args = job.args,
|
||||||
|
file = job.file,
|
||||||
|
area = job.area,
|
||||||
|
})
|
||||||
|
|
||||||
|
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||||
|
|
||||||
|
local no_metadata = job.args.no_metadata
|
||||||
|
local mediainfo_height = 0
|
||||||
|
local lines = {}
|
||||||
|
local limit = job.area.h
|
||||||
|
local last_line = 0
|
||||||
|
local EOF_mediainfo = true
|
||||||
|
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||||
|
|
||||||
|
if not no_metadata then
|
||||||
|
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||||
|
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||||
|
if output then
|
||||||
|
local max_width = math.max(1, job.area.w)
|
||||||
|
if output:match("^Error:") then
|
||||||
|
job.args.force_reload_mediainfo = true
|
||||||
|
preload_status, preload_err = self:preload(job)
|
||||||
|
if not preload_status or preload_err then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
output = output:gsub("\n+$", "")
|
||||||
|
|
||||||
|
local iter = output:gmatch("[^\n]*")
|
||||||
|
local str = iter()
|
||||||
|
|
||||||
|
while str ~= nil do
|
||||||
|
local next_str = iter()
|
||||||
|
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||||
|
local line
|
||||||
|
if label then
|
||||||
|
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||||
|
line = ui.Line({
|
||||||
|
ui.Span(label .. ": "):style(ui.Style():fg("reset"):bold()),
|
||||||
|
ui.Span(value):style(th.spot.tbl_col or ui.Style():fg("blue")),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||||
|
line = ui.Line({ ui.Span(str):style(th.spot.title or ui.Style():fg("green")) })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if line then
|
||||||
|
local line_height = ui.height
|
||||||
|
and ui.height(str, { width = max_width, ansi = true, wrap = rt.preview.wrap })
|
||||||
|
or (math.max(1, is_wrap and math.ceil(ui.width(line) / max_width) or 1))
|
||||||
|
if next_str == nil and line_height == 1 then
|
||||||
|
EOF_mediainfo = true
|
||||||
|
end
|
||||||
|
if (last_line + line_height) > job.skip then
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
if (last_line + line_height) >= job.skip + limit then
|
||||||
|
last_line = job.skip + limit
|
||||||
|
EOF_mediainfo = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
last_line = last_line + line_height
|
||||||
|
end
|
||||||
|
str = next_str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mediainfo_height = math.min(limit, last_line)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not no_metadata and EOF_mediainfo and #lines == 0 and job.skip > 0 then
|
||||||
|
ya.emit("peek", {
|
||||||
|
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||||
|
only_if = job.file.url,
|
||||||
|
upper_bound = true,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
utils.force_render()
|
||||||
|
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||||
|
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||||
|
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||||
|
if
|
||||||
|
old_metadata_area.win_x == job.area.x
|
||||||
|
and old_metadata_area.win_y == job.area.y
|
||||||
|
and old_metadata_area.win_w == job.area.w
|
||||||
|
and old_metadata_area.win_h == job.area.h
|
||||||
|
then
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Clear(ui.Rect({
|
||||||
|
x = old_metadata_area.x,
|
||||||
|
y = old_metadata_area.y,
|
||||||
|
w = old_metadata_area.w,
|
||||||
|
h = old_metadata_area.h,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local rendered_img_rect = cache_img_url
|
||||||
|
and fs.cha(cache_img_url)
|
||||||
|
and ya.image_show(
|
||||||
|
cache_img_url,
|
||||||
|
ui.Rect({
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y,
|
||||||
|
w = job.area.w,
|
||||||
|
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
or nil
|
||||||
|
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||||
|
|
||||||
|
-- Handle image preload error
|
||||||
|
if preload_err then
|
||||||
|
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||||
|
end
|
||||||
|
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Text(lines)
|
||||||
|
:area(ui.Rect({
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y + image_height,
|
||||||
|
w = job.area.w,
|
||||||
|
h = job.area.h - image_height,
|
||||||
|
}))
|
||||||
|
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||||
|
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y + image_height,
|
||||||
|
w = job.area.w,
|
||||||
|
h = job.area.h - image_height,
|
||||||
|
win_x = job.area.x,
|
||||||
|
win_y = job.area.y,
|
||||||
|
win_w = job.area.w,
|
||||||
|
win_h = job.area.h,
|
||||||
|
} or nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:preload(job)
|
||||||
|
local cmd = "mediainfo"
|
||||||
|
local err_msg = ""
|
||||||
|
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||||
|
|
||||||
|
-- NOTE: Preload image
|
||||||
|
|
||||||
|
local mime = job.mime:match(".*/(.*)$")
|
||||||
|
local is_svg = mime == "svg+xml"
|
||||||
|
local is_magick = const.magick_image_mimes[mime]
|
||||||
|
local no_skip_job = { skip = 0, file = job.file, args = job.args, area = job.area }
|
||||||
|
local cache_img_url = ya.file_cache(no_skip_job)
|
||||||
|
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||||
|
|
||||||
|
-- NOTE: Only generate preview image when cache image is not exist
|
||||||
|
if not cache_img_url_cha or cache_img_url_cha.len <= 0 then
|
||||||
|
local cache_img_status, image_preload_err
|
||||||
|
if not is_valid_utf8_path then
|
||||||
|
-- NOTE: Case not valid utf8 path, use trick to generate preview image
|
||||||
|
if is_svg then
|
||||||
|
local cache_img_url_tmp = Url(cache_img_url .. ".tmp")
|
||||||
|
if fs.cha(cache_img_url_tmp) then
|
||||||
|
fs.remove("file", cache_img_url_tmp)
|
||||||
|
end
|
||||||
|
local tmp_file_path, _ = type(fs.unique) == "function" and fs.unique("file", cache_img_url_tmp)
|
||||||
|
or fs.unique_name(cache_img_url_tmp)
|
||||||
|
-- svg under invalid utf8 path
|
||||||
|
cache_img_status, image_preload_err = require("magick")
|
||||||
|
.with_limit()
|
||||||
|
:arg({
|
||||||
|
"-background",
|
||||||
|
"none",
|
||||||
|
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||||
|
"-auto-orient",
|
||||||
|
"-strip",
|
||||||
|
string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height),
|
||||||
|
"-quality",
|
||||||
|
rt.preview.image_quality,
|
||||||
|
string.format("PNG32:%s", tostring(tmp_file_path)),
|
||||||
|
})
|
||||||
|
:status()
|
||||||
|
if cache_img_status then
|
||||||
|
os.rename(tostring(tmp_file_path), tostring(cache_img_url))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- NOTE: Case valid utf8 path, use image, svg, or magick module
|
||||||
|
local image_module = is_svg and "svg" or (is_magick and "magick" or "image")
|
||||||
|
cache_img_status, image_preload_err = require(image_module):preload(no_skip_job)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not cache_img_status and image_preload_err then
|
||||||
|
ya.dbg("mediainfo", image_preload_err)
|
||||||
|
err_msg = err_msg .. (image_preload_err and (tostring(image_preload_err)) or "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NOTE: Get mediainfo and save to cache folder
|
||||||
|
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||||
|
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||||
|
-- Case peek function called preload to refetch mediainfo
|
||||||
|
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||||
|
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local output, err
|
||||||
|
if is_valid_utf8_path then
|
||||||
|
output, err = Command(cmd)
|
||||||
|
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||||
|
:output()
|
||||||
|
else
|
||||||
|
cmd = "cd "
|
||||||
|
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||||
|
.. " && "
|
||||||
|
.. cmd
|
||||||
|
.. " "
|
||||||
|
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||||
|
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||||
|
end
|
||||||
|
if err then
|
||||||
|
ya.dbg("mediainfo", tostring(err))
|
||||||
|
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
return fs.write(
|
||||||
|
cache_mediainfo_url,
|
||||||
|
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
--- @since 26.5.6
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
local const = require(".const")
|
||||||
|
local utils = require(".utils")
|
||||||
|
|
||||||
|
function M:peek(job)
|
||||||
|
local preload_status, preload_err = self:preload(job)
|
||||||
|
-- Stop if preload failed
|
||||||
|
if not preload_status then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local cache_img_url = ya.file_cache({
|
||||||
|
skip = 0,
|
||||||
|
args = job.args,
|
||||||
|
file = job.file,
|
||||||
|
area = job.area,
|
||||||
|
})
|
||||||
|
|
||||||
|
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||||
|
|
||||||
|
local no_metadata = job.args.no_metadata
|
||||||
|
local mediainfo_job_skip = job.skip
|
||||||
|
local mediainfo_height = 0
|
||||||
|
local lines = {}
|
||||||
|
local limit = job.area.h
|
||||||
|
local last_line = 0
|
||||||
|
local EOF_mediainfo = true
|
||||||
|
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||||
|
|
||||||
|
if not no_metadata then
|
||||||
|
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||||
|
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||||
|
if output then
|
||||||
|
local max_width = math.max(1, job.area.w)
|
||||||
|
if output:match("^Error:") then
|
||||||
|
job.args.force_reload_mediainfo = true
|
||||||
|
preload_status, preload_err = self:preload(job)
|
||||||
|
if not preload_status or preload_err then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
output = output:gsub("\n+$", "")
|
||||||
|
|
||||||
|
local iter = output:gmatch("[^\n]*")
|
||||||
|
local str = iter()
|
||||||
|
local opt = { ansi = true, tab_size = rt.preview.tab_size, wrap = rt.preview.wrap, width = max_width }
|
||||||
|
while str ~= nil do
|
||||||
|
local next_str = iter()
|
||||||
|
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||||
|
|
||||||
|
local line
|
||||||
|
if label then
|
||||||
|
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||||
|
line = label .. ": " .. value
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||||
|
line = str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if line then
|
||||||
|
local wrapped = ui.lines(line, opt)
|
||||||
|
local line_height = #wrapped
|
||||||
|
local from = 1
|
||||||
|
local to = math.min(line_height, mediainfo_job_skip + limit - last_line)
|
||||||
|
|
||||||
|
local total_rendered_text_len = 1
|
||||||
|
local total_label_rendered_len = 1
|
||||||
|
local label_total_len = label and utf8.len(label .. ": ") or 0
|
||||||
|
for j = from, to do
|
||||||
|
local current_line_components = {}
|
||||||
|
local wrapped_line_len = wrapped[j]:width() or 0
|
||||||
|
local wrapped_raw =
|
||||||
|
utils.utf8_sub(line, total_rendered_text_len, total_rendered_text_len + wrapped_line_len)
|
||||||
|
wrapped_line_len = utf8.len(wrapped_raw)
|
||||||
|
total_rendered_text_len = total_rendered_text_len + wrapped_line_len
|
||||||
|
|
||||||
|
if last_line + 1 > mediainfo_job_skip then
|
||||||
|
local label_raw = label_total_len - total_label_rendered_len <= 0 and ""
|
||||||
|
or utils.utf8_sub(wrapped_raw, 1, label_total_len - total_label_rendered_len)
|
||||||
|
local label_raw_len = utf8.len(label_raw)
|
||||||
|
if label_raw_len > 0 then
|
||||||
|
table.insert(
|
||||||
|
current_line_components,
|
||||||
|
ui.Span(label_raw):style(ui.Style():fg("reset"):bold())
|
||||||
|
)
|
||||||
|
total_label_rendered_len = total_label_rendered_len + label_raw_len
|
||||||
|
wrapped_raw = wrapped_raw:gsub("^" .. utils.is_literal_string(label_raw), "", 1)
|
||||||
|
end
|
||||||
|
if total_label_rendered_len >= label_total_len then
|
||||||
|
local value_raw = wrapped_raw
|
||||||
|
table.insert(
|
||||||
|
current_line_components,
|
||||||
|
ui.Span(value_raw or ""):style(
|
||||||
|
label and (th.spot.tbl_col or ui.Style():fg("blue"))
|
||||||
|
or (th.spot.title or ui.Style():fg("green"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
table.insert(lines, ui.Line(current_line_components))
|
||||||
|
-- last_line = last_line + 1
|
||||||
|
else
|
||||||
|
total_label_rendered_len = total_label_rendered_len + total_rendered_text_len
|
||||||
|
end
|
||||||
|
last_line = last_line + 1
|
||||||
|
if next_str == nil and not wrapped[j + 1] then
|
||||||
|
EOF_mediainfo = true
|
||||||
|
end
|
||||||
|
if last_line >= mediainfo_job_skip + limit then
|
||||||
|
last_line = mediainfo_job_skip + limit
|
||||||
|
EOF_mediainfo = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
str = next_str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mediainfo_height = math.min(limit, last_line)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not no_metadata and EOF_mediainfo and #lines == 0 and job.skip > 0 then
|
||||||
|
ya.emit("peek", {
|
||||||
|
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||||
|
only_if = job.file.url,
|
||||||
|
upper_bound = true,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
utils.force_render()
|
||||||
|
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||||
|
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||||
|
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||||
|
if
|
||||||
|
old_metadata_area.win_x == job.area.x
|
||||||
|
and old_metadata_area.win_y == job.area.y
|
||||||
|
and old_metadata_area.win_w == job.area.w
|
||||||
|
and old_metadata_area.win_h == job.area.h
|
||||||
|
then
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Clear(ui.Rect({
|
||||||
|
x = old_metadata_area.x,
|
||||||
|
y = old_metadata_area.y,
|
||||||
|
w = old_metadata_area.w,
|
||||||
|
h = old_metadata_area.h,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local rendered_img_rect = cache_img_url
|
||||||
|
and fs.cha(cache_img_url)
|
||||||
|
and ya.image_show(
|
||||||
|
cache_img_url,
|
||||||
|
ui.Rect({
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y,
|
||||||
|
w = job.area.w,
|
||||||
|
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
or nil
|
||||||
|
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||||
|
|
||||||
|
-- Handle image preload error
|
||||||
|
if preload_err then
|
||||||
|
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||||
|
end
|
||||||
|
|
||||||
|
ya.preview_widget(job, {
|
||||||
|
ui.Text(lines)
|
||||||
|
:area(ui.Rect({
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y + image_height,
|
||||||
|
w = job.area.w,
|
||||||
|
h = job.area.h - image_height,
|
||||||
|
}))
|
||||||
|
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||||
|
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||||
|
x = job.area.x,
|
||||||
|
y = job.area.y + image_height,
|
||||||
|
w = job.area.w,
|
||||||
|
h = job.area.h - image_height,
|
||||||
|
win_x = job.area.x,
|
||||||
|
win_y = job.area.y,
|
||||||
|
win_w = job.area.w,
|
||||||
|
win_h = job.area.h,
|
||||||
|
} or nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:preload(job)
|
||||||
|
local cmd = "mediainfo"
|
||||||
|
local err_msg = ""
|
||||||
|
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||||
|
|
||||||
|
-- NOTE: Preload image
|
||||||
|
|
||||||
|
local mime = job.mime:match(".*/(.*)$")
|
||||||
|
local is_svg = mime == "svg+xml"
|
||||||
|
local is_magick = const.magick_image_mimes[mime]
|
||||||
|
local no_skip_job = { skip = 0, file = job.file, args = job.args, area = job.area }
|
||||||
|
local cache_img_url = ya.file_cache(no_skip_job)
|
||||||
|
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||||
|
|
||||||
|
-- NOTE: Only generate preview image when cache image is not exist
|
||||||
|
if not cache_img_url_cha or cache_img_url_cha.len <= 0 then
|
||||||
|
local cache_img_status, image_preload_err
|
||||||
|
if not is_valid_utf8_path then
|
||||||
|
-- NOTE: Case not valid utf8 path, use trick to generate preview image
|
||||||
|
if is_svg then
|
||||||
|
local cache_img_url_tmp = Url(cache_img_url .. ".tmp")
|
||||||
|
if fs.cha(cache_img_url_tmp) then
|
||||||
|
fs.remove("file", cache_img_url_tmp)
|
||||||
|
end
|
||||||
|
local tmp_file_path, _ = type(fs.unique) == "function" and fs.unique("file", cache_img_url_tmp)
|
||||||
|
or fs.unique_name(cache_img_url_tmp)
|
||||||
|
-- svg under invalid utf8 path
|
||||||
|
cache_img_status, image_preload_err = require("magick")
|
||||||
|
.with_limit()
|
||||||
|
:arg({
|
||||||
|
"-background",
|
||||||
|
"none",
|
||||||
|
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||||
|
"-auto-orient",
|
||||||
|
"-strip",
|
||||||
|
string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height),
|
||||||
|
"-quality",
|
||||||
|
rt.preview.image_quality,
|
||||||
|
string.format("PNG32:%s", tostring(tmp_file_path)),
|
||||||
|
})
|
||||||
|
:status()
|
||||||
|
if cache_img_status then
|
||||||
|
os.rename(tostring(tmp_file_path), tostring(cache_img_url))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- NOTE: Case valid utf8 path, use image, svg, or magick module
|
||||||
|
local image_module = is_svg and "svg" or (is_magick and "magick" or "image")
|
||||||
|
cache_img_status, image_preload_err = require(image_module):preload(no_skip_job)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not cache_img_status and image_preload_err then
|
||||||
|
ya.dbg("mediainfo", image_preload_err)
|
||||||
|
err_msg = err_msg .. (image_preload_err and (tostring(image_preload_err)) or "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NOTE: Get mediainfo and save to cache folder
|
||||||
|
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||||
|
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||||
|
-- Case peek function called preload to refetch mediainfo
|
||||||
|
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||||
|
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local output, err
|
||||||
|
if is_valid_utf8_path then
|
||||||
|
output, err = Command(cmd)
|
||||||
|
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||||
|
:output()
|
||||||
|
else
|
||||||
|
cmd = "cd "
|
||||||
|
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||||
|
.. " && "
|
||||||
|
.. cmd
|
||||||
|
.. " "
|
||||||
|
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||||
|
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||||
|
end
|
||||||
|
if err then
|
||||||
|
ya.dbg("mediainfo", tostring(err))
|
||||||
|
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
return fs.write(
|
||||||
|
cache_mediainfo_url,
|
||||||
|
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,497 +1,233 @@
|
|||||||
--- @since 25.5.31
|
--- @since 26.1.22
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
local suffix = "_mediainfo"
|
local using_new_height_api = ui.lines ~= nil
|
||||||
local SHELL = os.getenv("SHELL") or ""
|
local const
|
||||||
|
local utils
|
||||||
|
local adobe
|
||||||
|
local audio
|
||||||
|
local image
|
||||||
|
local video
|
||||||
|
local none_media_preview
|
||||||
|
|
||||||
local function is_valid_utf8(str)
|
const = require(".const")
|
||||||
return utf8.len(str) ~= nil
|
utils = require(".utils")
|
||||||
end
|
|
||||||
|
|
||||||
local function path_quote(path)
|
if using_new_height_api then
|
||||||
if not path or tostring(path) == "" then
|
ya.dbg("mediainfo", "Using yazi version >= 26.5.6")
|
||||||
return path
|
adobe = require(".adobe-stable")
|
||||||
end
|
audio = require(".audio-stable")
|
||||||
local result = "'" .. string.gsub(tostring(path), "'", "'\\''") .. "'"
|
image = require(".image-stable")
|
||||||
return result
|
video = require(".video-stable")
|
||||||
end
|
none_media_preview = require(".none-media-preview-stable")
|
||||||
|
else
|
||||||
local function read_mediainfo_cached_file(file_path)
|
ya.dbg("mediainfo", "Using yazi version <= 26.1.22")
|
||||||
-- Open the file in read mode
|
adobe = require(".adobe-old")
|
||||||
local file = io.open(file_path, "r")
|
audio = require(".audio-old")
|
||||||
|
image = require(".image-old")
|
||||||
if file then
|
video = require(".video-old")
|
||||||
-- Read the entire file content
|
none_media_preview = require(".none-media-preview-old")
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M:peek(job)
|
function M:peek(job)
|
||||||
|
-- debounce peek
|
||||||
local start = os.clock()
|
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
|
if not job.mime then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
set_state(STATE_KEY.prev_peek_data, {
|
|
||||||
file = tostring(job.file.path or job.file.cache or job.file.url),
|
local no_metadata_user_cfg = utils.get_state(const.STATE_KEY.no_metadata)
|
||||||
mime = job.mime,
|
if no_metadata_user_cfg ~= nil then
|
||||||
area = {
|
job.args.no_metadata = no_metadata_user_cfg
|
||||||
x = job.area.x,
|
end
|
||||||
y = job.area.y,
|
|
||||||
w = job.area.w,
|
local no_preview_user_cfg = utils.get_state(const.STATE_KEY.no_preview)
|
||||||
h = job.area.h,
|
if no_preview_user_cfg ~= nil then
|
||||||
},
|
job.args.no_preview = no_preview_user_cfg
|
||||||
args = job.args,
|
end
|
||||||
skip = job.skip,
|
|
||||||
})
|
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_video = string.find(job.mime, "^video/")
|
||||||
local is_audio = string.find(job.mime, "^audio/")
|
local is_audio = string.find(job.mime, "^audio/")
|
||||||
local is_image = string.find(job.mime, "^image/")
|
local is_image = string.find(job.mime, "^image/")
|
||||||
local is_seekable = seekable_mimes[job.mime] or is_video
|
local is_adobe = const.seekable_mimes[job.mime]
|
||||||
local cache_img_url = (is_audio or is_image) and cache_img_url_no_skip
|
|
||||||
|
|
||||||
if is_seekable then
|
if is_adobe then
|
||||||
cache_img_url = ya.file_cache(job)
|
return adobe:peek(job)
|
||||||
end
|
elseif is_image then
|
||||||
local preload_status, preload_err = self:preload(job)
|
return image:peek(job)
|
||||||
if not preload_status then
|
elseif is_video then
|
||||||
return
|
return video:peek(job)
|
||||||
end
|
elseif is_audio then
|
||||||
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))
|
return audio:peek(job)
|
||||||
local hide_metadata = get_state(STATE_KEY.hide_metadata)
|
else
|
||||||
local mediainfo_height = 0
|
return none_media_preview:peek(job)
|
||||||
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,
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M:seek(job)
|
function M:seek(job)
|
||||||
local h = cx.active.current.hovered
|
local h = cx.active.current.hovered
|
||||||
if h and h.url == job.file.url then
|
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", {
|
ya.emit("peek", {
|
||||||
math.max(0, cx.active.preview.skip + job.units),
|
math.max(0, cx.active.preview.skip + job.units),
|
||||||
only_if = job.file.url,
|
only_if = job.file.url,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
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)
|
function M:preload(job)
|
||||||
local cache_img_url = ya.file_cache({ file = job.file, skip = 0 })
|
local cache_img_url = ya.file_cache({ file = job.file, skip = 0 })
|
||||||
if not cache_img_url then
|
if not cache_img_url then
|
||||||
|
ya.dbg("mediainfo", "Can't access yazi cache folder")
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
local cache_mediainfo_url = Url(tostring(cache_img_url) .. suffix)
|
if not job.mime then
|
||||||
cache_img_url = seekable_mimes[job.mime] and ya.file_cache(job) or cache_img_url
|
return false
|
||||||
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
|
|
||||||
end
|
end
|
||||||
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
|
||||||
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
local no_metadata_user_cfg = utils.get_state(const.STATE_KEY.no_metadata)
|
||||||
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
if no_metadata_user_cfg ~= nil then
|
||||||
|
job.args.no_metadata = no_metadata_user_cfg
|
||||||
end
|
end
|
||||||
local cmd = "mediainfo"
|
|
||||||
local output, err
|
local no_preview_user_cfg = utils.get_state(const.STATE_KEY.no_preview)
|
||||||
if is_valid_utf8_path then
|
if no_preview_user_cfg ~= nil then
|
||||||
output, err = Command(cmd)
|
job.args.no_preview = no_preview_user_cfg
|
||||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
end
|
||||||
:output()
|
|
||||||
|
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
|
else
|
||||||
cmd = "cd "
|
return none_media_preview:preload(job)
|
||||||
.. 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()
|
|
||||||
end
|
end
|
||||||
if err then
|
end
|
||||||
err_msg = err_msg .. string.format("Failed to start `%s`, Do you have `%s` installed?\n", cmd, cmd)
|
|
||||||
|
function M:setup(opts)
|
||||||
|
if not opts or type(opts) ~= "table" then
|
||||||
|
return
|
||||||
end
|
end
|
||||||
return fs.write(
|
|
||||||
cache_mediainfo_url,
|
if type(opts.skip_labels) == "table" then
|
||||||
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
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
|
end
|
||||||
|
|
||||||
function M:entry(job)
|
function M:entry(job)
|
||||||
local action = job.args[1]
|
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
|
if action == const.ENTRY_ACTION.show_metadata or job.args.show_metadata then
|
||||||
set_state(STATE_KEY.hide_metadata, not get_state(STATE_KEY.hide_metadata))
|
utils.set_state(const.STATE_KEY.no_metadata, false)
|
||||||
M:re_peek()
|
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
|
||||||
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
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
--- @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 = 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()
|
||||||
|
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 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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# mime-ext.yazi
|
# mime-ext.yazi
|
||||||
|
|
||||||
A mime-type provider based on a file extension database, replacing the [builtin `file(1)`](https://github.com/sxyazi/yazi/blob/main/yazi-plugin/preset/plugins/mime.lua) to speed up mime-type retrieval at the expense of accuracy.
|
A MIME type provider based on a file extension database, replacing the [builtin `file(1)`](https://github.com/sxyazi/yazi/blob/main/yazi-plugin/preset/plugins/mime-local.lua) to speed up MIME type retrieval at the expense of accuracy.
|
||||||
|
|
||||||
See https://yazi-rs.github.io/docs/tips#make-yazi-even-faster for more information.
|
See https://yazi-rs.github.io/docs/tips#make-yazi-even-faster for more information.
|
||||||
|
|
||||||
@@ -16,10 +16,16 @@ Add this to your `~/.config/yazi/yazi.toml`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[plugin.prepend_fetchers]]
|
[[plugin.prepend_fetchers]]
|
||||||
id = "mime"
|
url = "local://*"
|
||||||
url = "*"
|
run = "mime-ext.local"
|
||||||
run = "mime-ext"
|
prio = "high"
|
||||||
prio = "high"
|
group = "mime"
|
||||||
|
|
||||||
|
[[plugin.prepend_fetchers]]
|
||||||
|
url = "remote://*"
|
||||||
|
run = "mime-ext.remote"
|
||||||
|
prio = "high"
|
||||||
|
group = "mime"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Advanced
|
## Advanced
|
||||||
@@ -27,21 +33,25 @@ prio = "high"
|
|||||||
You can also customize it in your `~/.config/yazi/init.lua` with:
|
You can also customize it in your `~/.config/yazi/init.lua` with:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
require("mime-ext"):setup {
|
require("mime-ext.local"):setup {
|
||||||
-- Expand the existing filename database (lowercase), for example:
|
-- Expand the default filename database (lowercase), for example:
|
||||||
with_files = {
|
with_files = {
|
||||||
makefile = "text/makefile",
|
makefile = "text/makefile",
|
||||||
-- ...
|
-- ...
|
||||||
},
|
},
|
||||||
|
|
||||||
-- Expand the existing extension database (lowercase), for example:
|
-- Expand the default extension database (lowercase), for example:
|
||||||
with_exts = {
|
with_exts = {
|
||||||
mk = "text/makefile",
|
mk = "text/makefile",
|
||||||
-- ...
|
-- ...
|
||||||
},
|
},
|
||||||
|
|
||||||
-- If the mime-type is not in both filename and extension databases,
|
-- Empty the default filename and extension databases,
|
||||||
-- then fallback to Yazi's preset `mime` plugin, which uses `file(1)`
|
-- 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,
|
fallback_file1 = false,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -49,7 +59,7 @@ require("mime-ext"):setup {
|
|||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- Add more file types (PRs welcome!).
|
- Add more file types (PRs welcome!).
|
||||||
- Compress mime-type tables.
|
- Compress MIME type tables.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,53 @@
|
|||||||
|
--- @since 26.8.15
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local function stale_cache(file)
|
||||||
|
local url = file.url
|
||||||
|
local stamp = url.spec.stamp:join(url:hash(true))
|
||||||
|
|
||||||
|
local fd = fs.access():read(true):open(Url(stamp))
|
||||||
|
if not fd then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local sig = fd:read(26)
|
||||||
|
ya.drop(fd)
|
||||||
|
return sig ~= file.cha:hash(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:fetch(job)
|
||||||
|
return ya.co(function()
|
||||||
|
local updates, unknown = {}, {}
|
||||||
|
for _, file in ipairs(job.files) do
|
||||||
|
if file.cha.is_dummy then
|
||||||
|
coroutine.yield(file, {})
|
||||||
|
elseif not file.cache then
|
||||||
|
unknown[#unknown + 1] = file
|
||||||
|
elseif not fs.cha(Url(file.cache)) then
|
||||||
|
updates[file.url] = coroutine.yield(file, { "vfs/absent" }) and "vfs/absent" or nil
|
||||||
|
elseif stale_cache(file) then
|
||||||
|
updates[file.url] = coroutine.yield(file, { "vfs/stale" }) and "vfs/stale" or nil
|
||||||
|
else
|
||||||
|
unknown[#unknown + 1] = file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if next(updates) then
|
||||||
|
ya.emit("update_mimes", { updates = updates })
|
||||||
|
end
|
||||||
|
|
||||||
|
if #unknown > 0 then
|
||||||
|
self.fallback_local(job, unknown)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.fallback_local(job, unknown)
|
||||||
|
local next = require(".local"):fetch(ya.dict_merge(job, { files = unknown }))
|
||||||
|
local file, result = next()
|
||||||
|
while file do
|
||||||
|
file, result = next(coroutine.yield(file, result))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -2,6 +2,7 @@ local M = {}
|
|||||||
|
|
||||||
function M:peek(job)
|
function M:peek(job)
|
||||||
local child = Command("rich")
|
local child = Command("rich")
|
||||||
|
:env("COLUMNS", tostring(job.area.w))
|
||||||
:arg({
|
:arg({
|
||||||
"-j",
|
"-j",
|
||||||
"--left",
|
"--left",
|
||||||
@@ -11,7 +12,7 @@ function M:peek(job)
|
|||||||
"--guides",
|
"--guides",
|
||||||
"--max-width",
|
"--max-width",
|
||||||
tostring(job.area.w),
|
tostring(job.area.w),
|
||||||
tostring(job.file.url),
|
tostring(job.file.url.path),
|
||||||
})
|
})
|
||||||
:stdout(Command.PIPED)
|
:stdout(Command.PIPED)
|
||||||
:stderr(Command.PIPED)
|
:stderr(Command.PIPED)
|
||||||
@@ -22,22 +23,27 @@ function M:peek(job)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local limit = job.area.h
|
local limit = job.area.h
|
||||||
local i, lines = 0, ""
|
local i, lines, errs = 0, "", 0
|
||||||
repeat
|
repeat
|
||||||
local next, event = child:read_line()
|
local next, event = child:read_line()
|
||||||
if event == 1 then
|
if event == 1 then
|
||||||
return require("code"):peek(job)
|
errs = errs + 1
|
||||||
elseif event ~= 0 then
|
elseif event ~= 0 then
|
||||||
break
|
break
|
||||||
end
|
else
|
||||||
|
i = i + 1
|
||||||
i = i + 1
|
if i > job.skip then
|
||||||
if i > job.skip then
|
lines = lines .. next
|
||||||
lines = lines .. next
|
end
|
||||||
end
|
end
|
||||||
until i >= job.skip + limit
|
until i >= job.skip + limit
|
||||||
|
|
||||||
child:start_kill()
|
child:start_kill()
|
||||||
|
|
||||||
|
if i == 0 and errs > 0 then
|
||||||
|
return require("code"):peek(job)
|
||||||
|
end
|
||||||
|
|
||||||
if job.skip > 0 and i < job.skip + limit then
|
if job.skip > 0 and i < job.skip + limit then
|
||||||
ya.emit("peek", { math.max(0, i - limit), only_if = job.file.url, upper_bound = true })
|
ya.emit("peek", { math.max(0, i - limit), only_if = job.file.url, upper_bound = true })
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ desc = "Enter the child directory, or open the file"
|
|||||||
|
|
||||||
## Advanced
|
## Advanced
|
||||||
|
|
||||||
By default, `--hovered` is passed to the [`open`][open] command, make the behavior consistent with [`enter`][enter] avoiding accidental triggers,
|
By default, `--hovered` is passed to the [`open`][open] action, make the behavior consistent with [`enter`][enter] avoiding accidental triggers,
|
||||||
which means both will only target the currently hovered file.
|
which means both will only target the currently hovered file.
|
||||||
|
|
||||||
If you still want `open` to target multiple selected files, add this to your `~/.config/yazi/init.lua`:
|
If you still want `open` to target multiple selected files, add this to your `~/.config/yazi/init.lua`:
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
|
||||||
"diagnostics.globals": ["ya", "ui", "cx", "ps"]
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 Hunter Hwang
|
Copyright (c) 2026 WhoSowSee
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,458 +0,0 @@
|
|||||||
<h1 align="center">🌀 whoosh.yazi</h1>
|
|
||||||
<p align="center">
|
|
||||||
<b>Молниеносный менеджер закладок для <a href="https://github.com/sxyazi/yazi">Yazi</a></b><br>
|
|
||||||
<i>Сохраняйте, ищите и мгновенно переходите к любимым путям</i>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
---
|
|
||||||
> [!TIP]
|
|
||||||
> **Английская версия:** [README.md](README.md)
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Плагин для [Yazi](https://github.com/sxyazi/yazi) для управления закладками, поддерживающий следующие функции:
|
|
||||||
>
|
|
||||||
> - **Постоянные закладки** - Закладки не теряются после закрытия yazi
|
|
||||||
> - **Временные закладки** - Закладки только для текущей сессии, не сохраняются между перезапусками
|
|
||||||
> - **Быстрая навигация** - Переход, удаление и переименование закладок по горячим клавишам
|
|
||||||
> - **Нечеткий поиск** - Поддержка нечеткого поиска через [fzf](https://github.com/junegunn/fzf) с контекстными промптами
|
|
||||||
> - **Множественное удаление закладок** - Выбор нескольких закладок с помощью TAB в fzf
|
|
||||||
> - **Конфигурационные закладки** - Предварительная настройка закладок с помощью языка Lua
|
|
||||||
> - **Умное сокращение путей** - Настраиваемое сокращение путей для лучшей читаемости
|
|
||||||
> - **История директорий по вкладкам** - Независимая история для каждой вкладки с возвратом через Backspace
|
|
||||||
> - **Навигация по истории Tab** - Просмотр и переход к недавно посещенным директориям через fzf
|
|
||||||
> - **Быстрое создание закладок** - Создание временных закладок прямо из меню навигации
|
|
||||||
> - **Настраиваемые клавиши меню** - Переопределяйте привязки Tab/Backspace/Enter/Space через `init.lua`
|
|
||||||
|
|
||||||
<div style="text-align: center;">
|
|
||||||
<img src="image/plugin.png" alt="Превью плагина" width="1100px">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Установка
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> Требуется Yazi v26.1.4+
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Ручная установка
|
|
||||||
|
|
||||||
# Linux/macOS
|
|
||||||
git clone https://gitlab.com/WhoSowSee/whoosh.yazi.git ~/.config/yazi/plugins/whoosh.yazi
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
git clone https://gitlab.com/WhoSowSee/whoosh.yazi.git $env:APPDATA\yazi\config\plugins\whoosh.yazi
|
|
||||||
```
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
Добавьте это в ваш `init.lua`:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
-- Вы можете настроить закладки, используя упрощенный синтаксис
|
|
||||||
local bookmarks = {
|
|
||||||
{ tag = "Рабочий стол", path = "~/Desktop", key = "d" },
|
|
||||||
{ tag = "Документы", path = "~/Documents", key = "D" },
|
|
||||||
{ tag = "Загрузки", path = "~/Downloads", key = "o" },
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Вы также можете настроить закладки с массивом ключей
|
|
||||||
local bookmarks = {
|
|
||||||
{ tag = "Рабочий стол", path = "~/Desktop", key = { "d", "D" } },
|
|
||||||
{ tag = "Документы", path = "~/Documents", key = { "d", "d" } },
|
|
||||||
{ tag = "Загрузки", path = "~/Downloads", key = "o" },
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Или же используя другой, более сложный синтаксис
|
|
||||||
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 {
|
|
||||||
-- Конфигурационные закладки (нельзя удалить через плагин)
|
|
||||||
bookmarks = bookmarks,
|
|
||||||
|
|
||||||
-- Настройки уведомлений
|
|
||||||
jump_notify = false,
|
|
||||||
|
|
||||||
-- Генерация ключей для автоматического назначения клавиш закладок
|
|
||||||
keys = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
|
||||||
|
|
||||||
-- Настройка клавиш для встроенных действий меню
|
|
||||||
-- false - скрыть пункт меню
|
|
||||||
special_keys = {
|
|
||||||
create_temp = "<Enter>", -- Создание временной закладки
|
|
||||||
fuzzy_search = "<Space>", -- Нечеткий поиск (fzf)
|
|
||||||
history = "<Tab>", -- Открыть историю директорий
|
|
||||||
previous_dir = "<Backspace>", -- Вернуться в предыдущую директорию
|
|
||||||
},
|
|
||||||
|
|
||||||
-- Путь к файлу для хранения пользовательских закладок
|
|
||||||
bookmarks_path = (ya.target_family() == "windows" and os.getenv("APPDATA") .. "\\yazi\\config\\plugins\\whoosh.yazi\\bookmarks") or
|
|
||||||
(os.getenv("HOME") .. "/.config/yazi/plugins/whoosh.yazi/bookmarks"),
|
|
||||||
|
|
||||||
-- Подмена домашней директории на "~"
|
|
||||||
home_alias_enabled = true, -- Управляет отображением домашнего каталога
|
|
||||||
|
|
||||||
-- Сокращение путей в меню навигации
|
|
||||||
path_truncate_enabled = false, -- Включить/выключить сокращение путей
|
|
||||||
path_max_depth = 3, -- Максимальная глубина пути перед сокращением
|
|
||||||
|
|
||||||
-- Сокращение путей в нечетком поиске (fzf)
|
|
||||||
fzf_path_truncate_enabled = false, -- Включить/выключить сокращение путей в fzf
|
|
||||||
fzf_path_max_depth = 5, -- Максимальная глубина пути перед сокращением в fzf
|
|
||||||
|
|
||||||
-- Сокращение длинных названий папок
|
|
||||||
path_truncate_long_names_enabled = false, -- Включить в меню навигации
|
|
||||||
fzf_path_truncate_long_names_enabled = false, -- Включить в fzf
|
|
||||||
path_max_folder_name_length = 20, -- Максимальная длина в меню навигации
|
|
||||||
fzf_path_max_folder_name_length = 20, -- Максимальная длина в fzf
|
|
||||||
|
|
||||||
-- Настройки истории директорий
|
|
||||||
history_size = 10, -- Количество директорий в истории (по умолчанию 10)
|
|
||||||
history_fzf_path_truncate_enabled = false, -- Включить сокращение путей по глубине для истории
|
|
||||||
history_fzf_path_max_depth = 5, -- Максимальная глубина путей для истории (по умолчанию 5)
|
|
||||||
history_fzf_path_truncate_long_names_enabled = false, -- Включить сокращение длинных названий для истории
|
|
||||||
history_fzf_path_max_folder_name_length = 30, -- Максимальная длина названий папок для истории (по умолчанию 30)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Добавьте это в ваш `keymap.toml`:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = "["
|
|
||||||
run = "plugin whoosh jump_by_key"
|
|
||||||
desc = "Перейти к закладке по клавише"
|
|
||||||
|
|
||||||
# Прямой доступ к нечеткому поиску закладок
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = "}"
|
|
||||||
run = "plugin whoosh jump_by_fzf"
|
|
||||||
desc = "Прямой вызов fuzzy search закладок"
|
|
||||||
|
|
||||||
# Основные операции с закладками
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = [ "]", "a" ]
|
|
||||||
run = "plugin whoosh save"
|
|
||||||
desc = "Добавить закладку (выделенный файл/директория)"
|
|
||||||
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = [ "]", "A" ]
|
|
||||||
run = "plugin whoosh save_cwd"
|
|
||||||
desc = "Добавить закладку (текущая директория)"
|
|
||||||
|
|
||||||
# Временные закладки
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = [ "]", "t" ]
|
|
||||||
run = "plugin whoosh save_temp"
|
|
||||||
desc = "Добавить временную закладку (выделенный файл/директория)"
|
|
||||||
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = [ "]", "T" ]
|
|
||||||
run = "plugin whoosh save_cwd_temp"
|
|
||||||
desc = "Добавить временную закладку (текущая директория)"
|
|
||||||
|
|
||||||
# Переход к закладкам
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = "<A-k>"
|
|
||||||
run = "plugin whoosh jump_key_k"
|
|
||||||
desc = "Мгновенный переход к закладке с клавишей k"
|
|
||||||
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = [ "]", "f" ]
|
|
||||||
run = "plugin whoosh jump_by_fzf"
|
|
||||||
desc = "Перейти к закладке через fzf"
|
|
||||||
|
|
||||||
# Удаление закладок
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = [ "]", "d" ]
|
|
||||||
run = "plugin whoosh delete_by_key"
|
|
||||||
desc = "Удалить закладку по клавише"
|
|
||||||
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = [ "]", "D" ]
|
|
||||||
run = "plugin whoosh delete_by_fzf"
|
|
||||||
desc = "Удалить закладки через fzf (используйте TAB для выбора нескольких)"
|
|
||||||
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = [ "]", "C" ]
|
|
||||||
run = "plugin whoosh delete_all"
|
|
||||||
desc = "Удалить все пользовательские закладки"
|
|
||||||
|
|
||||||
# Переименование закладок
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = [ "]", "r" ]
|
|
||||||
run = "plugin whoosh rename_by_key"
|
|
||||||
desc = "Переименовать закладку по клавише"
|
|
||||||
|
|
||||||
[[mgr.prepend_keymap]]
|
|
||||||
on = [ "]", "R" ]
|
|
||||||
run = "plugin whoosh rename_by_fzf"
|
|
||||||
desc = "Переименовать закладку через fzf"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Функции
|
|
||||||
|
|
||||||
### Временные закладки
|
|
||||||
|
|
||||||
Закладки только для текущей сессии, которые не сохраняются между перезапусками Yazi:
|
|
||||||
|
|
||||||
- Создание с помощью команд `save_temp` или `save_cwd_temp`
|
|
||||||
- Идентификация с префиксом [TEMP] в меню навигации и fzf
|
|
||||||
- Автоматическая очистка при перезапуске Yazi
|
|
||||||
- Можно удалить по отдельности или все сразу с помощью `delete_all_temp`
|
|
||||||
|
|
||||||
### История директорий
|
|
||||||
|
|
||||||
<div style="text-align: center;">
|
|
||||||
<img src="image/history.png" alt="Превью истории директорий" width="1100px">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
Плагин поддерживает умную систему истории директорий:
|
|
||||||
|
|
||||||
- **Независимая история для каждой вкладки** - Каждая вкладка имеет свою собственную историю
|
|
||||||
- **Автоматическое отслеживание** - История обновляется при переходах между директориями
|
|
||||||
- **Фильтрация текущей директории** - Текущая директория исключается из отображения истории
|
|
||||||
- **Настраиваемый размер** - Количество сохраняемых директорий настраивается (по умолчанию 10)
|
|
||||||
- **Отдельные настройки сокращения** - Независимые настройки отображения путей для истории
|
|
||||||
|
|
||||||
**Поведение системы:**
|
|
||||||
|
|
||||||
- При первом запуске yazi история пуста
|
|
||||||
- Предыдущие директории добавляются в историю только при переходе в новую директорию
|
|
||||||
- Новые элементы добавляются в начало списка (сортировка от новых к старым)
|
|
||||||
- При превышении лимита самые старые элементы удаляются
|
|
||||||
- Дубликаты автоматически удаляются и перемещаются наверх
|
|
||||||
|
|
||||||
### Функции меню навигации
|
|
||||||
|
|
||||||
При использовании `jump_by_key` вы получаете доступ к умному меню навигации с:
|
|
||||||
|
|
||||||
- **Создание временной закладки** - Нажмите `<Enter>` для быстрого создания закладки текущей директории
|
|
||||||
- **Нечеткий поиск** - Нажмите `<Space>` для открытия поиска fzf
|
|
||||||
- **История директорий** - Нажмите `<Tab>` для просмотра истории через fzf (только если есть история)
|
|
||||||
- **Предыдущая директория** - Нажмите `<Backspace>` для возврата к предыдущей директории (только если доступно)
|
|
||||||
- **Все закладки** - Как постоянные, так и временные закладки с четким визуальным различием
|
|
||||||
|
|
||||||
### Навигация по истории директорий
|
|
||||||
|
|
||||||
Плагин предоставляет два способа навигации по истории:
|
|
||||||
|
|
||||||
1. **Через меню навигации** - При использовании `jump_by_key` нажмите `<Tab>` для доступа к истории
|
|
||||||
2. **Прямой доступ** - Используйте настроенную специальную клавишу истории (по умолчанию `<Tab>`) для прямого доступа к fzf с историей
|
|
||||||
|
|
||||||
#### Примечание о клавише `<Tab>` в Neovim (yazi.nvim)
|
|
||||||
|
|
||||||
При запуске Whoosh внутри [mikavilpas/yazi.nvim](https://github.com/mikavilpas/yazi.nvim) стандартная привязка `<Tab>` (`cycle_open_buffers`) обрабатывается самим Neovim, поэтому Yazi не получает нажатие. Если нажатие `<Tab>` возвращает вас в буфер, из которого открывался Yazi, отключите или переназначьте эту клавишу в настройках yazi.nvim, чтобы корректно вызывать историю директорий:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
opts = {
|
|
||||||
keymaps = {
|
|
||||||
cycle_open_buffers = false,
|
|
||||||
},
|
|
||||||
-- OR
|
|
||||||
keymaps = {
|
|
||||||
cycle_open_buffers = "<S-Tab>",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
Полный пример конфигурации:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
return {
|
|
||||||
"mikavilpas/yazi.nvim",
|
|
||||||
version = "*",
|
|
||||||
event = "VeryLazy",
|
|
||||||
dependencies = { { "nvim-lua/plenary.nvim", lazy = true } },
|
|
||||||
keys = {
|
|
||||||
{ "<leader>-", mode = { "n", "v" }, "<cmd>Yazi<cr>", desc = "Open Yazi" },
|
|
||||||
{ "<leader>cw", "<cmd>Yazi cwd<cr>", desc = "Open Yazi at CWD" },
|
|
||||||
},
|
|
||||||
opts = {
|
|
||||||
open_for_directories = false,
|
|
||||||
keymaps = {
|
|
||||||
cycle_open_buffers = false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
init = function() vim.g.loaded_netrwPlugin = 1 end,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Если вы хотите сохранить привязку `<Tab>` за Neovim, но при этом иметь доступ к истории директорий, переназначьте горячую клавишу whoosh через `special_keys` в файле `init.lua`:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
require("whoosh"):setup {
|
|
||||||
special_keys = {
|
|
||||||
history = "<H>",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Типы закладок
|
|
||||||
|
|
||||||
Плагин поддерживает три типа закладок:
|
|
||||||
|
|
||||||
1. **Конфигурационные закладки** - Определены в `init.lua`, нельзя удалить через интерфейс плагина
|
|
||||||
2. **Пользовательские закладки** - Созданы во время использования, сохранены в файл, можно удалить
|
|
||||||
3. **Временные закладки** - Только для сессии, хранятся в памяти, очищаются при перезапуске
|
|
||||||
|
|
||||||
При конфликте путей пользовательские закладки переопределяют конфигурационные закладки в отображении
|
|
||||||
|
|
||||||
## Параметры конфигурации
|
|
||||||
|
|
||||||
Плагин поддерживает следующие параметры конфигурации в функции `setup()`:
|
|
||||||
|
|
||||||
| Параметр | Тип | По умолчанию | Описание |
|
|
||||||
| ---------------------------------------------- | ------- | ----------------------- | -------------------------------------------------------------------------- |
|
|
||||||
| `bookmarks` | table | `{}` | Предварительно настроенные закладки (нельзя удалить через плагин) |
|
|
||||||
| `jump_notify` | boolean | `false` | Показывать уведомление при переходе к закладке |
|
|
||||||
| `keys` | string | `"0123456789abcdef..."` | Символы, используемые для автогенерации ключей закладок |
|
|
||||||
| `special_keys` | table | `см. описание` | Настройка клавиш встроенного меню (Enter/Space/Tab/Backspace); можно задать `false` для отключения |
|
|
||||||
| `path` | string | Зависит от ОС | Путь к файлу, где хранятся пользовательские закладки |
|
|
||||||
| `home_alias_enabled` | boolean | `true` | Подменять домашнюю директорию на `~` в отображении пути |
|
|
||||||
| `path_truncate_enabled` | boolean | `false` | Включить/выключить сокращение путей в меню навигации |
|
|
||||||
| `path_max_depth` | number | `3` | Максимальная глубина пути перед сокращением с "…" в меню навигации |
|
|
||||||
| `fzf_path_truncate_enabled` | boolean | `false` | Включить/выключить сокращение путей в нечетком поиске (fzf) |
|
|
||||||
| `fzf_path_max_depth` | number | `5` | Максимальная глубина пути перед сокращением с "…" в fzf |
|
|
||||||
| `path_truncate_long_names_enabled` | boolean | `false` | Включить/выключить сокращение длинных названий папок в меню навигации |
|
|
||||||
| `fzf_path_truncate_long_names_enabled` | boolean | `false` | Включить/выключить сокращение длинных названий папок в fzf |
|
|
||||||
| `path_max_folder_name_length` | number | `20` | Максимальная длина названия папки перед сокращением в меню навигации |
|
|
||||||
| `fzf_path_max_folder_name_length` | number | `20` | Максимальная длина названия папки перед сокращением в fzf |
|
|
||||||
| `history_size` | number | `10` | Количество директорий для хранения в истории |
|
|
||||||
| `history_fzf_path_truncate_enabled` | boolean | `false` | Включить/выключить сокращение путей по глубине для отображения в истории |
|
|
||||||
| `history_fzf_path_max_depth` | number | `5` | Максимальная глубина пути перед сокращением для истории |
|
|
||||||
| `history_fzf_path_truncate_long_names_enabled` | boolean | `false` | Включить/выключить сокращение длинных названий папок для истории |
|
|
||||||
| `history_fzf_path_max_folder_name_length` | number | `30` | Максимальная длина названий папок перед сокращением для истории |
|
|
||||||
|
|
||||||
### Конфигурация закладок
|
|
||||||
|
|
||||||
Плагин поддерживает упрощенный синтаксис закладок в конфигурации:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
-- Упрощенный синтаксис (рекомендуется)
|
|
||||||
local bookmarks = {
|
|
||||||
{ tag = "Рабочий стол", path = "~/Desktop", key = "d" },
|
|
||||||
{ tag = "Проекты", path = "~/Projects", key = "p" },
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Особенности упрощенного синтаксиса:**
|
|
||||||
|
|
||||||
- **Расширение тильды** - `~` автоматически расширяется до домашней директории
|
|
||||||
- **Нормализация путей** - Разделители `/` автоматически конвертируются для вашей ОС
|
|
||||||
- **Автоматический завершающий разделитель** - Директории получают правильные завершающие разделители
|
|
||||||
|
|
||||||
### Сокращение путей
|
|
||||||
|
|
||||||
Функция сокращения путей может контролироваться двумя параметрами:
|
|
||||||
|
|
||||||
- `path_truncate_enabled` (boolean, по умолчанию: `false`) - Включает или выключает сокращение путей полностью. Если не указано в конфигурации, по умолчанию `false`
|
|
||||||
- `path_max_depth` (number, по умолчанию: `3`) - Контролирует, как длинные пути отображаются в меню навигации
|
|
||||||
- `history_fzf_path_truncate_enabled` (boolean, по умолчанию: `false`) - Включает или выключает сокращение путей в истории директорий
|
|
||||||
- `history_fzf_path_max_depth` (number, по умолчанию: `5`) - Контролирует, как длинные пути отображаются в истории директорий
|
|
||||||
|
|
||||||
Когда `path_truncate_enabled` явно установлен в `true` и путь имеет больше уровней директорий, чем `path_max_depth`, начальные части заменяются на "…" для сохранения краткости отображения.
|
|
||||||
|
|
||||||
**По умолчанию (когда `path_truncate_enabled` не указан или установлен в `false`):**
|
|
||||||
|
|
||||||
- Все пути отображаются полностью без сокращения
|
|
||||||
- `C:\Users\Documents\Projects\MyProject` → `C:\Users\Documents\Projects\MyProject` (полный путь)
|
|
||||||
|
|
||||||
**С `path_truncate_enabled = true` и `path_max_depth = 3`:**
|
|
||||||
|
|
||||||
- `C:\Users\Documents` → `C:\Users\Documents` (без изменений, 3 части)
|
|
||||||
- `C:\Users\Documents\Projects\MyProject` → `C:\…\Projects\MyProject` (сокращено, 5 частей)
|
|
||||||
- `~/.config/yazi/plugins/whoosh.yazi` → `~\…\plugins\whoosh.yazi` (сокращено, 5 частей)
|
|
||||||
|
|
||||||
#### Сокращение длины названий папок
|
|
||||||
|
|
||||||
Длинные названия папок могут быть сокращены для улучшения читаемости как в меню навигации, так и в нечетком поиске:
|
|
||||||
|
|
||||||
**Параметры конфигурации:**
|
|
||||||
|
|
||||||
- `path_truncate_long_names_enabled` (boolean, по умолчанию: `false`) - Включить/выключить для меню навигации
|
|
||||||
- `fzf_path_truncate_long_names_enabled` (boolean, по умолчанию: `false`) - Включить/выключить для нечеткого поиска (fzf)
|
|
||||||
- `history_fzf_path_truncate_long_names_enabled` (boolean, по умолчанию: `false`) - Включить/выключить для истории директорий
|
|
||||||
- `path_max_folder_name_length` (number, по умолчанию: `20`) - Максимальная длина для названий папок в меню навигации
|
|
||||||
- `fzf_path_max_folder_name_length` (number, по умолчанию: `20`) - Максимальная длина для названий папок в нечетком поиске
|
|
||||||
- `history_fzf_path_max_folder_name_length` (number, по умолчанию: `30`) - Максимальная длина для названий папок в истории директорий
|
|
||||||
|
|
||||||
**Как это работает:**
|
|
||||||
|
|
||||||
- Отдельные названия папок длиннее указанного лимита сокращаются до 40% от лимита + "..."
|
|
||||||
- Это сокращение применяется к каждому названию папки отдельно и работает независимо от сокращения по глубине пути
|
|
||||||
- Оба метода сокращения могут использоваться вместе для оптимального отображения
|
|
||||||
- Буквы дисков Windows (например, `C:\`) обрабатываются особым образом и никогда не сокращаются
|
|
||||||
|
|
||||||
**Примеры с `path_max_folder_name_length = 20`:**
|
|
||||||
|
|
||||||
- `VeryLongFolderNameThatExceedsLimit` → `VeryLongF…` (9 символов + "…")
|
|
||||||
- `C:\VeryLongFolderNameThatExceedsLimit\Documents` → `C:\VeryLongF…\Documents`
|
|
||||||
- `ShortName` → `ShortName` (без изменений, под лимитом)
|
|
||||||
- `/home/VeryLongFolderNameThatExceedsLimit/projects` → `/home/VeryLongF…/projects`
|
|
||||||
|
|
||||||
**В сочетании с сокращением по глубине:**
|
|
||||||
|
|
||||||
Когда включены как сокращение названий папок, так и сокращение по глубине, сначала сокращаются названия папок, затем применяется сокращение по глубине:
|
|
||||||
|
|
||||||
- Оригинал: `C:\Users\VeryLongFolderNameThatExceedsLimit\Documents\Projects\MyProject`
|
|
||||||
- После сокращения названий папок: `C:\Users\VeryLongF…\Documents\Projects\MyProject`
|
|
||||||
- После сокращения по глубине (max_depth=3): `C:\…\Projects\MyProject`
|
|
||||||
|
|
||||||
Эта функция значительно улучшает читаемость в глубоко вложенных структурах директорий, сохраняя при этом наиболее релевантную информацию о пути.
|
|
||||||
|
|
||||||
### Доступные команды
|
|
||||||
|
|
||||||
| Команда | Описание |
|
|
||||||
| ----------------- | -------------------------------------------------------------------- |
|
|
||||||
| `save` | Добавить закладку для выделенного файла/директории |
|
|
||||||
| `save_cwd` | Добавить закладку для текущей рабочей директории |
|
|
||||||
| `save_temp` | Добавить временную закладку для выделенного файла/директории |
|
|
||||||
| `save_cwd_temp` | Добавить временную закладку для текущей рабочей директории |
|
|
||||||
| `jump_by_key` | Открыть меню навигации для перехода к закладке по клавише |
|
|
||||||
| `jump_key_<keys>` | Мгновенный переход к закладке по указанной последовательности клавиш |
|
|
||||||
| `jump_by_fzf` | Открыть нечеткий поиск для перехода к закладке |
|
|
||||||
| `delete_by_key` | Удалить закладку, выбрав по клавише |
|
|
||||||
| `delete_by_fzf` | Удалить несколько закладок с помощью fzf (TAB для выбора) |
|
|
||||||
| `delete_all` | Удалить все пользовательские закладки (исключая конфигурационные) |
|
|
||||||
| `delete_all_temp` | Удалить все временные закладки |
|
|
||||||
| `rename_by_key` | Переименовать закладку, выбрав по клавише |
|
|
||||||
| `rename_by_fzf` | Переименовать закладку с помощью нечеткого поиска |
|
|
||||||
|
|
||||||
### Прямой переход по ключу
|
|
||||||
|
|
||||||
Вы можете перейти к закладке без меню, передав последовательность клавиш напрямую в одном аргументе:
|
|
||||||
|
|
||||||
- `plugin whoosh jump_key_<keys>` - последовательность внутри аргумента, например `jump_key_k`, `jump_key_<Space>`, `jump_key_bb`.
|
|
||||||
|
|
||||||
Последовательности должны передаваться единым аргументом; формы с разделением пробелами не поддерживаются. Формат совпадает с окном редактирования закладки, поэтому можно комбинировать отдельные символы, значения через запятую и специальные клавиши вроде `<Space>` или `<A-l>`.
|
|
||||||
|
|
||||||
### Управление меню навигации
|
|
||||||
|
|
||||||
При использовании `jump_by_key` доступны следующие специальные элементы управления:
|
|
||||||
|
|
||||||
| Клавиша по умолчанию | Действие |
|
|
||||||
| -------------------- | -------------------------------------------------------- |
|
|
||||||
| `<Enter>` | Создать временную закладку для текущей директории |
|
|
||||||
| `<Space>` | Открыть нечеткий поиск закладок |
|
|
||||||
| `<Tab>` | Открыть историю директорий (только если есть история) |
|
|
||||||
| `<Backspace>` | Вернуться к предыдущей директории (только если доступно) |
|
|
||||||
| `[a-zA-Z0-9]` | Перейти к закладке с соответствующей клавишей |
|
|
||||||
|
|
||||||
## Вдохновлено
|
|
||||||
|
|
||||||
- [yamb](https://github.com/h-hg/yamb.yazi)
|
|
||||||
- [bunny](https://github.com/stelcodes/bunny.yazi)
|
|
||||||
@@ -4,6 +4,10 @@
|
|||||||
<i>Save, search, and jump to your favorite paths in a blink</i>
|
<i>Save, search, and jump to your favorite paths in a blink</i>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="image/plugin.png" width="900" alt="Plugin preview" />
|
||||||
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
@@ -22,25 +26,26 @@
|
|||||||
> - **Directory history** - Navigate back to previous directory with Backspace
|
> - **Directory history** - Navigate back to previous directory with Backspace
|
||||||
> - **Tab history navigation** - Browse and jump to recently visited directories with Tab key
|
> - **Tab history navigation** - Browse and jump to recently visited directories with Tab key
|
||||||
> - **Quick bookmark creation** - Create temporary bookmarks directly from navigation menu
|
> - **Quick bookmark creation** - Create temporary bookmarks directly from navigation menu
|
||||||
> - **Configurable menu shortcuts** - Override the default Tab/Backspace/Enter/Space bindings from `init.lua`
|
> - **Project root navigation** - Jump to the current Git repository root with `-`
|
||||||
|
> - **Configurable menu shortcuts** - Override the default Tab/Backspace/Enter/Space/- bindings from `init.lua`
|
||||||
<div style="text-align: center;">
|
|
||||||
<img src="image/plugin.png" alt="Plugin preview" width="1100px">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> Requires Yazi v26.1.4+
|
> Requires Yazi v26.1.4+
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ya pkg add WhoSowSee/whoosh
|
||||||
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Manual installation
|
# Manual installation
|
||||||
|
|
||||||
# Linux/macOS
|
# Linux/macOS
|
||||||
git clone https://gitlab.com/WhoSowSee/whoosh.yazi.git ~/.config/yazi/plugins/whoosh.yazi
|
git clone https://github.com/WhoSowSee/whoosh.yazi.git ~/.config/yazi/plugins/whoosh.yazi
|
||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
git clone https://gitlab.com/WhoSowSee/whoosh.yazi.git $env:APPDATA\yazi\config\plugins\whoosh.yazi
|
git clone https://github.com/WhoSowSee/whoosh.yazi.git $env:APPDATA\yazi\config\plugins\whoosh.yazi
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -94,6 +99,7 @@ require("whoosh"):setup {
|
|||||||
fuzzy_search = "<Space>", -- Launch fuzzy search (fzf)
|
fuzzy_search = "<Space>", -- Launch fuzzy search (fzf)
|
||||||
history = "<Tab>", -- Open directory history
|
history = "<Tab>", -- Open directory history
|
||||||
previous_dir = "<Backspace>", -- Jump back to the previous directory
|
previous_dir = "<Backspace>", -- Jump back to the previous directory
|
||||||
|
project_root = "-", -- Jump to the current Git repository root
|
||||||
},
|
},
|
||||||
|
|
||||||
-- File path for storing user bookmarks
|
-- File path for storing user bookmarks
|
||||||
@@ -242,6 +248,7 @@ When using `jump_by_key`, you get access to a smart navigation menu with:
|
|||||||
- **Fuzzy search** - Press `<Space>` to open fzf search
|
- **Fuzzy search** - Press `<Space>` to open fzf search
|
||||||
- **Directory history** - Press `<Tab>` to browse history via fzf (only if history exists)
|
- **Directory history** - Press `<Tab>` to browse history via fzf (only if history exists)
|
||||||
- **Previous directory** - Press `<Backspace>` to return to the previous directory (if available)
|
- **Previous directory** - Press `<Backspace>` to return to the previous directory (if available)
|
||||||
|
- **Project root** - Press `-` to jump to the current Git repository root (when inside a repository)
|
||||||
- **All bookmarks** - Both permanent and temporary bookmarks with clear visual distinction
|
- **All bookmarks** - Both permanent and temporary bookmarks with clear visual distinction
|
||||||
|
|
||||||
### Directory History Navigation
|
### Directory History Navigation
|
||||||
@@ -300,6 +307,21 @@ require("whoosh"):setup {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Alt+Shift keybindings in yazi.nvim
|
||||||
|
|
||||||
|
In affected Neovim versions, `Alt+Shift` combinations forwarded to an
|
||||||
|
application inside Neovim's built-in terminal may lose the Shift modifier.
|
||||||
|
For example, Yazi may receive `<A-d>` instead of `<A-D>` and execute
|
||||||
|
`jump_key_d` instead of `jump_key_D`.
|
||||||
|
|
||||||
|
This was reproduced with Neovim `0.12.4` and Kitty `0.43.1` and is tracked
|
||||||
|
in [neovim/neovim#36213](https://github.com/neovim/neovim/pull/36213).
|
||||||
|
Whoosh cannot restore a modifier that was lost before the plugin was invoked.
|
||||||
|
|
||||||
|
Until this is fixed in Neovim, avoid keybindings inside `yazi.nvim` that
|
||||||
|
differ only by Shift, such as `<A-d>` and `<A-D>`. Assign one of the
|
||||||
|
bookmarks a different key combination.
|
||||||
|
|
||||||
### Bookmark Types
|
### Bookmark Types
|
||||||
|
|
||||||
The plugin supports three types of bookmarks:
|
The plugin supports three types of bookmarks:
|
||||||
@@ -319,7 +341,7 @@ The plugin supports the following configuration options in the `setup()` functio
|
|||||||
| `bookmarks` | table | `{}` | Pre-configured bookmarks (cannot be deleted through plugin) |
|
| `bookmarks` | table | `{}` | Pre-configured bookmarks (cannot be deleted through plugin) |
|
||||||
| `jump_notify` | boolean | `false` | Show notification when jumping to a bookmark |
|
| `jump_notify` | boolean | `false` | Show notification when jumping to a bookmark |
|
||||||
| `keys` | string | `"0123456789abcdef..."` | Characters used for auto-generating bookmark keys |
|
| `keys` | string | `"0123456789abcdef..."` | Characters used for auto-generating bookmark keys |
|
||||||
| `special_keys` | table | `see description` | Override the built-in menu shortcuts (Enter/Space/Tab/Backspace); set to `false` to hide an item |
|
| `special_keys` | table | `see description` | Override the built-in menu shortcuts (Enter/Space/-/Tab/Backspace); set to `false` to hide an item |
|
||||||
| `path` | string | OS-dependent | File path where user bookmarks are stored |
|
| `path` | string | OS-dependent | File path where user bookmarks are stored |
|
||||||
| `home_alias_enabled` | boolean | `true` | Replace paths under the user's home directory with `~` |
|
| `home_alias_enabled` | boolean | `true` | Replace paths under the user's home directory with `~` |
|
||||||
| `path_truncate_enabled` | boolean | `false` | Enable/disable path truncation in navigation menu |
|
| `path_truncate_enabled` | boolean | `false` | Enable/disable path truncation in navigation menu |
|
||||||
@@ -437,6 +459,10 @@ You can jump without opening the menu by calling the plugin with an inline key s
|
|||||||
|
|
||||||
Sequences must be provided inline; whitespace-separated forms are not supported. The format matches the bookmark editing prompt, so you can mix plain characters, comma-separated tokens, and special keys like `<Space>` or `<A-l>`.
|
Sequences must be provided inline; whitespace-separated forms are not supported. The format matches the bookmark editing prompt, so you can mix plain characters, comma-separated tokens, and special keys like `<Space>` or `<A-l>`.
|
||||||
|
|
||||||
|
The project root shortcut is detected dynamically by walking upward from the current directory until whoosh finds a `.git` file or directory. It is not saved to your bookmarks file.
|
||||||
|
|
||||||
|
Special menu shortcuts are resolved before bookmark keys, so a bookmark using the same key will be shadowed by the special shortcut.
|
||||||
|
|
||||||
### Navigation Menu Controls
|
### Navigation Menu Controls
|
||||||
|
|
||||||
When using `jump_by_key`, the following special controls are available:
|
When using `jump_by_key`, the following special controls are available:
|
||||||
@@ -447,9 +473,43 @@ When using `jump_by_key`, the following special controls are available:
|
|||||||
| `<Space>` | Open fuzzy search |
|
| `<Space>` | Open fuzzy search |
|
||||||
| `<Tab>` | Open directory history (only if history exists) |
|
| `<Tab>` | Open directory history (only if history exists) |
|
||||||
| `<Backspace>` | Return to previous directory (if available) |
|
| `<Backspace>` | Return to previous directory (if available) |
|
||||||
|
| `-` | Jump to current Git repository root |
|
||||||
| `[a-zA-Z0-9]` | Jump to bookmark with corresponding key |
|
| `[a-zA-Z0-9]` | Jump to bookmark with corresponding key |
|
||||||
|
|
||||||
## Inspiration
|
## Inspiration
|
||||||
|
|
||||||
- [yamb](https://github.com/h-hg/yamb.yazi)
|
- [yamb](https://github.com/h-hg/yamb.yazi)
|
||||||
- [bunny](https://github.com/stelcodes/bunny.yazi)
|
- [bunny](https://github.com/stelcodes/bunny.yazi)
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://starchart.cc/WhoSowSee/whoosh.yazi">
|
||||||
|
<picture>
|
||||||
|
<source
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
srcset="https://starchart.cc/WhoSowSee/whoosh.yazi.svg?variant=custom&background=%230d1117&axis=%238b949e&line=%232f81f7"
|
||||||
|
/>
|
||||||
|
<source
|
||||||
|
media="(prefers-color-scheme: light)"
|
||||||
|
srcset="https://starchart.cc/WhoSowSee/whoosh.yazi.svg?variant=custom&background=%23ffffff&axis=%2357606a&line=%230969da"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
alt="Stargazers over time"
|
||||||
|
src="https://starchart.cc/WhoSowSee/whoosh.yazi.svg?variant=custom&background=%23ffffff&axis=%2357606a&line=%230969da"
|
||||||
|
/>
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/catppuccin/catppuccin/main/assets/footers/gray0_ctp_on_line.svg?sanitize=true" alt="catppuccin" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<i><code>© 2026-present <a href="https://github.com/WhoSowSee">WhoSowSee</a></code></i>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/WhoSowSee/whoosh.yazi/blob/main/LICENSE"><img src="https://img.shields.io/github/license/WhoSowSee/whoosh.yazi?style=for-the-badge&color=CBA6F7&logoColor=cdd6f4&labelColor=302D41" alt="LICENSE"></a>
|
||||||
|
</p>
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
zshrc /home/liph/dotfiles/zshrc d,z
|
|
||||||
yazi /home/liph/dotfiles/yazi/.config/yazi d,y
|
|
||||||
tank /mnt/tank t,t
|
|
||||||
stash /mnt/flash1/podman/lxc_servarr/servarr/data/stash S
|
|
||||||
ssd2 /mnt/ssd2 s,2
|
|
||||||
ssd1 /mnt/ssd1 s,1
|
|
||||||
podman /mnt/flash1/podman t,p
|
|
||||||
plugins /home/liph/dotfiles/nvim/.config/nvim/lua/plugins n,p
|
|
||||||
nvim /home/liph/dotfiles/nvim/.config/nvim n,n
|
|
||||||
niri /home/liph/dotfiles/niri/.config/niri d,n
|
|
||||||
music /mnt/tank/music t,m
|
|
||||||
home /home/liph f
|
|
||||||
hdd4 /mnt/hdd4 h,4
|
|
||||||
hdd3 /mnt/hdd3 h,3
|
|
||||||
hdd2 /mnt/hdd2 h,2
|
|
||||||
hdd1 /mnt/hdd1 h,1
|
|
||||||
flash1 /mnt/flash1 t,f
|
|
||||||
ebooks_phil /mnt/tank/ebooks_phil t,b
|
|
||||||
Downloads /home/liph/Downloads D,d
|
|
||||||
downloads /mnt/tank/downloads t,d
|
|
||||||
dotfiles /home/liph/dotfiles d,d
|
|
||||||
Documents /home/liph/Documents D,D
|
|
||||||
.config /home/liph/.config c
|
|
||||||
code /home/liph/code C
|
|
||||||
aerc /home/liph/dotfiles/aerc/.config/aerc d,a
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -5,6 +5,7 @@ local path_sep = package.config:sub(1, 1)
|
|||||||
local DEFAULT_SPECIAL_KEYS = {
|
local DEFAULT_SPECIAL_KEYS = {
|
||||||
create_temp = "<Enter>",
|
create_temp = "<Enter>",
|
||||||
fuzzy_search = "<Space>",
|
fuzzy_search = "<Space>",
|
||||||
|
project_root = "-",
|
||||||
history = "<Tab>",
|
history = "<Tab>",
|
||||||
previous_dir = "<Backspace>",
|
previous_dir = "<Backspace>",
|
||||||
}
|
}
|
||||||
@@ -18,14 +19,6 @@ local function default(value, fallback)
|
|||||||
return value
|
return value
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_fzf_delimiter()
|
|
||||||
if ya.target_family() == "windows" then
|
|
||||||
return "--delimiter=\\t"
|
|
||||||
else
|
|
||||||
return "--delimiter='\t'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local get_hovered_path = ya.sync(function(state)
|
local get_hovered_path = ya.sync(function(state)
|
||||||
local h = cx.active.current.hovered
|
local h = cx.active.current.hovered
|
||||||
if h then
|
if h then
|
||||||
@@ -110,13 +103,15 @@ end)
|
|||||||
local function ensure_directory(path)
|
local function ensure_directory(path)
|
||||||
local dir_path = path:match("(.+)[\\/][^\\/]*$")
|
local dir_path = path:match("(.+)[\\/][^\\/]*$")
|
||||||
if not dir_path then
|
if not dir_path then
|
||||||
return
|
return true
|
||||||
end
|
end
|
||||||
if ya.target_family() == "windows" then
|
|
||||||
os.execute('mkdir "' .. dir_path:gsub("/", "\\") .. '" 2>nul')
|
local ok, err = fs.create("dir_all", Url(dir_path))
|
||||||
else
|
if not ok then
|
||||||
os.execute('mkdir -p "' .. dir_path .. '"')
|
notify("Failed to create directory: " .. dir_path .. "\nError: " .. tostring(err), "error", 2)
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local function normalize_path(path)
|
local function normalize_path(path)
|
||||||
@@ -138,6 +133,81 @@ local function normalize_path(path)
|
|||||||
return normalized_path
|
return normalized_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function path_exists(path)
|
||||||
|
if not path or path == "" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local cha = fs.cha(Url(path), false)
|
||||||
|
return cha ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function paths_equal(left, right)
|
||||||
|
left = normalize_path(left)
|
||||||
|
right = normalize_path(right)
|
||||||
|
|
||||||
|
if ya.target_family() == "windows" then
|
||||||
|
return left:lower() == right:lower()
|
||||||
|
end
|
||||||
|
|
||||||
|
return left == right
|
||||||
|
end
|
||||||
|
|
||||||
|
local function join_path(base, child)
|
||||||
|
if not base or base == "" then
|
||||||
|
return child
|
||||||
|
end
|
||||||
|
if base:sub(-1) == path_sep then
|
||||||
|
return base .. child
|
||||||
|
end
|
||||||
|
return base .. path_sep .. child
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parent_path(path)
|
||||||
|
local current = normalize_path(path)
|
||||||
|
if current == "" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if ya.target_family() == "windows" then
|
||||||
|
if current:match("^[A-Za-z]:\\$") then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
current = current:gsub("[\\/]+$", "")
|
||||||
|
local parent = current:match("^(.*)[\\/][^\\/]+$")
|
||||||
|
if parent and parent ~= "" then
|
||||||
|
if parent:match("^[A-Za-z]:$") then
|
||||||
|
return parent .. "\\"
|
||||||
|
end
|
||||||
|
return parent
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if current == "/" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
current = current:gsub("/+$", "")
|
||||||
|
local parent = current:match("^(.*)/[^/]+$")
|
||||||
|
if parent == "" then
|
||||||
|
return "/"
|
||||||
|
end
|
||||||
|
return parent
|
||||||
|
end
|
||||||
|
|
||||||
|
local function find_project_root(path)
|
||||||
|
local current = normalize_path(path)
|
||||||
|
while current and current ~= "" do
|
||||||
|
if path_exists(join_path(current, ".git")) then
|
||||||
|
return current
|
||||||
|
end
|
||||||
|
current = parent_path(current)
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
local function apply_home_alias(path)
|
local function apply_home_alias(path)
|
||||||
if not path or path == "" then
|
if not path or path == "" then
|
||||||
return path
|
return path
|
||||||
@@ -518,11 +588,14 @@ local function deserialize_key_from_file(key_str)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local save_to_file = function(mb_path, bookmarks)
|
local save_to_file = function(mb_path, bookmarks)
|
||||||
ensure_directory(mb_path)
|
if not ensure_directory(mb_path) then
|
||||||
local file = io.open(mb_path, "w")
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local file, open_err = io.open(mb_path, "w")
|
||||||
if file == nil then
|
if file == nil then
|
||||||
notify("Cannot create bookmark file: " .. mb_path, "error", 2)
|
notify("Cannot create bookmark file: " .. mb_path .. "\nError: " .. tostring(open_err), "error", 2)
|
||||||
return
|
return false
|
||||||
end
|
end
|
||||||
local array = {}
|
local array = {}
|
||||||
for _, item in pairs(bookmarks) do
|
for _, item in pairs(bookmarks) do
|
||||||
@@ -531,9 +604,55 @@ local save_to_file = function(mb_path, bookmarks)
|
|||||||
sort_bookmarks(array, "tag", "key", true)
|
sort_bookmarks(array, "tag", "key", true)
|
||||||
for _, item in ipairs(array) do
|
for _, item in ipairs(array) do
|
||||||
local serialized_key = serialize_key_for_file(item.key)
|
local serialized_key = serialize_key_for_file(item.key)
|
||||||
file:write(string.format("%s\t%s\t%s\n", item.tag, item.path, serialized_key))
|
local ok, write_err = file:write(string.format("%s\t%s\t%s\n", item.tag, item.path, serialized_key))
|
||||||
|
if not ok then
|
||||||
|
file:close()
|
||||||
|
notify("Cannot write bookmark file: " .. mb_path .. "\nError: " .. tostring(write_err), "error", 2)
|
||||||
|
return false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
file:close()
|
|
||||||
|
local ok, close_err = file:close()
|
||||||
|
if not ok then
|
||||||
|
notify("Cannot close bookmark file: " .. mb_path .. "\nError: " .. tostring(close_err), "error", 2)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Run fzf with given args and input lines
|
||||||
|
--- @param args string[]
|
||||||
|
--- @param input string[]
|
||||||
|
--- @return string[]|nil stdout, string|nil error # output lines or nil + error message
|
||||||
|
local function run_fzf_with(args, input)
|
||||||
|
local child, err = Command("fzf")
|
||||||
|
:arg(args)
|
||||||
|
:stdin(Command.PIPED)
|
||||||
|
:stdout(Command.PIPED)
|
||||||
|
:stderr(Command.PIPED)
|
||||||
|
:spawn()
|
||||||
|
|
||||||
|
if not child then
|
||||||
|
return nil, "Failed to launch fzf: \n" .. err
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, line in ipairs(input) do
|
||||||
|
child:write_all(line .. "\n")
|
||||||
|
end
|
||||||
|
child:flush()
|
||||||
|
|
||||||
|
local output, err = child:wait_with_output()
|
||||||
|
if not output then
|
||||||
|
return nil, "Error running fzf: \n" .. tostring(err)
|
||||||
|
elseif not output.status.success and #output.stderr > 0 then
|
||||||
|
return nil, "fzf exited with code " .. output.status.code .. ": \n" .. output.stderr
|
||||||
|
end
|
||||||
|
|
||||||
|
local lines = {}
|
||||||
|
for line in output.stdout:gmatch("[^\r\n]+") do
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
return lines, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Unified fzf bookmark picker. opts:
|
-- Unified fzf bookmark picker. opts:
|
||||||
@@ -550,69 +669,71 @@ local function run_fzf_picker(opts)
|
|||||||
local temp_bookmarks = get_temp_bookmarks()
|
local temp_bookmarks = get_temp_bookmarks()
|
||||||
local perm_bookmarks = source == "user" and get_state_attr("bookmarks") or get_all_bookmarks()
|
local perm_bookmarks = source == "user" and get_state_attr("bookmarks") or get_all_bookmarks()
|
||||||
|
|
||||||
|
local items = {}
|
||||||
|
local max_tag_w, max_path_w = 0, 0
|
||||||
|
|
||||||
|
local function add_section(bookmarks, prefix, require_full)
|
||||||
|
local arr = {}
|
||||||
|
for _, item in pairs(bookmarks or {}) do
|
||||||
|
if require_full then
|
||||||
|
if item and item.tag and item.path and item.key then table.insert(arr, item) end
|
||||||
|
else
|
||||||
|
table.insert(arr, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
sort_bookmarks(arr, "tag", "key", true)
|
||||||
|
for _, item in ipairs(arr) do
|
||||||
|
local tag = prefix .. item.tag
|
||||||
|
local display_path = path_to_desc_for_fzf(item.path)
|
||||||
|
table.insert(items, { tag = tag, path = item.path, key = item.key or "" })
|
||||||
|
max_tag_w = math.max(max_tag_w, get_display_width(tag))
|
||||||
|
max_path_w = math.max(max_path_w, get_display_width(display_path))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if temp_bookmarks and next(temp_bookmarks) then
|
||||||
|
add_section(temp_bookmarks, "[TEMP] ", true)
|
||||||
|
end
|
||||||
|
if perm_bookmarks and next(perm_bookmarks) then
|
||||||
|
add_section(perm_bookmarks, "", false)
|
||||||
|
end
|
||||||
|
|
||||||
local permit = ui.hide()
|
local permit = ui.hide()
|
||||||
local temp_file_path = os.tmpname()
|
|
||||||
local temp_file = io.open(temp_file_path, "w")
|
|
||||||
local cmd
|
|
||||||
|
|
||||||
if temp_file then
|
local args = {
|
||||||
local items = {}
|
"--prompt=" .. prompt,
|
||||||
local max_tag_w, max_path_w = 0, 0
|
}
|
||||||
|
|
||||||
local function add_section(bookmarks, prefix, require_full)
|
if #items > 0 then
|
||||||
local arr = {}
|
args = {
|
||||||
for _, item in pairs(bookmarks or {}) do
|
table.unpack(args),
|
||||||
if require_full then
|
"--delimiter=\t",
|
||||||
if item and item.tag and item.path and item.key then table.insert(arr, item) end
|
"--with-nth=1",
|
||||||
else
|
}
|
||||||
table.insert(arr, item)
|
if opts.multi then
|
||||||
end
|
table.insert(args, "--multi")
|
||||||
end
|
|
||||||
sort_bookmarks(arr, "tag", "key", true)
|
|
||||||
for _, item in ipairs(arr) do
|
|
||||||
local tag = prefix .. item.tag
|
|
||||||
local display_path = path_to_desc_for_fzf(item.path)
|
|
||||||
table.insert(items, { tag = tag, path = item.path, key = item.key or "" })
|
|
||||||
max_tag_w = math.max(max_tag_w, get_display_width(tag))
|
|
||||||
max_path_w = math.max(max_path_w, get_display_width(display_path))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if temp_bookmarks and next(temp_bookmarks) then
|
local input = {}
|
||||||
add_section(temp_bookmarks, "[TEMP] ", true)
|
if #items > 0 then
|
||||||
end
|
for _, item in ipairs(items) do
|
||||||
if perm_bookmarks and next(perm_bookmarks) then
|
local formatted_line = format_bookmark_for_fzf(item.tag, item.path, item.key, max_tag_w, max_path_w)
|
||||||
add_section(perm_bookmarks, "", false)
|
table.insert(input, formatted_line .. "\t" .. item.path)
|
||||||
end
|
|
||||||
|
|
||||||
if #items > 0 then
|
|
||||||
for _, item in ipairs(items) do
|
|
||||||
local formatted_line = format_bookmark_for_fzf(item.tag, item.path, item.key, max_tag_w, max_path_w)
|
|
||||||
temp_file:write(formatted_line .. "\t" .. item.path .. "\n")
|
|
||||||
end
|
|
||||||
temp_file:close()
|
|
||||||
cmd = string.format("fzf %s%s --with-nth=1 --prompt=\"%s\" < \"%s\"",
|
|
||||||
opts.multi and "--multi " or "", get_fzf_delimiter(), prompt, temp_file_path)
|
|
||||||
else
|
|
||||||
temp_file:close()
|
|
||||||
cmd = string.format("echo %s | fzf --prompt=\"%s\"", empty_msg, prompt)
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
cmd = string.format("echo %s | fzf --prompt=\"%s\"", empty_msg, prompt)
|
table.insert(input, empty_msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
local handle = io.popen(cmd, "r")
|
local output, err = run_fzf_with(args, input)
|
||||||
local raw = ""
|
|
||||||
if handle then
|
|
||||||
raw = handle:read("*all") or ""
|
|
||||||
handle:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
if temp_file_path then os.remove(temp_file_path) end
|
|
||||||
permit:drop()
|
permit:drop()
|
||||||
|
if not output then
|
||||||
|
notify(err, "error", 2)
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
local paths = {}
|
local paths = {}
|
||||||
for raw_line in raw:gmatch("[^\r\n]+") do
|
for _, raw_line in ipairs(output) do
|
||||||
local line = string.gsub(raw_line, "^%s*(.-)%s*$", "%1")
|
local line = string.gsub(raw_line, "^%s*(.-)%s*$", "%1")
|
||||||
if line ~= "" and line ~= empty_msg then
|
if line ~= "" and line ~= empty_msg then
|
||||||
local tab_pos = line:find("\t")
|
local tab_pos = line:find("\t")
|
||||||
@@ -655,37 +776,33 @@ fzf_history = function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local permit = ui.hide()
|
local permit = ui.hide()
|
||||||
local temp_file_path = os.tmpname()
|
|
||||||
local temp_file = io.open(temp_file_path, "w")
|
|
||||||
|
|
||||||
if temp_file then
|
local args = {
|
||||||
for i, path in ipairs(filtered_history) do
|
"--delimiter=\t",
|
||||||
local display_path = path_to_desc_for_history(path)
|
"--with-nth=1",
|
||||||
local formatted_line = string.format("%2d. %s", i, display_path)
|
"--prompt=History > ",
|
||||||
temp_file:write(formatted_line .. "\t" .. path .. "\n")
|
}
|
||||||
|
|
||||||
|
local input = {}
|
||||||
|
for i, path in ipairs(filtered_history) do
|
||||||
|
local display_path = path_to_desc_for_history(path)
|
||||||
|
local formatted_line = string.format("%2d. %s", i, display_path)
|
||||||
|
table.insert(input, formatted_line .. "\t" .. path)
|
||||||
|
end
|
||||||
|
|
||||||
|
local output, err = run_fzf_with(args, input)
|
||||||
|
permit:drop()
|
||||||
|
if not output then
|
||||||
|
notify(err, "error", 2)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if #output > 0 then
|
||||||
|
local result = output[1]:gsub("^%s*(.-)%s*$", "%1")
|
||||||
|
local tab_pos = result:find("\t")
|
||||||
|
if tab_pos then
|
||||||
|
return result:sub(tab_pos + 1)
|
||||||
end
|
end
|
||||||
temp_file:close()
|
|
||||||
|
|
||||||
local cmd = string.format("fzf %s --with-nth=1 --prompt=\"History > \" < \"%s\"",
|
|
||||||
get_fzf_delimiter(), temp_file_path)
|
|
||||||
local handle = io.popen(cmd, "r")
|
|
||||||
local result = ""
|
|
||||||
if handle then
|
|
||||||
result = string.gsub(handle:read("*all") or "", "^%s*(.-)%s*$", "%1")
|
|
||||||
handle:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
os.remove(temp_file_path)
|
|
||||||
permit:drop()
|
|
||||||
|
|
||||||
if result and result ~= "" then
|
|
||||||
local tab_pos = result:find("\t")
|
|
||||||
if tab_pos then
|
|
||||||
return result:sub(tab_pos + 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
permit:drop()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -694,6 +811,8 @@ end
|
|||||||
local create_special_menu_items = function()
|
local create_special_menu_items = function()
|
||||||
local special_items = {}
|
local special_items = {}
|
||||||
local special_keys = get_state_attr("special_keys") or DEFAULT_SPECIAL_KEYS
|
local special_keys = get_state_attr("special_keys") or DEFAULT_SPECIAL_KEYS
|
||||||
|
local current_path = normalize_path(get_current_dir_path())
|
||||||
|
|
||||||
local create_temp_key = special_keys.create_temp
|
local create_temp_key = special_keys.create_temp
|
||||||
if create_temp_key then
|
if create_temp_key then
|
||||||
table.insert(special_items, { desc = "Create temporary bookmark", on = create_temp_key, path = "__CREATE_TEMP__" })
|
table.insert(special_items, { desc = "Create temporary bookmark", on = create_temp_key, path = "__CREATE_TEMP__" })
|
||||||
@@ -706,7 +825,6 @@ local create_special_menu_items = function()
|
|||||||
|
|
||||||
local current_tab = get_current_tab_idx()
|
local current_tab = get_current_tab_idx()
|
||||||
local history = get_tab_history(current_tab)
|
local history = get_tab_history(current_tab)
|
||||||
local current_path = normalize_path(get_current_dir_path())
|
|
||||||
|
|
||||||
local filtered_history = {}
|
local filtered_history = {}
|
||||||
if history then
|
if history then
|
||||||
@@ -729,6 +847,18 @@ local create_special_menu_items = function()
|
|||||||
table.insert(special_items, { desc = "<- " .. display_path, on = previous_dir_key, path = previous_dir })
|
table.insert(special_items, { desc = "<- " .. display_path, on = previous_dir_key, path = previous_dir })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local project_root_key = special_keys.project_root
|
||||||
|
if project_root_key then
|
||||||
|
local project_root = find_project_root(current_path)
|
||||||
|
if project_root and not paths_equal(project_root, current_path) then
|
||||||
|
table.insert(special_items, {
|
||||||
|
desc = "Project root",
|
||||||
|
on = project_root_key,
|
||||||
|
path = project_root,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return special_items
|
return special_items
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1097,8 +1227,9 @@ action_save = function(path, is_temp)
|
|||||||
else
|
else
|
||||||
set_bookmarks(path, { tag = tag, path = path, key = key })
|
set_bookmarks(path, { tag = tag, path = path, key = key })
|
||||||
local user_bookmarks = get_state_attr("bookmarks")
|
local user_bookmarks = get_state_attr("bookmarks")
|
||||||
save_to_file(mb_path, user_bookmarks)
|
if save_to_file(mb_path, user_bookmarks) then
|
||||||
notify('"' .. tag .. '" saved')
|
notify('"' .. tag .. '" saved')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1123,8 +1254,9 @@ action_delete = function(path)
|
|||||||
else
|
else
|
||||||
set_bookmarks(path, nil)
|
set_bookmarks(path, nil)
|
||||||
local updated_user_bookmarks = get_state_attr("bookmarks")
|
local updated_user_bookmarks = get_state_attr("bookmarks")
|
||||||
save_to_file(mb_path, updated_user_bookmarks)
|
if save_to_file(mb_path, updated_user_bookmarks) then
|
||||||
notify('"' .. tag .. '" deleted')
|
notify('"' .. tag .. '" deleted')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1162,7 +1294,9 @@ action_delete_multi = function(paths)
|
|||||||
|
|
||||||
if deleted_count > 0 then
|
if deleted_count > 0 then
|
||||||
local updated_user_bookmarks = get_state_attr("bookmarks")
|
local updated_user_bookmarks = get_state_attr("bookmarks")
|
||||||
save_to_file(mb_path, updated_user_bookmarks)
|
if not save_to_file(mb_path, updated_user_bookmarks) then
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local total_deleted = deleted_count + deleted_temp_count
|
local total_deleted = deleted_count + deleted_temp_count
|
||||||
@@ -1203,8 +1337,9 @@ local action_delete_all = function(temp_only)
|
|||||||
notify("All temporary bookmarks deleted")
|
notify("All temporary bookmarks deleted")
|
||||||
else
|
else
|
||||||
set_state_attr("bookmarks", {})
|
set_state_attr("bookmarks", {})
|
||||||
save_to_file(mb_path, {})
|
if save_to_file(mb_path, {}) then
|
||||||
notify("All user-created bookmarks deleted")
|
notify("All user-created bookmarks deleted")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1246,7 +1381,6 @@ return {
|
|||||||
end
|
end
|
||||||
state.special_keys = special_keys
|
state.special_keys = special_keys
|
||||||
|
|
||||||
ensure_directory(state.path)
|
|
||||||
local keys = options.keys or "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
local keys = options.keys or "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
state.keys, state.key2rank = {}, {}
|
state.keys, state.key2rank = {}, {}
|
||||||
for i = 1, #keys do
|
for i = 1, #keys do
|
||||||
|
|||||||
+308
-96
@@ -1,16 +1,24 @@
|
|||||||
|
# A TOML linter such as Tombi can use this schema to validate your config.
|
||||||
|
# If you encounter any problems, please file an issue at https://github.com/yazi-rs/schemas.
|
||||||
|
|
||||||
|
#:schema https://yazi-rs.github.io/schemas/yazi.json
|
||||||
|
|
||||||
[mgr]
|
[mgr]
|
||||||
# it also support --mode=or when using with --input or --keys=!1q or --key=!1q or --tag=!1q or --tags=!1q
|
ratio = [1, 2, 3]
|
||||||
ratio = [2, 3, 5]
|
sort_by = "alphabetical"
|
||||||
sort_by = "natural"
|
|
||||||
sort_sensitive = false
|
sort_sensitive = false
|
||||||
sort_reverse = false
|
sort_reverse = false
|
||||||
sort_dir_first = true
|
sort_dir_first = true
|
||||||
|
sort_translit = false
|
||||||
|
sort_fallback = "alphabetical"
|
||||||
linemode = "none"
|
linemode = "none"
|
||||||
show_hidden = true
|
show_hidden = false
|
||||||
show_symlink = true
|
show_symlink = true
|
||||||
scrolloff = 5
|
scrolloff = 5
|
||||||
|
mouse_events = ["click", "scroll", "drag"]
|
||||||
|
|
||||||
[preview]
|
[preview]
|
||||||
|
wrap = "no"
|
||||||
tab_size = 2
|
tab_size = 2
|
||||||
max_width = 600
|
max_width = 600
|
||||||
max_height = 900
|
max_height = 900
|
||||||
@@ -18,127 +26,331 @@ cache_dir = ""
|
|||||||
image_delay = 30
|
image_delay = 30
|
||||||
image_filter = "triangle"
|
image_filter = "triangle"
|
||||||
image_quality = 75
|
image_quality = 75
|
||||||
|
ueberzug_scale = 1
|
||||||
|
ueberzug_offset = [0, 0, 0, 0]
|
||||||
|
|
||||||
[opener]
|
[opener]
|
||||||
# edit = [
|
edit = [
|
||||||
# { run = '$EDITOR "$@"', block = true, for = "unix" },
|
{ run = "${EDITOR:-vi} %s", desc = "$EDITOR", for = "unix", block = true },
|
||||||
# ]
|
{ run = "code %s", desc = "code", for = "windows", orphan = true },
|
||||||
open = [{ run = 'xdg-open "$1"', desc = "Open", orphan = true }]
|
{ run = "code -w %s", desc = "code (block)", for = "windows", block = true },
|
||||||
edit = [{ run = 'nvim "$@"', block = true, desc = "nvim", for = "unix" }]
|
]
|
||||||
|
play = [
|
||||||
|
{ run = "xdg-open %s1", desc = "Play", for = "linux", orphan = true },
|
||||||
|
{ run = "open %s", desc = "Play", for = "macos" },
|
||||||
|
{ run = 'start "" %s1', desc = "Play", for = "windows", orphan = true },
|
||||||
|
{ run = "termux-open %s1", desc = "Play", for = "android" },
|
||||||
|
{ run = "mediainfo %s1; echo 'Press enter to exit'; read _", block = true, desc = "Show media info", for = "unix" },
|
||||||
|
{ run = "mediainfo %s1 & pause", block = true, desc = "Show media info", for = "windows" },
|
||||||
|
]
|
||||||
|
open = [
|
||||||
|
{ run = "xdg-open %s1", desc = "Open", for = "linux" },
|
||||||
|
{ run = "open %s", desc = "Open", for = "macos" },
|
||||||
|
{ run = 'start "" %s1', desc = "Open", for = "windows", orphan = true },
|
||||||
|
{ run = "termux-open %s1", desc = "Open", for = "android" },
|
||||||
|
]
|
||||||
reveal = [
|
reveal = [
|
||||||
{ run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" },
|
{ run = "xdg-open %d1", desc = "Reveal", for = "linux" },
|
||||||
|
{ run = "open -R %s1", desc = "Reveal", for = "macos" },
|
||||||
|
{ run = "explorer /select,%s1", desc = "Reveal", for = "windows", orphan = true },
|
||||||
|
{ run = "termux-open %d1", desc = "Reveal", for = "android" },
|
||||||
|
{ run = "clear; exiftool %s1; echo 'Press enter to exit'; read _", desc = "Show EXIF", for = "unix", block = true },
|
||||||
|
]
|
||||||
|
extract = [{ run = "ya pub extract --list %s", desc = "Extract here" }]
|
||||||
|
download = [
|
||||||
|
{ run = "ya emit download --open %S", desc = "Download and open" },
|
||||||
|
{ run = "ya emit download %S", desc = "Download" },
|
||||||
|
]
|
||||||
|
trash = [
|
||||||
|
{ run = "ya pub trash-restore --list %S", desc = "Restore selected files" },
|
||||||
|
{ run = "ya pub trash-empty --list %S", desc = "Empty trash bin" },
|
||||||
]
|
]
|
||||||
play = [{ run = 'mpv --force-window "$@"', orphan = true, for = "unix" }]
|
|
||||||
pdf = [{ run = 'zathura "$@"', orphan = true, for = "linux" }]
|
|
||||||
text = [{ run = 'nvim "$@"', block = true, for = "linux" }]
|
|
||||||
epub = [{ run = 'epy "$@"', orphan = true, for = "linux" }]
|
|
||||||
|
|
||||||
[open]
|
[open]
|
||||||
rules = [
|
rules = [
|
||||||
# Empty files
|
# Folder
|
||||||
{ mime = "inode/empty", use = "edit" },
|
{ url = "*/", use = [
|
||||||
# { mime = "inode/x-empty", use = "edit" },
|
"edit",
|
||||||
# Text files - use edit (which uses $EDITOR/nvim)
|
"open",
|
||||||
{ mime = "text/*", use = "edit" }, # Changed from "text" to "edit"
|
"reveal",
|
||||||
# { mime = "text/x-tex", use = "edit" }, # Changed from "text" to "edit"
|
] },
|
||||||
{ mime = "application/json", use = "edit" },
|
# Text
|
||||||
{ mime = "application/x-sh", use = "edit" },
|
{ mime = "text/*", use = [
|
||||||
{ mime = "application/x-shellscript", use = "edit" },
|
"edit",
|
||||||
{ mime = "application/javascript", use = "edit" },
|
"reveal",
|
||||||
# Fallback by extension
|
] },
|
||||||
{ url = "*.sh", use = "edit" },
|
# Image
|
||||||
{ url = "*.json", use = "edit" },
|
{ mime = "image/*", use = [
|
||||||
{ url = "*.md", use = "edit" },
|
"open",
|
||||||
{ url = "*.txt", use = "edit" },
|
"reveal",
|
||||||
{ url = "*.css", use = "edit" },
|
] },
|
||||||
{ url = "*.js", use = "edit" },
|
# Media
|
||||||
{ url = "*.toml", use = "edit" },
|
{ mime = "{audio,video}/*", use = [
|
||||||
{ url = "*.yaml", use = "edit" },
|
"play",
|
||||||
{ url = "*.yml", use = "edit" },
|
"reveal",
|
||||||
{ url = "*.tex", use = "edit" }, # Uncommented!
|
] },
|
||||||
{ url = "*.text", use = "edit" },
|
# Code
|
||||||
{ url = "*.epub", use = "edit" },
|
{ mime = "application/{json,ndjson,javascript,wine-extension-ini}", use = [
|
||||||
{ url = "*,kdl", use = "edit" },
|
"edit",
|
||||||
# Media files
|
"reveal",
|
||||||
{ mime = "image/*", use = "open" },
|
] },
|
||||||
{ mime = "video/*", use = "play" },
|
# Archive
|
||||||
{ mime = "audio/*", use = "play" },
|
{ mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", use = [
|
||||||
{ mime = "application/pdf", use = "pdf" },
|
"extract",
|
||||||
|
"reveal",
|
||||||
|
] },
|
||||||
|
# Empty file
|
||||||
|
{ mime = "inode/empty", use = [
|
||||||
|
"edit",
|
||||||
|
"reveal",
|
||||||
|
] },
|
||||||
|
# Virtual file system
|
||||||
|
{ mime = "vfs/{absent,stale}", use = "download" },
|
||||||
|
# Trash
|
||||||
|
{ mime = "trash/**", use = [
|
||||||
|
"open",
|
||||||
|
"trash",
|
||||||
|
] },
|
||||||
# Fallback
|
# Fallback
|
||||||
{ mime = "*", use = "open" },
|
{ url = "*", use = [
|
||||||
|
"open",
|
||||||
|
"reveal",
|
||||||
|
] },
|
||||||
]
|
]
|
||||||
|
|
||||||
[tasks]
|
[tasks]
|
||||||
micro_workers = 10
|
file_workers = 3
|
||||||
macro_workers = 25
|
plugin_workers = 5
|
||||||
bizarre_retry = 5
|
fetch_workers = 5
|
||||||
|
preload_workers = 2
|
||||||
|
process_workers = 5
|
||||||
|
bizarre_retry = 3
|
||||||
|
image_alloc = 536870912 # 512MB
|
||||||
|
image_bound = [10000, 10000]
|
||||||
suppress_preload = false
|
suppress_preload = false
|
||||||
|
|
||||||
[plugin]
|
[plugin]
|
||||||
prepend_fetchers = [
|
fetchers = [
|
||||||
{ group = "mime", id = "mime", url = "*", run = "mime-ext", prio = "high" },
|
# MIME-type
|
||||||
# git plugin
|
{ url = "*/", run = "mime.dir", prio = "high", group = "mime" },
|
||||||
{ group = "mime", id = "git", url = "*", run = "git" },
|
{ url = "local://*", run = "mime.local", prio = "high", group = "mime" },
|
||||||
{ group = "mime", id = "git", url = "*/", run = "git" },
|
{ url = "trash://*", run = "mime.trash", prio = "high", group = "mime" },
|
||||||
# mime plugin
|
{ url = "remote://*", run = "mime.remote", prio = "high", group = "mime" },
|
||||||
{ group = "mime", id = "simple-tag", url = "*", run = "simple-tag" },
|
]
|
||||||
{ group = "mime", id = "simple-tag", url = "*/", run = "simple-tag" },
|
|
||||||
|
### mime-ext ###
|
||||||
|
[[plugin.prepend_fetchers]]
|
||||||
|
url = "local://*"
|
||||||
|
run = "mime-ext.local"
|
||||||
|
prio = "high"
|
||||||
|
group = "mime"
|
||||||
|
|
||||||
|
[[plugin.prepend_fetchers]]
|
||||||
|
url = "remote://*"
|
||||||
|
run = "mime-ext.remote"
|
||||||
|
prio = "high"
|
||||||
|
group = "mime"
|
||||||
|
|
||||||
|
### mediainfo ###
|
||||||
|
prepend_preloaders = [
|
||||||
|
# Replace magick, image, video with mediainfo
|
||||||
|
{ mime = "{audio,video,image}/*", 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)
|
||||||
|
]
|
||||||
|
|
||||||
|
spotters = [
|
||||||
|
# Multi-file
|
||||||
|
{ mime = "multi/*", run = "multi" },
|
||||||
|
# Trash
|
||||||
|
{ mime = "trash/**", run = "trash" },
|
||||||
|
# Folder
|
||||||
|
{ url = "*/", run = "folder" },
|
||||||
|
# Code
|
||||||
|
{ mime = "text/*", run = "code" },
|
||||||
|
{ mime = "application/{mbox,javascript,wine-extension-ini}", run = "code" },
|
||||||
|
# Image
|
||||||
|
{ mime = "image/{avif,hei?,jxl}", run = "magick" },
|
||||||
|
{ mime = "image/svg+xml", run = "svg" },
|
||||||
|
{ mime = "image/*", run = "image" },
|
||||||
|
# Video
|
||||||
|
{ mime = "video/*", run = "video" },
|
||||||
|
# Virtual file system
|
||||||
|
{ mime = "vfs/*", run = "vfs" },
|
||||||
|
# Error
|
||||||
|
{ mime = "null/*", run = "null" },
|
||||||
|
# Fallback
|
||||||
|
{ url = "*", run = "file" },
|
||||||
]
|
]
|
||||||
preloaders = [
|
preloaders = [
|
||||||
# Image
|
# Image
|
||||||
{ mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" },
|
{ mime = "image/{avif,hei?,jxl}", run = "magick" },
|
||||||
|
{ mime = "image/svg+xml", run = "svg" },
|
||||||
{ mime = "image/*", run = "image" },
|
{ mime = "image/*", run = "image" },
|
||||||
# Video
|
# Video
|
||||||
# { mime = "video/*", run = "video" },
|
{ mime = "video/*", run = "video" },
|
||||||
# PDF
|
# PDF
|
||||||
{ mime = "application/pdf", run = "pdf" },
|
{ mime = "application/pdf", run = "pdf" },
|
||||||
# Font
|
# Font
|
||||||
{ mime = "font/*", run = "font" },
|
{ mime = "font/*", run = "font" },
|
||||||
{ mime = "application/ms-opentype", run = "font" },
|
{ mime = "application/ms-opentype", run = "font" },
|
||||||
]
|
# Trash
|
||||||
prepend_preloaders = [
|
{ mime = "trash/**", run = "trash" },
|
||||||
# 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" },
|
|
||||||
]
|
|
||||||
spotters = [
|
|
||||||
# extra-metadata plugin
|
|
||||||
{ url = "*", run = "file-extra-metadata" },
|
|
||||||
]
|
|
||||||
previewers = [
|
|
||||||
{ url = "*/", run = "folder", sync = true },
|
|
||||||
# Code
|
|
||||||
{ url = "text/*", run = "code" },
|
|
||||||
{ url = "*/{xml,javascript,wine-extension-ini}", run = "code" },
|
|
||||||
# JSON
|
|
||||||
{ url = "application/{json,ndjson}", run = "json" },
|
|
||||||
# Image
|
|
||||||
{ url = "image/{avif,hei?,jxl,svg+xml}", run = "magick" },
|
|
||||||
{ url = "image/*", run = "image" },
|
|
||||||
# # Video
|
|
||||||
# { mime = "video/*", run = "video" },
|
|
||||||
# PDF
|
|
||||||
{ url = "application/pdf", run = "pdf" },
|
|
||||||
# extra-metadata plugin
|
|
||||||
{ url = "*", run = "file-extra-metadata" },
|
|
||||||
]
|
]
|
||||||
prepend_previewers = [
|
prepend_previewers = [
|
||||||
# mediainfo MUST come before the catch-all piper rule
|
### rich-preview ###
|
||||||
{ mime = "{audio,video,image}/*", run = "mediainfo" },
|
|
||||||
{ mime = "application/subrip", run = "mediainfo" },
|
|
||||||
{ mime = "application/postscript", run = "mediainfo" },
|
|
||||||
# ouch plugin for archives
|
|
||||||
{ mime = "application/{*zip,tar,bzip2,7z*,rar,xz,zstd,java-archive}", run = "ouch" },
|
|
||||||
# piper for directories
|
|
||||||
{ url = "*/", run = 'piper -- eza -TL=3 --color=always --icons=always --group-directories-first --no-quotes "$1"' },
|
|
||||||
# piper markdown (commented out as you had it)
|
|
||||||
# rich preview plugin
|
|
||||||
{ url = "*.csv", run = "rich-preview" }, # for csv files
|
{ url = "*.csv", run = "rich-preview" }, # for csv files
|
||||||
{ url = "*.md", run = "rich-preview" }, # for markdown (.md) files
|
{ url = "*.md", run = "rich-preview" }, # for markdown (.md) files
|
||||||
{ url = "*.rst", run = "rich-preview" }, # for restructured text (.rst) files
|
{ url = "*.rst", run = "rich-preview" }, # for restructured text (.rst) files
|
||||||
{ url = "*.ipynb", run = "rich-preview" }, # for jupyter notebooks (.ipynb)
|
{ url = "*.ipynb", run = "rich-preview" }, # for jupyter notebooks (.ipynb)
|
||||||
{ url = "*.json", run = "rich-preview" }, # for json (.json) files
|
{ url = "*.json", run = "rich-preview" }, # for json (.json) files
|
||||||
# { url = "*.lang_type", run = "rich-preview"} # for particular language files eg. .py, .go., .lua, etc.
|
# { url = "*.lang_type", run = "rich-preview"} # for particular language files eg. .py, .go., .lua, etc.
|
||||||
|
|
||||||
|
# ### eza-preview ###
|
||||||
|
# { url = "*/", run = "eza-preview" },
|
||||||
|
|
||||||
|
### mediainfo ###
|
||||||
|
# Replace magick, image, video with mediainfo
|
||||||
|
{ mime = "{audio,video,image}/*", 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)
|
||||||
]
|
]
|
||||||
|
previewers = [
|
||||||
|
{ url = "*/", run = "folder" },
|
||||||
|
# Code
|
||||||
|
{ mime = "text/*", run = "code" },
|
||||||
|
{ mime = "application/{mbox,javascript,wine-extension-ini}", run = "code" },
|
||||||
|
# JSON
|
||||||
|
{ mime = "application/{json,ndjson}", run = "json" },
|
||||||
|
# Image
|
||||||
|
{ mime = "image/{avif,hei?,jxl}", run = "magick" },
|
||||||
|
{ mime = "image/svg+xml", run = "svg" },
|
||||||
|
{ mime = "image/*", run = "image" },
|
||||||
|
# Video
|
||||||
|
{ mime = "video/*", run = "video" },
|
||||||
|
# PDF
|
||||||
|
{ mime = "application/pdf", run = "pdf" },
|
||||||
|
# Archive
|
||||||
|
{ mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", run = "archive" },
|
||||||
|
{ mime = "application/{debian*-package,redhat-package-manager,rpm,android.package-archive}", run = "archive" },
|
||||||
|
{ url = "*.{AppImage,appimage}", run = "archive" },
|
||||||
|
# Virtual Disk / Disk Image
|
||||||
|
{ mime = "application/{iso9660-image,qemu-disk,ms-wim,apple-diskimage}", run = "archive" },
|
||||||
|
{ mime = "application/virtualbox-{vhd,vhdx}", run = "archive" },
|
||||||
|
{ url = "*.{img,fat,ext,ext2,ext3,ext4,squashfs,ntfs,hfs,hfsx}", run = "archive" },
|
||||||
|
# Font
|
||||||
|
{ mime = "font/*", run = "font" },
|
||||||
|
{ mime = "application/ms-opentype", run = "font" },
|
||||||
|
# Empty file
|
||||||
|
{ mime = "inode/empty", run = "empty" },
|
||||||
|
# Virtual file system
|
||||||
|
{ mime = "vfs/*", run = "vfs" },
|
||||||
|
# Trash
|
||||||
|
{ mime = "trash/**", run = "trash" },
|
||||||
|
# Error
|
||||||
|
{ mime = "null/*", run = "null" },
|
||||||
|
# Fallback
|
||||||
|
{ url = "*", run = "file" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[input]
|
||||||
|
cursor_blink = false
|
||||||
|
|
||||||
|
# cd
|
||||||
|
cd_title = "Change directory:"
|
||||||
|
cd_origin = "top-center"
|
||||||
|
cd_offset = [0, 2, 50, 3]
|
||||||
|
|
||||||
|
# create
|
||||||
|
create_title = ["Create:", "Create (dir):"]
|
||||||
|
create_origin = "top-center"
|
||||||
|
create_offset = [0, 2, 50, 3]
|
||||||
|
|
||||||
|
# rename
|
||||||
|
rename_title = "Rename:"
|
||||||
|
rename_origin = "hovered"
|
||||||
|
rename_offset = [0, 1, 50, 3]
|
||||||
|
|
||||||
|
# filter
|
||||||
|
filter_title = "Filter:"
|
||||||
|
filter_origin = "top-center"
|
||||||
|
filter_offset = [0, 2, 50, 3]
|
||||||
|
|
||||||
|
# find
|
||||||
|
find_title = ["Find next:", "Find previous:"]
|
||||||
|
find_origin = "top-center"
|
||||||
|
find_offset = [0, 2, 50, 3]
|
||||||
|
|
||||||
|
# search
|
||||||
|
search_title = "Search via {n}:"
|
||||||
|
search_origin = "top-center"
|
||||||
|
search_offset = [0, 2, 50, 3]
|
||||||
|
|
||||||
|
# shell
|
||||||
|
shell_title = ["Shell:", "Shell (block):"]
|
||||||
|
shell_origin = "top-center"
|
||||||
|
shell_offset = [0, 2, 50, 3]
|
||||||
|
|
||||||
|
[confirm]
|
||||||
|
# trash
|
||||||
|
trash_title = "Trash {n} selected file{s}?"
|
||||||
|
trash_origin = "center"
|
||||||
|
trash_offset = [0, 0, 70, 20]
|
||||||
|
|
||||||
|
# delete
|
||||||
|
delete_title = "Permanently delete {n} selected file{s}?"
|
||||||
|
delete_origin = "center"
|
||||||
|
delete_offset = [0, 0, 70, 20]
|
||||||
|
|
||||||
|
# overwrite
|
||||||
|
overwrite_title = "Overwrite file?"
|
||||||
|
overwrite_body = "Will overwrite the following file:"
|
||||||
|
overwrite_origin = "center"
|
||||||
|
overwrite_offset = [0, 0, 50, 15]
|
||||||
|
|
||||||
|
# quit
|
||||||
|
quit_title = "Quit?"
|
||||||
|
quit_body = "There are unfinished tasks, quit anyway?\n(Open task manager with default key 'w')"
|
||||||
|
quit_origin = "center"
|
||||||
|
quit_offset = [0, 0, 50, 15]
|
||||||
|
|
||||||
|
[pick]
|
||||||
|
open_title = "Open with:"
|
||||||
|
open_origin = "hovered"
|
||||||
|
open_offset = [0, 1, 50, 7]
|
||||||
|
|
||||||
|
[which]
|
||||||
|
sort_by = "none"
|
||||||
|
sort_sensitive = false
|
||||||
|
sort_reverse = false
|
||||||
|
sort_translit = false
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,22 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Ben Yip
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Subhaditya Nath
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<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 Dark Flavor for <a href="https://github.com/sxyazi/yazi">Yazi</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
## 👀 Preview
|
||||||
|
|
||||||
|
<img src="https://github.com/bennyyip/i/blob/main/yazi-gruvbox-dark.png?raw=true" width="600" />
|
||||||
|
|
||||||
|
## 🎨 Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ya pkg add bennyyip/gruvbox-dark
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ Usage
|
||||||
|
|
||||||
|
Add the these lines to your `theme.toml` configuration file to use it:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[flavor]
|
||||||
|
dark = "gruvbox-dark"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📜 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.
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
# vim:fileencoding=utf-8:foldmethod=marker
|
||||||
|
|
||||||
|
# : Manager {{{
|
||||||
|
|
||||||
|
[mgr]
|
||||||
|
cwd = { fg = "#83a598" }
|
||||||
|
|
||||||
|
# Find
|
||||||
|
find_keyword = { fg = "#b8bb26", bold = true, italic = true, underline = true }
|
||||||
|
find_position = { fg = "#fe8019", bg = "reset", bold = true, italic = true }
|
||||||
|
|
||||||
|
# Symlink
|
||||||
|
symlink_target = { italic = true }
|
||||||
|
|
||||||
|
# Marker
|
||||||
|
marker_copied = { fg = "#8ec07c", bg = "#8ec07c" }
|
||||||
|
marker_cut = { fg = "#d3869b", bg = "#d3869b" }
|
||||||
|
marker_marked = { fg = "#83a598", bg = "#83a598" }
|
||||||
|
marker_selected = { fg = "#fbf1c7", bg = "#fbf1c7" }
|
||||||
|
|
||||||
|
# Count
|
||||||
|
count_copied = { fg = "#282828", bg = "#8ec07c" }
|
||||||
|
count_cut = { fg = "#282828", bg = "#d3869b" }
|
||||||
|
count_selected = { fg = "#282828", bg = "#fbf1c7" }
|
||||||
|
|
||||||
|
# Border
|
||||||
|
border_symbol = "│"
|
||||||
|
border_style = { fg = "#665c54" }
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Tabs {{{
|
||||||
|
|
||||||
|
[tabs]
|
||||||
|
active = { fg = "#282828", bg = "#a89984" }
|
||||||
|
inactive = { fg = "#a89984", bg = "#504945" }
|
||||||
|
|
||||||
|
# Separator
|
||||||
|
sep_inner = { open = "", close = "" }
|
||||||
|
sep_outer = { open = "", close = "" }
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Mode {{{
|
||||||
|
|
||||||
|
[mode]
|
||||||
|
normal_main = { fg = "#282828", bg = "#a89984", bold = true }
|
||||||
|
normal_alt = { fg = "#a89984", bg = "#504945" }
|
||||||
|
|
||||||
|
# Select mode
|
||||||
|
select_main = { fg = "#282828", bg = "#fe8019", bold = true }
|
||||||
|
select_alt = { fg = "#a89984", bg = "#504945" }
|
||||||
|
|
||||||
|
# Unset mode
|
||||||
|
unset_main = { fg = "#282828", bg = "#b8bb26", bold = true }
|
||||||
|
unset_alt = { fg = "#a89984", bg = "#504945" }
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Indicator of hovered file {{{
|
||||||
|
|
||||||
|
[indicator]
|
||||||
|
parent = { reversed = true }
|
||||||
|
current = { reversed = true }
|
||||||
|
preview = { underline = true }
|
||||||
|
padding = { open = "█", close = "█" }
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Status {{{
|
||||||
|
|
||||||
|
[status]
|
||||||
|
overall = { }
|
||||||
|
sep_left = { open = "\ue0be", close = "\ue0b8" }
|
||||||
|
sep_right = { open = "\ue0be", close = "\ue0b8" }
|
||||||
|
# Permissions
|
||||||
|
perm_sep = { fg = "#665c54" }
|
||||||
|
perm_type = { fg = "#504945" }
|
||||||
|
perm_read = { fg = "#b8bb26" }
|
||||||
|
perm_write = { fg = "#fb4934" }
|
||||||
|
perm_exec = { fg = "#b8bb26" }
|
||||||
|
|
||||||
|
# Progress
|
||||||
|
progress_label = { fg = "#ebdbb2", bold = true }
|
||||||
|
progress_normal = { fg = "#504945", bg = "#3c3836" }
|
||||||
|
progress_error = { fg = "#fb4934", bg = "#3c3836" }
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Which {{{
|
||||||
|
|
||||||
|
[which]
|
||||||
|
cols = 3
|
||||||
|
mask = { bg = "#3c3836" }
|
||||||
|
cand = { fg = "#83a598" }
|
||||||
|
rest = { fg = "#928374" }
|
||||||
|
desc = { fg = "#fe8019" }
|
||||||
|
separator = " "
|
||||||
|
separator_style = { fg = "#504945" }
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Confirmation {{{
|
||||||
|
|
||||||
|
[confirm]
|
||||||
|
border = { fg = "#a89984" }
|
||||||
|
title = { fg = "#83a598" }
|
||||||
|
body = { fg = "#fbf1c7" }
|
||||||
|
list = { fg = "#ebdbb2" }
|
||||||
|
btn_yes = { reversed = true, fg = "#ebdbb2" }
|
||||||
|
btn_no = {}
|
||||||
|
btn_labels = [ " [Y]es ", " (N)o " ]
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Spotter {{{
|
||||||
|
|
||||||
|
[spot]
|
||||||
|
border = { fg = "#83a598" }
|
||||||
|
title = { fg = "#83a598" }
|
||||||
|
|
||||||
|
# Table
|
||||||
|
tbl_col = { fg = "#83a598" }
|
||||||
|
tbl_cell = { fg = "#fabd2f", reversed = true }
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Notification {{{
|
||||||
|
|
||||||
|
[notify]
|
||||||
|
title_info = { fg = "#8ec07c" }
|
||||||
|
title_warn = { fg = "#fbf1c7" }
|
||||||
|
title_error = { fg = "#d3869b" }
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Picker {{{
|
||||||
|
|
||||||
|
[pick]
|
||||||
|
border = { fg = "#458588" }
|
||||||
|
active = { fg = "#d3869b", bold = true }
|
||||||
|
inactive = {}
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Input {{{
|
||||||
|
|
||||||
|
[input]
|
||||||
|
border = { fg = "#ebdbb2" }
|
||||||
|
title = {}
|
||||||
|
value = {}
|
||||||
|
selected = { reversed = true }
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Completion {{{
|
||||||
|
|
||||||
|
[cmp]
|
||||||
|
border = { fg = "#a89984" }
|
||||||
|
active = { reversed = true, fg = "#83a598" }
|
||||||
|
inactive = { fg = "#ebdbb2" }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# : Task manager {{{
|
||||||
|
|
||||||
|
[tasks]
|
||||||
|
border = { fg = "#504945" }
|
||||||
|
title = {}
|
||||||
|
hovered = { underline = true }
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Help {{{
|
||||||
|
|
||||||
|
[help]
|
||||||
|
on = { fg = "#83a598" }
|
||||||
|
run = { fg = "#d3869b" }
|
||||||
|
desc = {}
|
||||||
|
hovered = { reversed = true, bold = true }
|
||||||
|
footer = { fg = "#3c3836", bg = "#a89984" }
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : File-specific styles {{{
|
||||||
|
|
||||||
|
[filetype]
|
||||||
|
rules = [
|
||||||
|
# Images
|
||||||
|
{ mime = "image/*", fg = "#d3869b" },
|
||||||
|
|
||||||
|
# Media
|
||||||
|
{ mime = "{audio,video}/*", fg = "#fabd2f" },
|
||||||
|
|
||||||
|
# Archives
|
||||||
|
{ mime = "application/*zip", fg = "#fb4934" },
|
||||||
|
{ mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", fg = "#fb4934" },
|
||||||
|
|
||||||
|
# Documents
|
||||||
|
{ mime = "application/{pdf,doc,rtf,vnd.*}", fg = "#689d6a" },
|
||||||
|
|
||||||
|
# Fallback
|
||||||
|
{ url = "*", fg = "#ebdbb2" },
|
||||||
|
{ url = "*/", fg = "#83a598" },
|
||||||
|
]
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
placeholder
|
||||||
File diff suppressed because it is too large.
Load diff
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,358 @@
|
|||||||
|
-- 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" },
|
||||||
|
-- }
|
||||||
|
|
||||||
|
-- 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" },
|
||||||
|
-- }
|
||||||
|
|
||||||
|
require("whoosh"):setup({
|
||||||
|
-- Configuration bookmarks (cannot be deleted through plugin)
|
||||||
|
bookmarks = bookmarks,
|
||||||
|
|
||||||
|
-- Notification settings
|
||||||
|
jump_notify = false,
|
||||||
|
|
||||||
|
-- Key generation for auto-assigning bookmark keys
|
||||||
|
keys = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||||
|
|
||||||
|
-- Configure the built-in menu action hotkeys
|
||||||
|
-- false - hide menu item
|
||||||
|
special_keys = {
|
||||||
|
create_temp = "<Enter>", -- Create a temporary bookmark from the menu
|
||||||
|
fuzzy_search = "<Space>", -- Launch fuzzy search (fzf)
|
||||||
|
history = "<Tab>", -- Open directory history
|
||||||
|
previous_dir = "<Backspace>", -- Jump back to the previous directory
|
||||||
|
},
|
||||||
|
|
||||||
|
-- File path for storing user bookmarks
|
||||||
|
bookmarks_path = (
|
||||||
|
ya.target_family() == "windows" and os.getenv("APPDATA") .. "\\yazi\\config\\plugins\\whoosh.yazi\\bookmarks"
|
||||||
|
) or (os.getenv("HOME") .. "/.config/yazi/plugins/whoosh.yazi/bookmarks"),
|
||||||
|
|
||||||
|
-- Replace home directory with "~"
|
||||||
|
home_alias_enabled = true, -- Toggle home aliasing in displays
|
||||||
|
|
||||||
|
-- Path truncation in navigation menu
|
||||||
|
path_truncate_enabled = false, -- Enable/disable path truncation
|
||||||
|
path_max_depth = 3, -- Maximum path depth before truncation
|
||||||
|
|
||||||
|
-- Path truncation in fuzzy search (fzf)
|
||||||
|
fzf_path_truncate_enabled = false, -- Enable/disable path truncation in fzf
|
||||||
|
fzf_path_max_depth = 5, -- Maximum path depth before truncation in fzf
|
||||||
|
|
||||||
|
-- Long folder name truncation
|
||||||
|
path_truncate_long_names_enabled = false, -- Enable in navigation menu
|
||||||
|
fzf_path_truncate_long_names_enabled = false, -- Enable in fzf
|
||||||
|
path_max_folder_name_length = 20, -- Max length in navigation menu
|
||||||
|
fzf_path_max_folder_name_length = 20, -- Max length in fzf
|
||||||
|
|
||||||
|
-- History directory settings
|
||||||
|
history_size = 10, -- Number of directories in history (default 10)
|
||||||
|
history_fzf_path_truncate_enabled = false, -- Enable/disable path truncation by depth for history
|
||||||
|
history_fzf_path_max_depth = 5, -- Maximum path depth before truncation for history (default 5)
|
||||||
|
history_fzf_path_truncate_long_names_enabled = false, -- Enable/disable long folder name truncation for history
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
-- 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",
|
||||||
|
-- })
|
||||||
|
|
||||||
|
-- default config
|
||||||
|
require("fchar"):setup({
|
||||||
|
-- if true: f -> file, File, FILE
|
||||||
|
insensitive = true,
|
||||||
|
-- if true: f -> file, .file, @file, #file, ...file
|
||||||
|
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 = {},
|
||||||
|
})
|
||||||
|
|
||||||
|
-- aliases for German
|
||||||
|
require("fchar"):setup({
|
||||||
|
aliases = {
|
||||||
|
a = "ä",
|
||||||
|
o = "ö",
|
||||||
|
u = "ü",
|
||||||
|
s = "ß",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
-- you may want to turn off the search regex from
|
||||||
|
-- showing up in the header by doing this
|
||||||
|
function Header:flags()
|
||||||
|
local cwd = self._current.cwd
|
||||||
|
local filter = self._current.files.filter
|
||||||
|
|
||||||
|
local t = {}
|
||||||
|
if cwd.is_search then
|
||||||
|
t[#t + 1] = string.format("search: %s", cwd.domain)
|
||||||
|
end
|
||||||
|
if filter then
|
||||||
|
t[#t + 1] = string.format("filter: %s", filter)
|
||||||
|
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
|
||||||
|
-- },
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,536 @@
|
|||||||
|
[mgr]
|
||||||
|
prepend_keymap = [
|
||||||
|
|
||||||
|
|
||||||
|
### smart-paste plugin ###
|
||||||
|
{ on = "P", run = "plugin smart-paste", desc = "Paste into the hovered directory or CWD" },
|
||||||
|
|
||||||
|
### open-with-cmd plugin ###
|
||||||
|
{ on = "o", run = "plugin open-with-cmd -- block", desc = "Open with command in the terminal" },
|
||||||
|
{ on = "O", run = "plugin open-with-cmd", desc = "Open with command" },
|
||||||
|
|
||||||
|
### bypass plugin ###
|
||||||
|
{ on = [
|
||||||
|
"L",
|
||||||
|
], run = "plugin bypass", desc = "Recursively enter child directory, skipping children with only a single subdirectory" },
|
||||||
|
{ on = [
|
||||||
|
"H",
|
||||||
|
], run = "plugin bypass reverse", desc = "Recursively enter parent directory, skipping parents with only a single subdirectory" },
|
||||||
|
|
||||||
|
### recycle-bin plugin
|
||||||
|
{ on = [
|
||||||
|
"R",
|
||||||
|
"b",
|
||||||
|
], run = "plugin recycle-bin", desc = "Open Recycle Bin menu" },
|
||||||
|
|
||||||
|
### dupes plugin ###
|
||||||
|
{ on = [
|
||||||
|
"<A-J>",
|
||||||
|
"i",
|
||||||
|
], run = "plugin dupes interactive", desc = "Run dupes interactive" },
|
||||||
|
{ on = [
|
||||||
|
"<A-J>",
|
||||||
|
"o",
|
||||||
|
], run = "plugin dupes override", desc = "Run dupes override" },
|
||||||
|
{ on = [
|
||||||
|
"<A-J>",
|
||||||
|
"d",
|
||||||
|
], run = "plugin dupes dry", desc = "Run dupes dry" },
|
||||||
|
{ on = [
|
||||||
|
"<A-J>",
|
||||||
|
"a",
|
||||||
|
], run = "plugin dupes apply", desc = "Run dupes apply" },
|
||||||
|
{ on = [
|
||||||
|
"<A-J>",
|
||||||
|
"c",
|
||||||
|
], run = "plugin dupes custom", desc = "Run dupes custom" },
|
||||||
|
|
||||||
|
### toggle-pane plugin ###
|
||||||
|
{ on = "W", run = "plugin toggle-pane max-preview", desc = "Maximize or restore the preview pane" },
|
||||||
|
{ on = "E", run = "plugin toggle-pane min-preview", desc = "Minimize or restore the preview pane" },
|
||||||
|
|
||||||
|
### what size plugin ###
|
||||||
|
{ on = "i", run = "plugin what-size", desc = "Calc size of selection or cwd" },
|
||||||
|
|
||||||
|
### rsync plugin ###
|
||||||
|
{ on = [
|
||||||
|
"R",
|
||||||
|
], run = "plugin rsync", desc = "Copy files using rsync" },
|
||||||
|
|
||||||
|
### yafg plugin ###
|
||||||
|
{ on = [
|
||||||
|
"F",
|
||||||
|
"G",
|
||||||
|
], run = "plugin yafg" },
|
||||||
|
|
||||||
|
###### GIT ######
|
||||||
|
### open-git-remote plugin ###
|
||||||
|
{ on = [
|
||||||
|
"g",
|
||||||
|
"l",
|
||||||
|
], run = "plugin open-git-remote", desc = "open git remote url" },
|
||||||
|
### lazygit plugin ###
|
||||||
|
{ on = [
|
||||||
|
"g",
|
||||||
|
"i",
|
||||||
|
], run = "plugin lazygit", desc = "run lazygit" },
|
||||||
|
### vcs-files plugin ###
|
||||||
|
{ on = [
|
||||||
|
"g",
|
||||||
|
"c",
|
||||||
|
], run = "plugin vcs-files", desc = "Show Git file changes" },
|
||||||
|
|
||||||
|
### mount plugin ###
|
||||||
|
{ on = "M", run = "plugin mount" },
|
||||||
|
|
||||||
|
# ### smart-enter plugin ###
|
||||||
|
{ on = "l", run = "plugin smart-enter", desc = "Enter the child directory, or open the file" },
|
||||||
|
|
||||||
|
### f-char plugin ###
|
||||||
|
{ on = "f", run = "plugin fchar", desc = "Jump to char" },
|
||||||
|
# ### jump-to-char plugin ###
|
||||||
|
# { on = "f", run = "plugin jump-to-char", desc = "Jump to char" },
|
||||||
|
|
||||||
|
### whoosh plugin ###
|
||||||
|
{ on = "[", run = "plugin whoosh jump_by_key", desc = "Jump bookmark by key" },
|
||||||
|
# Direct fuzzy search access
|
||||||
|
{ on = "}", run = "plugin whoosh jump_by_fzf", desc = "Direct fuzzy search for bookmarks" },
|
||||||
|
# Basic bookmark operations
|
||||||
|
{ on = [
|
||||||
|
"]",
|
||||||
|
"a",
|
||||||
|
], run = "plugin whoosh save", desc = "Add bookmark (hovered file/directory)" },
|
||||||
|
{ on = [
|
||||||
|
"]",
|
||||||
|
"A",
|
||||||
|
], run = "plugin whoosh save_cwd", desc = "Add bookmark (current directory)" },
|
||||||
|
# Temporary bookmarks
|
||||||
|
{ on = [
|
||||||
|
"]",
|
||||||
|
"t",
|
||||||
|
], run = "plugin whoosh save_temp", desc = "Add temporary bookmark (hovered file/directory)" },
|
||||||
|
{ on = [
|
||||||
|
"]",
|
||||||
|
"T",
|
||||||
|
], run = "plugin whoosh save_cwd_temp", desc = "Add temporary bookmark (current directory)" },
|
||||||
|
# Jump to bookmarks
|
||||||
|
{ on = "<A-k>", run = "plugin whoosh jump_key_k", desc = "Jump directly to bookmark with key k" },
|
||||||
|
{ on = [
|
||||||
|
"]",
|
||||||
|
"f",
|
||||||
|
], run = "plugin whoosh jump_by_fzf", desc = "Jump bookmark by fzf" },
|
||||||
|
# Delete bookmarks
|
||||||
|
{ on = [
|
||||||
|
"]",
|
||||||
|
"d",
|
||||||
|
], run = "plugin whoosh delete_by_key", desc = "Delete bookmark by key" },
|
||||||
|
{ on = [
|
||||||
|
"]",
|
||||||
|
"D",
|
||||||
|
], run = "plugin whoosh delete_by_fzf", desc = "Delete bookmarks by fzf (use TAB to select multiple)" },
|
||||||
|
{ on = [
|
||||||
|
"]",
|
||||||
|
"C",
|
||||||
|
], run = "plugin whoosh delete_all", desc = "Delete all user bookmarks" },
|
||||||
|
# Rename Bookmarks
|
||||||
|
{ on = [
|
||||||
|
"]",
|
||||||
|
"r",
|
||||||
|
], run = "plugin whoosh rename_by_key", desc = "Rename bookmark by key" },
|
||||||
|
{ on = [
|
||||||
|
"]",
|
||||||
|
"R",
|
||||||
|
], run = "plugin whoosh rename_by_fzf", desc = "Rename bookmark by fzf" },
|
||||||
|
# Navigation
|
||||||
|
{ on = [
|
||||||
|
"<Esc>",
|
||||||
|
], run = "escape", desc = "Exit visual mode, clear selected, or cancel search" },
|
||||||
|
# { on = [
|
||||||
|
# "q",
|
||||||
|
# "q",
|
||||||
|
# ], run = "quit", desc = "Exit the process" },
|
||||||
|
{ on = [
|
||||||
|
"<C-q>",
|
||||||
|
], run = "close", desc = "Close the current tab, or quit if it is last tab" },
|
||||||
|
|
||||||
|
### projects plugin ###
|
||||||
|
{ on = [
|
||||||
|
"P",
|
||||||
|
"p",
|
||||||
|
], run = "plugin projects 'load SomeProject'", desc = "Load the 'SomeProject' project" },
|
||||||
|
{ on = [
|
||||||
|
"P",
|
||||||
|
"s",
|
||||||
|
], run = "plugin projects save", desc = "Save current project" },
|
||||||
|
{ on = [
|
||||||
|
"P",
|
||||||
|
"l",
|
||||||
|
], run = "plugin projects load", desc = "Load project" },
|
||||||
|
{ on = [
|
||||||
|
"P",
|
||||||
|
"P",
|
||||||
|
], run = "plugin projects load_last", desc = "Load last project" },
|
||||||
|
{ on = [
|
||||||
|
"P",
|
||||||
|
"d",
|
||||||
|
], run = "plugin projects delete", desc = "Delete project" },
|
||||||
|
{ on = [
|
||||||
|
"P",
|
||||||
|
"D",
|
||||||
|
], run = "plugin projects delete_all", desc = "Delete all projects" },
|
||||||
|
{ on = [
|
||||||
|
"P",
|
||||||
|
"m",
|
||||||
|
], run = "plugin projects 'merge current'", desc = "Merge current tab to other projects" },
|
||||||
|
{ on = [
|
||||||
|
"P",
|
||||||
|
"M",
|
||||||
|
], run = "plugin projects 'merge all'", desc = "Merge current project to other projects" },
|
||||||
|
|
||||||
|
### fazif plugin ###
|
||||||
|
{ on = [
|
||||||
|
"b",
|
||||||
|
"d",
|
||||||
|
], run = "plugin fazif faziffd", desc = "Find files/directories with fd and fzf" },
|
||||||
|
{ on = [
|
||||||
|
"b",
|
||||||
|
"D",
|
||||||
|
], run = "plugin fazif faziffdr", desc = "Find files/directories with fd and fzf" },
|
||||||
|
{ on = [
|
||||||
|
"b",
|
||||||
|
"r",
|
||||||
|
], run = "plugin fazif fazifrg", desc = "Find content in files with ripgrep and fzf" },
|
||||||
|
{ on = [
|
||||||
|
"b",
|
||||||
|
"R",
|
||||||
|
], run = "plugin fazif fazifrga", desc = "Find content in documents with ripgrep-all and fzf" },
|
||||||
|
|
||||||
|
### Tagging plugin ###
|
||||||
|
# TOGGLE TAG(S)
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"t",
|
||||||
|
"k",
|
||||||
|
], run = "plugin simple-tag -- toggle-tag", desc = "Toggle a tag (press any key)" },
|
||||||
|
{ on = [
|
||||||
|
"'",
|
||||||
|
], run = "plugin simple-tag -- toggle-tag --keys=!", desc = "Toggle tag(s) with fixed tag key(s) = (! and 1 and q)" },
|
||||||
|
{ on = [
|
||||||
|
"-",
|
||||||
|
], run = "plugin simple-tag -- toggle-tag --keys=1", desc = "Toggle tag(s) with fixed tag key(s) = (! and 1 and q)" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"t",
|
||||||
|
"i",
|
||||||
|
], run = "plugin simple-tag -- toggle-tag --input", desc = "Toggle tag(s) with value from (input box)" },
|
||||||
|
# ADD TAG(S)
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"a",
|
||||||
|
"k",
|
||||||
|
], run = "plugin simple-tag -- add-tag", desc = "Add a tag (press any key)" },
|
||||||
|
# { on = [ "t", "a", "f" ], run = "plugin simple-tag -- add-tag --keys=!1q", desc = "Add tag(s) with fixed tag keys = (! and 1 and q)" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"a",
|
||||||
|
"i",
|
||||||
|
], run = "plugin simple-tag -- add-tag --input", desc = "Add tag(s) with value from (input box)" },
|
||||||
|
# REMOVE/DELETE TAG(S)
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"d",
|
||||||
|
"k",
|
||||||
|
], run = "plugin simple-tag -- remove-tag", desc = "Remove a tag (press any key)" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"d",
|
||||||
|
"f",
|
||||||
|
], run = "plugin simple-tag -- remove-tag --keys=!1q", desc = "Remove tag(s) with fixed tag keys = (! and 1 and q)" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"d",
|
||||||
|
"i",
|
||||||
|
], run = "plugin simple-tag -- remove-tag --input", desc = "Remove tag(s) with value from (input box)" },
|
||||||
|
# REPLACE ALL OLD TAG(S) WITH NEW TAG(S)
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"r",
|
||||||
|
"k",
|
||||||
|
], run = "plugin simple-tag -- replace-tag", desc = "Replace with a new tag (press any key)" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"r",
|
||||||
|
"f",
|
||||||
|
], run = "plugin simple-tag -- replace-tag --keys=!1q", desc = "Replace tag(s) with fixed tag keys = (! and 1 and q)" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"r",
|
||||||
|
"i",
|
||||||
|
], run = "plugin simple-tag -- replace-tag --input", desc = "Replace tag(s) with value from (input box)" },
|
||||||
|
# EDIT TAG(S)
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"e",
|
||||||
|
], run = "plugin simple-tag -- edit-tag ", desc = "Edit tag(s) (input box)" },
|
||||||
|
# CLEAR TAG(S)
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"c",
|
||||||
|
], run = "plugin simple-tag -- clear", desc = "Clear all tags from selected or hovered files" },
|
||||||
|
# CHANGE UI
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"u",
|
||||||
|
"s",
|
||||||
|
], run = "plugin simple-tag -- toggle-ui", desc = "Toggle tag indicator (icon > tag key > hidden)" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"u",
|
||||||
|
"h",
|
||||||
|
], run = "plugin simple-tag -- toggle-ui --mode=hidden", desc = "Hide all tags indicator" },
|
||||||
|
# FILTER FILES/FOLDERS BY TAGS:
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"f",
|
||||||
|
], run = "plugin simple-tag -- filter", desc = "Filter files/folders by a tag (press any key)" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"F",
|
||||||
|
], run = "plugin simple-tag -- filter --input", desc = "Filter files/folders by tag(s) (input box)" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"F",
|
||||||
|
], run = "plugin simple-tag -- filter --input --mode=or", desc = "Filter files/folders by contain tags (input box)" },
|
||||||
|
# VISUAL SELECT FILES/FOLDERS BY TAGS:
|
||||||
|
# Available selection actions:
|
||||||
|
# replace → Replaces the current selection list with files/folders that have the selected tag.
|
||||||
|
# unite → Combines the currently selected files/folders with those that have the selected tag.
|
||||||
|
# intersect → Keeps only the files/folders that are present in both the current selection and the tagged items.
|
||||||
|
# subtract → Deselects files/folders that have the selected tag from the current selection.
|
||||||
|
# exclude → Combines the currently selected files/folders with the tagged items, then deselects any overlapping items between the current selection and the tagged items.
|
||||||
|
# undo → Undos or redoes the last selection action.
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"t",
|
||||||
|
], run = "plugin simple-tag -- toggle-select", desc = "Select a selection action then select a tag key (toggle-select)" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"t",
|
||||||
|
], run = "plugin simple-tag -- toggle-select --keys=!1q", desc = "" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"r",
|
||||||
|
], run = "plugin simple-tag -- replace-select", desc = "replace-select" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"u",
|
||||||
|
], run = "plugin simple-tag -- unite-select", desc = "unite-select" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"i",
|
||||||
|
], run = "plugin simple-tag -- intersect-select", desc = "intersect-select" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"s",
|
||||||
|
], run = "plugin simple-tag -- subtract-select", desc = "subtract-select" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"e",
|
||||||
|
], run = "plugin simple-tag -- exclude-select", desc = "exclude-select" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"e",
|
||||||
|
], run = "plugin simple-tag -- replace-select --keys=!1q", desc = "Replaces the current selection list with files/folders that have (! and 1 and q) tag(s)" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"R",
|
||||||
|
], run = "plugin simple-tag -- replace-select --input", desc = "replace-select --input" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"U",
|
||||||
|
], run = "plugin simple-tag -- unite-select --input", desc = "unite-select --input" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"I",
|
||||||
|
], run = "plugin simple-tag -- intersect-select --input", desc = "intersect-select --input" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"S",
|
||||||
|
], run = "plugin simple-tag -- subtract-select --input", desc = "subtract-select --input" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"E",
|
||||||
|
], run = "plugin simple-tag -- exclude-select --input", desc = "exclude-select --input" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"R",
|
||||||
|
], run = "plugin simple-tag -- replace-select --input --mode=or", desc = "replace-select --input --mode=or" },
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"R",
|
||||||
|
], run = "plugin simple-tag -- replace-select --keys=!1q --mode=or", desc = "replace-select --keys=!1q --mode=or" },
|
||||||
|
# Undo/Redo selection (only works after using 5 modes above)
|
||||||
|
{ on = [
|
||||||
|
"t",
|
||||||
|
"s",
|
||||||
|
"u",
|
||||||
|
], run = "plugin simple-tag -- undo-select", desc = "Undos/Redos the last selection action" },
|
||||||
|
|
||||||
|
# Directory navigation
|
||||||
|
{ on = [
|
||||||
|
"k",
|
||||||
|
], run = "arrow -1", desc = "Move cursor up" },
|
||||||
|
{ on = [
|
||||||
|
"j",
|
||||||
|
], run = "arrow 1", desc = "Move cursor down" },
|
||||||
|
{ on = [
|
||||||
|
"h",
|
||||||
|
], run = "leave", desc = "Go back to parent directory" },
|
||||||
|
{ on = [
|
||||||
|
"l",
|
||||||
|
], run = "enter", desc = "Enter directory" },
|
||||||
|
# File operations
|
||||||
|
{ on = [
|
||||||
|
"<Enter>",
|
||||||
|
], run = "open", desc = "Open selected files" },
|
||||||
|
{ on = [
|
||||||
|
"y",
|
||||||
|
], run = "yank", desc = "Yank selected files (copy)" },
|
||||||
|
{ on = [
|
||||||
|
"x",
|
||||||
|
], run = "yank --cut", desc = "Yank selected files (cut)" },
|
||||||
|
{ on = [
|
||||||
|
"p",
|
||||||
|
], run = "paste", desc = "Paste yanked files" },
|
||||||
|
{ on = [
|
||||||
|
"d",
|
||||||
|
], run = "remove", desc = "Trash selected files" },
|
||||||
|
{ on = [
|
||||||
|
"D",
|
||||||
|
], run = "remove --permanently", desc = "Permanently delete selected files" },
|
||||||
|
{ on = [
|
||||||
|
"a",
|
||||||
|
], run = "create", desc = "Create a file or directory" },
|
||||||
|
{ on = [
|
||||||
|
"r",
|
||||||
|
], run = "rename --cursor=before_ext", desc = "Rename selected file" },
|
||||||
|
# Selection
|
||||||
|
{ on = [
|
||||||
|
"e",
|
||||||
|
], run = [
|
||||||
|
"select --state=none",
|
||||||
|
"arrow 1",
|
||||||
|
], desc = "Toggle selection" },
|
||||||
|
{ on = [
|
||||||
|
"v",
|
||||||
|
], run = "visual_mode", desc = "Enter visual mode" },
|
||||||
|
{ on = [
|
||||||
|
"V",
|
||||||
|
], run = "visual_mode --unset", desc = "Enter visual mode (unset)" },
|
||||||
|
# Search
|
||||||
|
{ on = [
|
||||||
|
"/",
|
||||||
|
], run = "find --smart", desc = "Find file" },
|
||||||
|
{ on = [
|
||||||
|
"n",
|
||||||
|
], run = "find_arrow", desc = "Go to next found" },
|
||||||
|
{ on = [
|
||||||
|
"N",
|
||||||
|
], run = "find_arrow --previous", desc = "Go to previous found" },
|
||||||
|
# Tabs
|
||||||
|
{ on = [
|
||||||
|
"T",
|
||||||
|
], run = "tab_create --current", desc = "Create new tab" },
|
||||||
|
{ on = [
|
||||||
|
"1",
|
||||||
|
], run = "tab_switch 0", desc = "Switch to tab 1" },
|
||||||
|
{ on = [
|
||||||
|
"2",
|
||||||
|
], run = "tab_switch 1", desc = "Switch to tab 2" },
|
||||||
|
{ on = [
|
||||||
|
"3",
|
||||||
|
], run = "tab_switch 2", desc = "Switch to tab 3" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[tasks]
|
||||||
|
prepend_keymap = [
|
||||||
|
{ on = [
|
||||||
|
"<Esc>",
|
||||||
|
], run = "close", desc = "Close task manager" },
|
||||||
|
{ on = [
|
||||||
|
"k",
|
||||||
|
], run = "arrow -1", desc = "Move cursor up" },
|
||||||
|
{ on = [
|
||||||
|
"j",
|
||||||
|
], run = "arrow 1", desc = "Move cursor down" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[select]
|
||||||
|
prepend_keymap = [
|
||||||
|
{ on = [
|
||||||
|
"<Esc>",
|
||||||
|
], run = "close", desc = "Cancel selection" },
|
||||||
|
{ on = [
|
||||||
|
"<Enter>",
|
||||||
|
], run = "close --submit", desc = "Submit selection" },
|
||||||
|
{ on = [
|
||||||
|
"k",
|
||||||
|
], run = "arrow -1", desc = "Move cursor up" },
|
||||||
|
{ on = [
|
||||||
|
"j",
|
||||||
|
], run = "arrow 1", desc = "Move cursor down" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[input]
|
||||||
|
prepend_keymap = [
|
||||||
|
{ on = [
|
||||||
|
"<Esc>",
|
||||||
|
], run = "close", desc = "Cancel input" },
|
||||||
|
{ on = [
|
||||||
|
"<Enter>",
|
||||||
|
], run = "close --submit", desc = "Submit input" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[completion]
|
||||||
|
prepend_keymap = [
|
||||||
|
{ on = [
|
||||||
|
"<Tab>",
|
||||||
|
], run = "close --submit", desc = "Submit completion" },
|
||||||
|
{ on = [
|
||||||
|
"k",
|
||||||
|
], run = "arrow -1", desc = "Move cursor up" },
|
||||||
|
{ on = [
|
||||||
|
"j",
|
||||||
|
], run = "arrow 1", desc = "Move cursor down" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[help]
|
||||||
|
prepend_keymap = [
|
||||||
|
{ on = [
|
||||||
|
"<Esc>",
|
||||||
|
], run = "escape", desc = "Clear filter or exit help" },
|
||||||
|
{ on = [
|
||||||
|
"q",
|
||||||
|
], run = "close", desc = "Exit help" },
|
||||||
|
{ on = [
|
||||||
|
"k",
|
||||||
|
], run = "arrow -1", desc = "Move cursor up" },
|
||||||
|
{ on = [
|
||||||
|
"j",
|
||||||
|
], run = "arrow 1", desc = "Move cursor down" },
|
||||||
|
]
|
||||||
File renamed without changes.
File renamed without changes.
Loaded 100 of 226 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user