diff --git a/herdr/.config/herdr/sessions/home/session-history.json b/herdr/.config/herdr/sessions/home/session-history.json new file mode 100644 index 0000000..8e6b75a --- /dev/null +++ b/herdr/.config/herdr/sessions/home/session-history.json @@ -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;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....local/share/nvim/lazy/noic\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 97 \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_percent\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....local/share/nvim/lazy/noic\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 98 \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;216merror\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[1m\u001b[38;2;246;193;119mnil\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....local/share/nvim/lazy/noic\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 99 \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;216mis_virtual\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[38;2;234;154;151mfalse\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...local/share/nvim/lazy/noice\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 100 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m \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;110;106;134m \u001b[0m\u001b[1m\u001b[38;2;235;111;146mC: in function 'xpcall'\u001b[0m\u001b[38;2;110;106;134m \u001b[0m \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 101 \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;244mh\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mfile\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...\u001b[0m\u001b[38;2;110;106;134m \u001b[0m \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 102 \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;244mfile_url\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mh\u001b[0m\u001b[38;2;144;140;170m.\u001b[0m\u001b[3m\u001b[38;2;156;207;216murl \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...local/share/nvim/lazy/noice\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 103 \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;244mis_virtual\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mfile_url\u001b[0m\u001b[38;2;144;140;170m.\u001b[0m\u001b[3m\u001b[38;2;35;33;54m\u001b[48;2;246;193;119mscheme\u001b[0m \u001b[0m\u001b[38;2;144;140;170mand\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 'xpcall'\u001b[0m\u001b[38;2;110;106;134m \u001b[0m \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\u001b[38;2;156;207;216m\u001b[48;2;47;50;70m \u001b[0m\r\n 104 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mfile_url\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mis_virtual\u001b[0m \u001b[0m\u001b[38;2;144;140;170mand\u001b[0m \u001b[0m\u001b[38;2;235;111;146m(\u001b[0m\u001b[3m\u001b[38;2;224;222;244mfile\u001b[0m\u001b[38;2;144;140;170m.\u001b[0m\u001b[3m\u001b[38;2;156;207;216mpath\u001b[0m \u001b[0m\u001b[38;2;144;140;170mor\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...local/share/nvim/lazy/noice\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\u001b[1m\u001b[3m\u001b[38;2;234;154;151mn\u001b[0m \u001b[0m\u001b[1m\u001b[3m\u001b[38;2;234;154;151mg\u001b[0m\u001b[38;2;62;143;176m(\u001b[0m\u001b[3m\u001b[38;2;224;222;244mfile_url\u001b[0m\u001b[38;2;144;140;170m.\u001b[0m\u001b[3m\u001b[38;2;156;207;216mpath\u001b[0m\u001b[38;2;62;143;176m)\u001b[0m\u001b[38;2;246;193;119m)\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;110;106;134m \u001b[0m\u001b[1m\u001b[38;2;235;111;146m...local/share/nvim/lazy/noice\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 105 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ \u001b[0m\u001b[38;2;144;140;170mor\u001b[0m \u001b[0m\u001b[38;2;235;111;146m(\u001b[0m\u001b[3m\u001b[38;2;224;222;244mfile_url\u001b[0m\u001b[38;2;144;140;170m.\u001b[0m\u001b[3m\u001b[38;2;156;207;216mpath\u001b[0m \u001b[0m\u001b[38;2;144;140;170mor\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mfile_url\u001b[0m\u001b[38;2;144;140;170m.\u001b[0m\u001b[3m\u001b[38;2;156;207;216murl\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;110;106;134m \u001b[0m\u001b[1m\u001b[38;2;235;111;146m.../share/nvim/lazy/noice.nvim\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 106 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m \u001b[0m\u001b[38;2;62;143;176mif\u001b[0m \u001b[0m\u001b[38;2;144;140;170mnot\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mh\u001b[0m \u001b[0m\u001b[38;2;144;140;170mor\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mya\u001b[0m\u001b[38;2;144;140;170m.\u001b[0m\u001b[3m\u001b[38;2;234;154;151mtarget_family\u001b[0m\u001b[38;2;235;111;146m()\u001b[0m \u001b[0m\u001b[38;2;144;140;170m~=\u001b[0m \u001b[0m\u001b[38;2;246;193;119m\"unix\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 'xpcall'\u001b[0m\u001b[38;2;110;106;134m \u001b[0m \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 107 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ \u001b[0m\u001b[38;2;62;143;176mreturn\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mresult\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...local/share/nvim/lazy/noice\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 108 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m \u001b[0m\u001b[38;2;62;143;176mend \u001b[0m \u001b[0m\u001b[38;2;235;111;146m╰─────────────────────── ↓ 3 lines ╯\u001b[0m\r\n 109 \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;244moutput\u001b[0m\u001b[38;2;144;140;170m,\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mchild\u001b[0m \u001b[0m\u001b[38;2;235;111;146m╭────────────  Error ────────────╮\u001b[0m\r\n 110 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m \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 111 \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;244malter_df_command\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;234;154;151mget_state\u001b[0m\u001b[38;2;235;111;146m(\u001b[0m\u001b[3m\u001b[38;2;246;193;119mSTAT\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 112 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m \u001b[0m\u001b[38;2;62;143;176mif\u001b[0m \u001b[0m\u001b[38;2;144;140;170mnot\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244malter_df_command\u001b[0m \u001b[0m\u001b[38;2;62;143;176mthen \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 113 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ \u001b[0m\u001b[3m\u001b[38;2;224;222;244mchild\u001b[0m\u001b[38;2;144;140;170m,\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244m_\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;234;154;151mCommand\u001b[0m\u001b[38;2;235;111;146m(\u001b[0m\u001b[38;2;246;193;119m\"df\"\u001b[0m\u001b[38;2;235;111;146m)\u001b[0m\u001b[38;2;144;140;170m:\u001b[0m\u001b[3m\u001b[38;2;196;167;231marg\u001b[0m\u001b[38;2;235;111;146m(\u001b[0m\u001b[38;2;246;193;119m{\u001b[0m \u001b[0m\u001b[38;2;246;193;119m\"-P\"\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\u001b[3m\u001b[38;2;196;167;231md\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;196;167;231mout\u001b[0m\u001b[38;2;235;111;146m(\u001b[0m\u001b[3m\u001b[38;2;224;222;244mCommand\u001b[0m\u001b[38;2;144;140;170m.\u001b[0m\u001b[3m\u001b[38;2;156;207;216mPIPED\u001b[0m\u001b[38;2;235;111;146m)\u001b[0m\u001b[38;2;144;140;170m:\u001b[0m\u001b[3m\u001b[38;2;196;167;231mspawn\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 'resume'\u001b[0m\u001b[38;2;110;106;134m \u001b[0m \u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 114 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏\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.../.local/share/nvim/lazy/nvi\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 115 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ \u001b[0m\u001b[38;2;62;143;176mif\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mchild\u001b[0m \u001b[0m\u001b[38;2;62;143;176mthen\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.../.local/share/nvim/lazy/nvi\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 116 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ ▏ \u001b[0m\u001b[3m\u001b[38;2;144;140;170m-- Ignore header \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;146mstring \"vim/_core/editor\":273:\u001b[0m\u001b[38;2;110;106;134m…\u001b[0m\u001b[38;2;235;111;146m│\u001b[0m\r\n 117 \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;244m_\u001b[0m\u001b[38;2;144;140;170m,\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mevent\u001b[0m \u001b[0m\u001b[38;2;144;140;170m=\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mchild\u001b[0m\u001b[38;2;144;140;170m:\u001b[0m\u001b[3m\u001b[38;2;196;167;231mread_line\u001b[0m\u001b[38;2;235;111;146m() ╰──────────────────────────────────╯\u001b[0m\r\n 118 \u001b[0m\u001b[38;2;156;207;216m▏\u001b[0m\u001b[38;2;57;53;82m ▏ ▏ \u001b[0m\u001b[38;2;62;143;176mif\u001b[0m \u001b[0m\u001b[3m\u001b[38;2;224;222;244mevent\u001b[0m \u001b[0m\u001b[38;2;144;140;170m==\u001b[0m \u001b[0m\u001b[38;2;246;193;119m0\u001b[0m \u001b[0m\u001b[38;2;62;143;176mthen \u001b[0m\u001b[38;2;224;222;244m\u001b[48;2;77;65;67m \u001b[0m\u001b[48;2;57;53;82m 24/277 \u001b[0m (8%) \u001b[0m\u001b[38;2;246;193;119m⠙\u001b[0m \u001b[0m\u001b[38;2;110;106;134mLoading workspace\u001b[0m \u001b[0m\u001b[1m\u001b[38;2;156;207;216mlua_ls\u001b[0m \r\n\u001b[0m\u001b[1m\u001b[38;2;35;33;54m\u001b[48;2;234;154;151m NORMAL \u001b[0m\u001b[38;2;234;154;151m\u001b[48;2;57;53;82m  main \u001b[0m\u001b[38;2;57;53;82m\u001b[48;2;42;39;63m\u001b[0m\u001b[38;2;224;222;244m\u001b[48;2;42;39;63m main.lua \u001b[0m\u001b[38;2;57;53;82m\u001b[48;2;42;39;63m\u001b[0m\u001b[38;2;234;154;151m\u001b[48;2;57;53;82m 27% \u001b[0m\u001b[1m\u001b[38;2;35;33;54m\u001b[48;2;234;154;151m 103:30 \u001b[0m", + "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;4m113G / 223G (51%) \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.java \u001b[0m\u001b[38;5;7m│ ││ \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.jd \u001b[0m\u001b[38;5;7m│ ││ \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.local \u001b[0m\u001b[38;5;7m│ ││ \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.locale \u001b[0m\u001b[38;5;7m│ ││ \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.mozilla \u001b[0m\u001b[38;5;7m│ ││ \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.npm \u001b[0m\u001b[38;5;7m│ ││ \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.nuget \u001b[0m\u001b[38;5;7m│ ││ \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.\u001b[0m\u001b[38;5;3m╭ Deprecated API──────────────────────────────────────────────────────────────╮\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.\u001b[0m\u001b[38;5;3m│\u001b[0m`file-extra-metadata.yazi` plugin: `Url.scheme` is deprecated, use `Url.spec` \u001b[0m\u001b[38;5;3m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m│\u001b[0m\u001b[38;5;4m \u001b[0m\u001b[38;2;158;158;158m \u001b[0m\u001b[38;5;4m.\u001b[0m\u001b[38;5;3m│\u001b[0minstead. \u001b[0m\u001b[38;5;3m│\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.\u001b[0m\u001b[38;5;3m╰──────────────────────────────────────────────────────────────────────────────╯\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.pyenv \u001b[0m\u001b[38;5;7m│ ││ \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.\u001b[0m\u001b[38;5;3m╭ Deprecated API──────────────────────────────────────────────────────────────╮\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.\u001b[0m\u001b[38;5;3m│\u001b[0m`root.yazi` plugin: `Url.is_search` is deprecated, use `Url.spec.is_search` \u001b[0m\u001b[38;5;3m│\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.\u001b[0m\u001b[38;5;3m│\u001b[0minstead. \u001b[0m\u001b[38;5;3m│\u001b[0m\r\n\u001b[0m\u001b[38;5;7m╰────\u001b[0m\u001b[38;5;3m╰──────────────────────────────────────────────────────────────────────────────╯\u001b[0m\r\n\u001b[0m\u001b[38;5;4m\u001b[0m\u001b[1m\u001b[48;5;4m NOR \u001b[0m\u001b[38;5;4m\u001b[48;5;7m 89.9K \u001b[0m\u001b[38;5;7m \u001b[0maddressbook \u001b[0m\u001b[38;5;3m\u001b[48;5;1m \u001b[0m\u001b[1m\u001b[38;5;3m\u001b[48;5;1m1 left\u001b[0m\u001b[38;5;3m\u001b[48;5;1m \u001b[0m\u001b[38;5;8m-\u001b[0m\u001b[38;5;3mr\u001b[0m\u001b[38;5;1mw\u001b[0m\u001b[38;5;8m-\u001b[0m\u001b[38;5;3mr\u001b[0m\u001b[38;5;8m--\u001b[0m\u001b[38;5;3mr\u001b[0m\u001b[38;5;8m--\u001b[0m\u001b[38;5;7m \u001b[0m\u001b[38;5;4m\u001b[48;5;7m Top \u001b[0m\u001b[1m\u001b[48;5;4m 1/2 \u001b[0m\u001b[38;5;4m\u001b[0m", + "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 \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 \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 \r\n\u001b[0m\u001b[38;2;224;222;244m  \u001b[0m \r\n\u001b[0m\u001b[38;2;224;222;244mWe've issued a one-time code challenge as an additional security check. Please\u001b[0m \r\n\u001b[0m\u001b[38;2;224;222;244menter the code below in your open browser window to complete the challenge and\u001b[0m \r\n\u001b[0m\u001b[38;2;224;222;244mcontinue authentication. \u001b[0m \r\n\u001b[0m\u001b[38;2;224;222;244m  \u001b[0m \r\n\u001b[0m\u001b[38;2;224;222;244m992655 \u001b[0m \r\n\u001b[0m\u001b[38;2;224;222;244m \u001b[0m \r\n\u001b[0m\u001b[38;2;224;222;244mThis code expires in 10 minutes. \u001b[0m \r\n\u001b[0m\u001b[38;2;224;222;244m  \u001b[0m \r\n\u001b[0m\u001b[38;2;224;222;244mIf you didn't attempt to sign in to Ollama, please reach out to Ollama support.\u001b[0m \r\n\u001b[0m\u001b[7m(END)\u001b[0m\u001b[38;2;224;222;244m \u001b[0m \r\n\u001b[0m\u001b[1m\u001b[38;2;35;33;54m\u001b[48;2;234;154;151m (text/plain) \u001b[0m\r\n \u001b[0m\u001b[38;2;196;167;231m(text/html)\u001b[0m \r\n\u001b[0m\u001b[1m\u001b[2m\u001b[38;2;156;207;216m\u001b[48;2;35;33;54m[phil] Connected threading | ~\u001b[0m\u001b[38;5;5march liph \u001b[0m\u001b[38;5;4m~\u001b[0m\u001b[48;2;35;33;54m \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~\u001b[0m\r\n\u001b[0m\u001b[38;5;5m❯\u001b[0m \u001b[0m\u001b[1m\u001b[38;5;1ml\u001b[0m\u001b[38;5;8msblk\u001b[0m", + "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 + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/herdr/.config/herdr/sessions/home/session.json b/herdr/.config/herdr/sessions/home/session.json new file mode 100644 index 0000000..d4580d0 --- /dev/null +++ b/herdr/.config/herdr/sessions/home/session.json @@ -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": [] +} \ No newline at end of file diff --git a/kanata/.config/systemd/user/default.target.wants/duplicati.service b/kanata/.config/systemd/user/default.target.wants/duplicati.service new file mode 120000 index 0000000..b5be98d --- /dev/null +++ b/kanata/.config/systemd/user/default.target.wants/duplicati.service @@ -0,0 +1 @@ +/usr/lib/systemd/user/duplicati.service \ No newline at end of file diff --git a/pi/.pi/agent/extensions/atuin.ts b/pi/.pi/agent/extensions/atuin.ts new file mode 100644 index 0000000..1f83974 --- /dev/null +++ b/pi/.pi/agent/extensions/atuin.ts @@ -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 { + 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 { + 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(); + + // 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)); + }); +} diff --git a/yazi/.config/yazi/bookmark b/yazi/.config/yazi/bookmark new file mode 100644 index 0000000..b6a65de --- /dev/null +++ b/yazi/.config/yazi/bookmark @@ -0,0 +1 @@ +Desktop /home/liph/Desktop/ d diff --git a/yazi/.config/yazi/init.lua b/yazi/.config/yazi/init.lua index 24b6f61..e38de49 100644 --- a/yazi/.config/yazi/init.lua +++ b/yazi/.config/yazi/init.lua @@ -1,140 +1,32 @@ --- Change this to switch themes --- local THEME = "kanagawa" --- local THEME = "catppuccin-mocha" --- local THEME = "everforest-medium" --- local THEME = "gruvbox-material" --- local THEME = "tokyo-night" -local THEME = "rose_pine-moon" - --- local theme_colors = { --- rose_pine = { --- primary = "#c4a7e7", -- iris/mauve (purple-like color from rose-pine palette) --- secondary = "#6e6a86", -- muted/subtle (comparable to comment/gray colors) --- yanked = "#9ccfd8", -- foam (cyan-like accent color) --- cut = "#eb6f92", -- love (red-like accent color) --- }, --- carbonfox = { --- primary = "#78a9ff", --- secondary = "#525252", --- yanked = "#3ddbd9", --- cut = "#ff7eb6", --- }, --- kanagawa = { --- primary = "#7e9cd8", --- secondary = "#54546d", --- yanked = "#7aa89f", --- cut = "#e82424", --- }, --- sonokai = { --- primary = "#76cce0", --- secondary = "#7f8490", --- yanked = "#fc5d7c", --- cut = "#f85e84", --- }, --- catppuccin = { --- primary = "#89b4fa", --- secondary = "#6c7086", --- yanked = "#94e2d5", --- cut = "#f38ba8", --- }, --- dracula = { --- primary = "#bd93f9", -- purple --- secondary = "#6272a4", -- comment --- yanked = "#8be9fd", -- cyan --- cut = "#ff5555", -- red --- }, --- everforest = { --- primary = "#7fbbb3", -- aqua --- secondary = "#859289", -- gray --- yanked = "#a7c080", -- green --- cut = "#e67e80", -- red --- }, --- gruvbox = { --- primary = "#7daea3", -- aqua --- secondary = "#7c6f64", -- gray --- yanked = "#a9b665", -- green --- cut = "#ea6962", -- red --- }, --- nightfox = { --- primary = "#719cd6", -- blue --- secondary = "#738091", -- comment --- yanked = "#63cdcf", -- cyan --- cut = "#c94f6d", -- red --- }, --- onedark = { --- primary = "#61afef", -- blue --- secondary = "#5c6370", -- gray --- yanked = "#56b6c2", -- cyan --- cut = "#e06c75", -- red --- }, --- } - --- eocal c = theme_colors[THEME] - --- require("yaziline"):setup({ --- color = c.primary, --- secondary_color = c.secondary, --- default_files_color = "darkgray", --- selected_files_color = "white", --- yanked_files_color = c.yanked, --- cut_files_color = c.cut, --- separator_style = "angly", -- "angly" | "curvy" | "liney" | "empty" --- separator_open = "", --- separator_close = "", --- separator_open_thin = "", --- separator_close_thin = "", --- separator_head = "", --- separator_tail = "", - --- select_symbol = "", --- yank_symbol = "󰆐", - --- filename_max_length = 24, -- truncate when filename > 24 --- filename_truncate_length = 6, -- leave 6 chars on both sides --- filename_truncate_separator = "...", --- }) - -require("recycle-bin"):setup({ - trash_dir = "~/.local/share/Trash/", -}) - --- require("omp"):setup({ config = "/home/liph/.config/yazi/omp/omp.toml" }) - -require("mime-ext"):setup({ - -- with_files = { - -- tex = "text/makefile", - -- }, - - with_exts = { - tex = "text/makefile", - }, -}) - -require("full-border"):setup({ - type = ui.Border.ROUNDED, -}) - --- git-yazi plugin --- th.git = th.git or {} --- th.git.modified = ui.Style():fg("blue") --- th.git.deleted = ui.Style():fg("red"):bold() -require("git"):setup() --- whoosh plugin --- You can configure your bookmarks using simplified syntax --- local bookmarks = { --- { tag = "Documents", path = "~/Documents", key = "D" }, --- } +--- whoosh --- +-- +local bookmarks = { + { tag = "Desktop", path = "~/Desktop", key = "d" }, + { tag = "Documents", path = "~/Documents", key = "D" }, + { tag = "Downloads", path = "~/Downloads", key = "o" }, +} -- You can also configure bookmarks with key arrays --- local bookmarks = { --- { tag = "Desktop", path = "~/Desktop", key = { "d", "D" } }, --- { tag = "Documents", path = "~/Documents", key = { "d", "d" } }, --- { tag = "Downloads", path = "~/Downloads", key = "o" }, --- { tag = "Downloads", path = "~/Downloads", key = "o" }, --- { tag = "Downloads", path = "~/Downloads", key = "o" }, --- { tag = "Downloads", path = "~/Downloads", key = "o" }, --- { tag = "Downloads", path = "~/Downloads", key = "o" }, --- } +local bookmarks = { + { tag = "Desktop", path = "~/Desktop", key = { "d", "D" } }, + { tag = "Documents", path = "~/Documents", key = { "d", "d" } }, + { tag = "Downloads", path = "~/Downloads", key = "o" }, +} + +-- Windows-specific bookmarks +if ya.target_family() == "windows" then + local home_path = os.getenv("USERPROFILE") + table.insert(bookmarks, { + tag = "Scoop Local", + path = os.getenv("SCOOP") or (home_path .. "\\scoop"), + key = "p", + }) + table.insert(bookmarks, { + tag = "Scoop Global", + path = os.getenv("SCOOP_GLOBAL") or "C:\\ProgramData\\scoop", + key = "P", + }) +end require("whoosh"):setup({ -- Configuration bookmarks (cannot be deleted through plugin) @@ -153,6 +45,7 @@ require("whoosh"):setup({ fuzzy_search = "", -- Launch fuzzy search (fzf) history = "", -- Open directory history previous_dir = "", -- Jump back to the previous directory + project_root = "-", -- Jump to the current Git repository root }, -- File path for storing user bookmarks @@ -185,115 +78,30 @@ require("whoosh"):setup({ history_fzf_path_max_folder_name_length = 30, -- Maximum length for folder names in history (default 30) }) --- projetc plugin -require("projects"):setup({ - event = { - save = { - enable = true, - name = "project-saved", - }, - load = { - enable = true, - name = "project-loaded", - }, - delete = { - enable = true, - name = "project-deleted", - }, - delete_all = { - enable = true, - name = "project-deleted-all", - }, - merge = { - enable = true, - name = "project-merged", - }, +--- mime-ext --- +require("mime-ext.local"):setup({ + -- Expand the default filename database (lowercase), for example: + with_files = { + makefile = "text/makefile", + -- ... }, - save = { - method = "yazi", -- yazi | lua - yazi_load_event = "@projects-load", -- event name when loading projects in `yazi` method - lua_save_path = "", -- path of saved file in `lua` method, comment out or assign explicitly - -- default value: - -- windows: "%APPDATA%/yazi/state/projects.json" - -- unix: "~/.local/state/yazi/projects.json" - }, - last = { - update_after_save = true, - update_after_load = true, - update_before_quit = false, - load_after_start = false, - }, - merge = { - event = "projects-merge", - quit_after_merge = false, - }, - notify = { - enable = true, - title = "Projects", - timeout = 3, - level = "info", + + -- Expand the default extension database (lowercase), for example: + with_exts = { + mk = "text/makefile", + -- ... }, + + -- Empty the default filename and extension databases, + -- use only the custom ones configured with `with_files` and `with_exts` + custom_only = false, + + -- If the MIME type is not in both filename and extension databases, + -- then fallback to Yazi's preset `mime.local` plugin, which uses `file(1)` + fallback_file1 = false, }) --- yafg plugin -require("yafg"):setup({ - editor = "nvim", -- Editor command (default: "hx") - args = { "--noplugin" }, -- Additional editor arguments (default: {}) - file_arg_format = "+{row} {file}", -- File argument format (default: "{file}:{row}:{col}") -}) - -require("simple-tag"):setup({ - -- UI display mode (icon, text, hidden) - ui_mode = "icon", -- (Optional) - - -- Disable tag key hints (popup in bottom right corner) - hints_disabled = false, -- (Optional) - - -- linemode order: adjusts icon/text position. For example, if you want icon to be on the most left of linemode then set linemode_order larger than 1000. - -- More info: https://github.com/sxyazi/yazi/blob/077faacc9a84bb5a06c5a8185a71405b0cb3dc8a/yazi-plugin/preset/components/linemode.lua#L4-L5 - linemode_order = 500, -- (Optional) - - -- You can backup/restore this folder within the same OS (Linux, windows, or MacOS). - -- But you can't restore backed up folder in the different OS because they use difference absolute path. - -- save_path = -- full path to save tags folder (Optional) - -- - Linux/MacOS: os.getenv("HOME") .. "/.config/yazi/tags" - -- - Windows: os.getenv("APPDATA") .. "\\yazi\\config\\tags" - - -- Set tag colors - colors = { -- (Optional) - -- Set this same value with `theme.toml` > [mgr] > hovered > reversed - -- Default theme use "reversed = true". - -- More info: https://github.com/sxyazi/yazi/blob/077faacc9a84bb5a06c5a8185a71405b0cb3dc8a/yazi-config/preset/theme-dark.toml#L25 - reversed = true, -- (Optional) - - -- More colors: https://yazi-rs.github.io/docs/configuration/theme#types.color - -- format: [tag key] = "color" - ["*"] = "#bf68d9", -- (Optional) - ["$"] = "green", - ["!"] = "#cc9057", - ["1"] = "cyan", - ["p"] = "red", - }, - - -- Set tag icons. Only show when ui_mode = "icon". - -- Any text or nerdfont icons should work as long as you use nerdfont to render yazi. - -- Default icon from mactag.yazi: ●; Some generic icons: , , 󱈤 - -- More icon from nerd fonts: https://www.nerdfonts.com/cheat-sheet - icons = { -- (Optional) - -- default icon - default = "󰚋", - - -- format: [tag key] = "tag icon" - ["*"] = "*", - ["$"] = "", - ["!"] = "", - ["p"] = "", - }, -}) --- require("omp"):setup({ --- config = "/home/liph/.config/ohmyposh/yazi-zen.json", --- }) - +-- fchar -- -- default config require("fchar"):setup({ -- if true: f -> file, File, FILE @@ -302,11 +110,18 @@ require("fchar"):setup({ skip_symbols = true, -- if {"yazi-"}: f -> file, yazi-file skip_prefix = {}, - -- start: f -> file - -- word: f -> file, also-file - -- all: f -> file, also-file, alsofile, elf - search_location = "start", aliases = {}, + keys = {}, +}) + +-- setup keymaps +require("fchar"):setup({ + keys = { + start = "f", -- ff -> file + ext = "e", -- ef -> name.fs + word = "F", -- Ff -> file, also-file + all = "", -- ^ff -> file, also-file, twofile, elf + }, }) -- aliases for German @@ -319,14 +134,14 @@ require("fchar"):setup({ }, }) --- you may want to turn off the search regex from --- showing up in the header by doing this +--[[ you may want to turn off the search regex from showing up + in the header by putting this in `~/.config/yazi/init.lua` ]] function Header:flags() local cwd = self._current.cwd local filter = self._current.files.filter local t = {} - if cwd.is_search then + if cwd.spec.is_search then t[#t + 1] = string.format("search: %s", cwd.domain) end if filter then @@ -334,25 +149,3 @@ function Header:flags() end return #t == 0 and "" or " (" .. table.concat(t, ", ") .. ")" end - -require("dupes"):setup({ - -- Global settings - save_op = false, -- Save results to file by default - -- auto_confirm = true, -- Skip confirmation for apply (use with caution!) - - profiles = { - -- Interactive mode: recursively scan and display duplicates - interactive = { - args = { "-r" }, - }, - -- Apply mode: recursively scan and DELETE duplicates - apply = { - args = { "-r", "-N", "-d" }, - save_op = true, -- Save results before deletion - }, - -- Custom profile example (uncomment to use) - -- custom = { - -- args = { "-r", "-s", }, -- Your custom jdupes flags - -- }, - }, -}) diff --git a/yazi/.config/yazi/keymap.toml b/yazi/.config/yazi/keymap.toml old mode 100644 new mode 100755 index 0448b07..e398e93 --- a/yazi/.config/yazi/keymap.toml +++ b/yazi/.config/yazi/keymap.toml @@ -1,536 +1,657 @@ +# 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/keymap.json + [mgr] -prepend_keymap = [ + +### chmod ### +[[mgr.prepend_keymap]] +on = ["C", "m"] +run = "plugin chmod" +desc = "Chmod on selected files" + +### whoosh ### +[[mgr.prepend_keymap]] +on = "[" +run = "plugin whoosh jump_by_key" +desc = "Jump bookmark by key" +# Direct fuzzy search access +[[mgr.prepend_keymap]] +on = "}" +run = "plugin whoosh jump_by_fzf" +desc = "Direct fuzzy search for bookmarks" +# Basic bookmark operations +[[mgr.prepend_keymap]] +on = ["]", "a"] +run = "plugin whoosh save" +desc = "Add bookmark (hovered file/directory)" +[[mgr.prepend_keymap]] +on = ["]", "A"] +run = "plugin whoosh save_cwd" +desc = "Add bookmark (current directory)" +# Temporary bookmarks +[[mgr.prepend_keymap]] +on = ["]", "t"] +run = "plugin whoosh save_temp" +desc = "Add temporary bookmark (hovered file/directory)" +[[mgr.prepend_keymap]] +on = ["]", "T"] +run = "plugin whoosh save_cwd_temp" +desc = "Add temporary bookmark (current directory)" +# Jump to bookmarks +[[mgr.prepend_keymap]] +on = "" +run = "plugin whoosh jump_key_k" +desc = "Jump directly to bookmark with key k" +[[mgr.prepend_keymap]] +on = ["]", "f"] +run = "plugin whoosh jump_by_fzf" +desc = "Jump bookmark by fzf" +# Delete bookmarks +[[mgr.prepend_keymap]] +on = ["]", "d"] +run = "plugin whoosh delete_by_key" +desc = "Delete bookmark by key" +[[mgr.prepend_keymap]] +on = ["]", "D"] +run = "plugin whoosh delete_by_fzf" +desc = "Delete bookmarks by fzf (use TAB to select multiple)" +[[mgr.prepend_keymap]] +on = ["]", "C"] +run = "plugin whoosh delete_all" +desc = "Delete all user bookmarks" +# Rename bookmarks +[[mgr.prepend_keymap]] +on = ["]", "r"] +run = "plugin whoosh rename_by_key" +desc = "Rename bookmark by key" + +[[mgr.prepend_keymap]] +on = ["]", "R"] +run = "plugin whoosh rename_by_fzf" +desc = "Rename bookmark by fzf" + +# ### eza-preview ### +# [[mgr.prepend_keymap]] +# on = ["R", "t"] +# run = "plugin eza-preview" +# desc = "Toggle tree/list dir preview" +# [[mgr.prepend_keymap]] +# on = ["R", "-"] +# run = "plugin eza-preview inc-level" +# desc = "Increment tree level" +# [[mgr.prepend_keymap]] +# on = ["R", "_"] +# run = "plugin eza-preview dec-level" +# desc = "Decrement tree level" +# [[mgr.prepend_keymap]] +# on = ["R", "$"] +# run = "plugin eza-preview toggle-follow-symlinks" +# desc = "Toggle tree follow symlinks" +# [[mgr.prepend_keymap]] +# on = ["R", "*"] +# run = "plugin eza-preview toggle-hidden" +# desc = "Toggle hidden files" +# [[mgr.prepend_keymap]] +# on = ["R", "g", "i"] +# run = "plugin eza-preview toggle-git-ignore" +# desc = "Toggle .gitignore files" +# [[mgr.prepend_keymap]] +# on = ["R", "g", "s"] +# run = "plugin eza-preview toggle-git-status" +# desc = "Toggle showing git status" + +### smart-enter ### +[[mgr.prepend_keymap]] +on = "l" +run = "plugin smart-enter" +desc = "Enter the child directory, or open the file" - ### smart-paste plugin ### - { on = "P", run = "plugin smart-paste", desc = "Paste into the hovered directory or CWD" }, +keymap = [ + { on = "", run = "escape", desc = "Exit visual mode, clear selection, or cancel search" }, + { on = "", run = "escape", desc = "Exit visual mode, clear selection, or cancel search" }, + { on = "q", run = "quit", desc = "Quit the process" }, + { on = "Q", run = "quit --no-cwd-file", desc = "Quit without outputting cwd-file" }, + { on = "", run = "close", desc = "Close the current tab, or quit if it's last" }, + { on = "", run = "suspend", desc = "Suspend the process" }, - ### 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" }, + # Hop around + { on = "k", run = "arrow prev", desc = "Previous file" }, + { on = "j", run = "arrow next", desc = "Next file" }, + { on = "", run = "arrow prev", desc = "Previous file" }, + { on = "", run = "arrow next", desc = "Next file" }, - ### 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 = [ - "", - "i", - ], run = "plugin dupes interactive", desc = "Run dupes interactive" }, - { on = [ - "", - "o", - ], run = "plugin dupes override", desc = "Run dupes override" }, - { on = [ - "", - "d", - ], run = "plugin dupes dry", desc = "Run dupes dry" }, - { on = [ - "", - "a", - ], run = "plugin dupes apply", desc = "Run dupes apply" }, - { on = [ - "", - "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" }, + ], run = "arrow top", desc = "Go to top" }, + { on = "G", run = "arrow bot", desc = "Go to bottom" }, + { on = "", run = "arrow top", desc = "Go to top" }, + { on = "", run = "arrow bot", desc = "Go to bottom" }, - ### mount plugin ### - { on = "M", run = "plugin mount" }, + { on = "", run = "arrow -50%", desc = "Move cursor up half page" }, + { on = "", run = "arrow 50%", desc = "Move cursor down half page" }, + { on = "", run = "arrow -100%", desc = "Move cursor up one page" }, + { on = "", run = "arrow 100%", desc = "Move cursor down one page" }, - # ### 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" }, + { on = "", run = "arrow -50%", desc = "Move cursor up half page" }, + { on = "", run = "arrow 50%", desc = "Move cursor down half page" }, + { on = "", run = "arrow -100%", desc = "Move cursor up one page" }, + { on = "", run = "arrow 100%", desc = "Move cursor down one page" }, - ### 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 = "", 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 = [ - "", - ], run = "escape", desc = "Exit visual mode, clear selected, or cancel search" }, - # { on = [ - # "q", - # "q", - # ], run = "quit", desc = "Exit the process" }, - { on = [ - "", - ], run = "close", desc = "Close the current tab, or quit if it is last tab" }, + { on = "h", run = "leave", desc = "Back to the parent directory" }, + { on = "l", run = "enter", desc = "Enter the child directory" }, - ### 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" }, + { on = "", run = "leave", desc = "Back to the parent directory" }, + { on = "", run = "enter", desc = "Enter the child directory" }, - ### 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" }, + { on = "H", run = "back", desc = "Back to previous directory" }, + { on = "L", run = "forward", desc = "Forward to next directory" }, - ### 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 = [ - "", - ], 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", + # Toggle + { on = "", run = [ + "toggle", "arrow 1", - ], desc = "Toggle selection" }, + ], desc = "Toggle the current selection state" }, + { on = "", run = "toggle_all --state=on", desc = "Select all files" }, + { on = "", run = "toggle_all", desc = "Invert selection of all files" }, + + # Visual mode + { on = "v", run = "visual_mode", desc = "Enter visual mode (selection mode)" }, + { on = "V", run = "visual_mode --unset", desc = "Enter visual mode (unset mode)" }, + + # Seeking + { on = "K", run = "seek -5", desc = "Seek up 5 units in the preview" }, + { on = "J", run = "seek 5", desc = "Seek down 5 units in the preview" }, + + # Spotting + { on = "", run = "spot", desc = "Spot hovered file" }, + + # Operation + { on = "o", run = "open", desc = "Open selected files" }, + { on = "O", run = "open --interactive", desc = "Open selected files interactively" }, + { on = "", run = "open", desc = "Open selected files" }, + { on = "", run = "open --interactive", desc = "Open selected files interactively" }, + { 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 = "P", run = "paste --force", desc = "Paste yanked files (overwrite if the destination exists)" }, + { on = "-", run = "link", desc = "Symlink the absolute path of yanked files" }, + { on = "_", run = "link --relative", desc = "Symlink the relative path of yanked files" }, + { on = "", run = "hardlink", desc = "Hardlink yanked files" }, + { on = "Y", run = "unyank", desc = "Cancel the yank status" }, + { on = "X", run = "unyank", desc = "Cancel the yank status" }, + { 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 (ends with / for directories)" }, + { on = "A", run = "bulk_create", desc = "Bulk create files" }, + { on = "r", run = "rename --cursor=before_ext", desc = "Rename selected file(s)" }, + { on = ";", run = "shell --interactive", desc = "Run a shell command" }, + { on = ":", run = "shell --block --interactive", desc = "Run a shell command (block until finishes)" }, + { on = ".", run = "hidden toggle", desc = "Toggle the visibility of hidden files" }, + { on = "s", run = "search --via=fd", desc = "Search files by name via fd" }, + { on = "S", run = "search --via=rg", desc = "Search files by content via ripgrep" }, + { on = "", run = "escape --search", desc = "Cancel the ongoing search" }, + { on = "z", run = "plugin fzf", desc = "Jump to a file/directory via fzf" }, + { on = "Z", run = "plugin zoxide", desc = "Jump to a directory via zoxide" }, + + # Linemode { on = [ - "v", - ], run = "visual_mode", desc = "Enter visual mode" }, + "m", + "s", + ], run = "linemode size", desc = "Linemode: size" }, { on = [ - "V", - ], run = "visual_mode --unset", desc = "Enter visual mode (unset)" }, - # Search + "m", + "p", + ], run = "linemode permissions", desc = "Linemode: permissions" }, { on = [ - "/", - ], run = "find --smart", desc = "Find file" }, + "m", + "b", + ], run = "linemode btime", desc = "Linemode: btime" }, { on = [ + "m", + "m", + ], run = "linemode mtime", desc = "Linemode: mtime" }, + { on = [ + "m", + "o", + ], run = "linemode owner", desc = "Linemode: owner" }, + { on = [ + "m", "n", - ], run = "find_arrow", desc = "Go to next found" }, + ], run = "linemode none", desc = "Linemode: none" }, + + # Copy { on = [ + "c", + "c", + ], run = "copy path", desc = "Copy file path" }, + { on = [ + "c", + "C", + ], run = "copy url", desc = "Copy file URL" }, + { on = [ + "c", + "d", + ], run = "copy dirpath", desc = "Copy directory path" }, + { on = [ + "c", + "D", + ], run = "copy dirurl", desc = "Copy directory URL" }, + { on = [ + "c", + "f", + ], run = "copy filename", desc = "Copy filename" }, + { on = [ + "c", + "n", + ], run = "copy name_without_ext", desc = "Copy filename without extension" }, + + # Filter + { on = "f", run = "filter --smart", desc = "Filter files" }, + + # Find + { on = "/", run = "find --smart", desc = "Find next file" }, + { on = "?", run = "find --previous --smart", desc = "Find previous file" }, + { on = "n", run = "find_arrow", desc = "Next found" }, + { on = "N", run = "find_arrow --previous", desc = "Previous found" }, + + # Sorting + { on = [ + ",", + "m", + ], run = [ + "sort mtime --reverse=no", + "linemode mtime", + ], desc = "Sort by modified time" }, + { on = [ + ",", + "M", + ], run = [ + "sort mtime --reverse=yes", + "linemode mtime", + ], desc = "Sort by modified time (reverse)" }, + { on = [ + ",", + "b", + ], run = [ + "sort btime --reverse=no", + "linemode btime", + ], desc = "Sort by birth time" }, + { on = [ + ",", + "B", + ], run = [ + "sort btime --reverse=yes", + "linemode btime", + ], desc = "Sort by birth time (reverse)" }, + { on = [ + ",", + "e", + ], run = "sort extension --reverse=no", desc = "Sort by extension" }, + { on = [ + ",", + "E", + ], run = "sort extension --reverse=yes", desc = "Sort by extension (reverse)" }, + { on = [ + ",", + "a", + ], run = "sort alphabetical --reverse=no", desc = "Sort alphabetically" }, + { on = [ + ",", + "A", + ], run = "sort alphabetical --reverse=yes", desc = "Sort alphabetically (reverse)" }, + { on = [ + ",", + "n", + ], run = "sort natural --reverse=no", desc = "Sort naturally" }, + { on = [ + ",", "N", - ], run = "find_arrow --previous", desc = "Go to previous found" }, + ], run = "sort natural --reverse=yes", desc = "Sort naturally (reverse)" }, + { on = [ + ",", + "s", + ], run = [ + "sort size --reverse=no", + "linemode size", + ], desc = "Sort by size" }, + { on = [ + ",", + "S", + ], run = [ + "sort size --reverse=yes", + "linemode size", + ], desc = "Sort by size (reverse)" }, + { on = [ + ",", + "r", + ], run = "sort random --reverse=no", desc = "Sort randomly" }, + + # Goto + { on = [ + "g", + "h", + ], run = "cd ~", desc = "Go home" }, + { on = [ + "g", + "c", + ], run = "cd ~/.config", desc = "Go to ~/.config" }, + { on = [ + "g", + "d", + ], run = "cd ~/Downloads", desc = "Go to ~/Downloads" }, + { on = [ + "g", + "t", + ], run = "plugin trash", desc = "Go to trash bin" }, + { on = [ + "g", + "", + ], run = "cd --interactive", desc = "Jump interactively" }, + { on = [ + "g", + "f", + ], run = "follow", desc = "Follow hovered symlink" }, + # Tabs { on = [ - "T", - ], run = "tab_create --current", desc = "Create new tab" }, + "t", + "t", + ], run = "tab_create --current", desc = "Create a new tab in CWD" }, { 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" }, + "t", + "r", + ], run = "tab_rename --interactive", desc = "Rename current tab" }, + + { on = "1", run = "tab_switch 0", desc = "Switch to first tab" }, + { on = "2", run = "tab_switch 1", desc = "Switch to second tab" }, + { on = "3", run = "tab_switch 2", desc = "Switch to third tab" }, + { on = "4", run = "tab_switch 3", desc = "Switch to fourth tab" }, + { on = "5", run = "tab_switch 4", desc = "Switch to fifth tab" }, + { on = "6", run = "tab_switch 5", desc = "Switch to sixth tab" }, + { on = "7", run = "tab_switch 6", desc = "Switch to seventh tab" }, + { on = "8", run = "tab_switch 7", desc = "Switch to eighth tab" }, + { on = "9", run = "tab_switch 8", desc = "Switch to ninth tab" }, + + { on = "[", run = "tab_switch -1 --relative", desc = "Switch to previous tab" }, + { on = "]", run = "tab_switch 1 --relative", desc = "Switch to next tab" }, + + { on = "{", run = "tab_swap -1", desc = "Swap current tab with previous tab" }, + { on = "}", run = "tab_swap 1", desc = "Swap current tab with next tab" }, + + # Tasks + { on = "w", run = "tasks:show", desc = "Show task manager" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, ] [tasks] -prepend_keymap = [ - { on = [ - "", - ], run = "close", desc = "Close task manager" }, - { on = [ - "k", - ], run = "arrow -1", desc = "Move cursor up" }, - { on = [ - "j", - ], run = "arrow 1", desc = "Move cursor down" }, + +keymap = [ + { on = "", run = "close", desc = "Close task manager" }, + { on = "", run = "close", desc = "Close task manager" }, + { on = "", run = "close", desc = "Close task manager" }, + { on = "w", run = "close", desc = "Close task manager" }, + + { on = "k", run = "arrow prev", desc = "Previous task" }, + { on = "j", run = "arrow next", desc = "Next task" }, + + { on = "", run = "arrow prev", desc = "Previous task" }, + { on = "", run = "arrow next", desc = "Next task" }, + + { on = "", run = "inspect", desc = "Inspect the task" }, + { on = "x", run = "cancel", desc = "Cancel the task" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, ] -[select] -prepend_keymap = [ - { on = [ - "", - ], run = "close", desc = "Cancel selection" }, - { on = [ - "", - ], run = "close --submit", desc = "Submit selection" }, - { on = [ - "k", - ], run = "arrow -1", desc = "Move cursor up" }, - { on = [ - "j", - ], run = "arrow 1", desc = "Move cursor down" }, +[spot] + +keymap = [ + { on = "", run = "close", desc = "Close the spot" }, + { on = "", run = "close", desc = "Close the spot" }, + { on = "", run = "close", desc = "Close the spot" }, + { on = "", run = "close", desc = "Close the spot" }, + + { on = "k", run = "arrow prev", desc = "Previous line" }, + { on = "j", run = "arrow next", desc = "Next line" }, + { on = "h", run = "swipe prev", desc = "Swipe to previous file" }, + { on = "l", run = "swipe next", desc = "Swipe to next file" }, + + { on = "", run = "arrow prev", desc = "Previous line" }, + { on = "", run = "arrow next", desc = "Next line" }, + { on = "", run = "swipe prev", desc = "Swipe to previous file" }, + { on = "", run = "swipe next", desc = "Swipe to next file" }, + + # Copy + { on = ["c", "c"], run = "copy cell", desc = "Copy selected cell" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, +] + +[pick] + +keymap = [ + { on = "", run = "close", desc = "Cancel pick" }, + { on = "", run = "close", desc = "Cancel pick" }, + { on = "", run = "close", desc = "Cancel pick" }, + { on = "", run = "close --submit", desc = "Submit the pick" }, + + { on = "k", run = "arrow prev", desc = "Previous option" }, + { on = "j", run = "arrow next", desc = "Next option" }, + + { on = "", run = "arrow prev", desc = "Previous option" }, + { on = "", run = "arrow next", desc = "Next option" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, ] [input] -prepend_keymap = [ - { on = [ - "", - ], run = "close", desc = "Cancel input" }, - { on = [ - "", - ], run = "close --submit", desc = "Submit input" }, + +keymap = [ + { on = "", run = "close", desc = "Cancel input" }, + { on = "", run = "close --submit", desc = "Submit input" }, + { on = "", run = "escape", desc = "Back to normal mode, or cancel input" }, + { on = "", run = "escape", desc = "Back to normal mode, or cancel input" }, + + # Mode + { on = "i", run = "insert", desc = "Enter insert mode" }, + { on = "I", run = [ + "move first-char", + "insert", + ], desc = "Move to the BOL, and enter insert mode" }, + { on = "a", run = "insert --append", desc = "Enter append mode" }, + { on = "A", run = [ + "move eol", + "insert --append", + ], desc = "Move to the EOL, and enter append mode" }, + { on = "v", run = "visual", desc = "Enter visual mode" }, + { on = "r", run = "replace", desc = "Replace a single character" }, + + # Selection + { on = "V", run = [ + "move bol", + "visual", + "move eol", + ], desc = "Select from BOL to EOL" }, + { on = "", run = [ + "move eol", + "visual", + "move bol", + ], desc = "Select from EOL to BOL" }, + { on = "", run = [ + "move bol", + "visual", + "move eol", + ], desc = "Select from BOL to EOL" }, + + # Character-wise movement + { on = "h", run = "move -1", desc = "Move back a character" }, + { on = "l", run = "move 1", desc = "Move forward a character" }, + { on = "", run = "move -1", desc = "Move back a character" }, + { on = "", run = "move 1", desc = "Move forward a character" }, + { on = "", run = "move -1", desc = "Move back a character" }, + { on = "", run = "move 1", desc = "Move forward a character" }, + + # Word-wise movement + { on = "b", run = "backward", desc = "Move back to the start of the current or previous word" }, + { on = "B", run = "backward wide", desc = "Move back to the start of the current or previous WORD" }, + { on = "w", run = "forward", desc = "Move forward to the start of the next word" }, + { on = "W", run = "forward wide", desc = "Move forward to the start of the next WORD" }, + # { on = "e", run = "forward --end-of-word", desc = "Move forward to the end of the current or next word" }, + { on = "E", run = "forward wide --end-of-word", desc = "Move forward to the end of the current or next WORD" }, + { on = "", run = "backward lean", desc = "Move back to the start of the current or previous word" }, + { on = "", run = "forward lean --end-of-word", desc = "Move forward to the end of the current or next word" }, + { on = "", run = "backward lean", desc = "Move back to the start of the current or previous word" }, + { on = "", run = "forward lean --end-of-word", desc = "Move forward to the end of the current or next word" }, + + # Line-wise movement + { on = "0", run = "move bol", desc = "Move to the BOL" }, + { on = "$", run = "move eol", desc = "Move to the EOL" }, + { on = "_", run = "move first-char", desc = "Move to the first non-whitespace character" }, + { on = "^", run = "move first-char", desc = "Move to the first non-whitespace character" }, + { on = "", run = "move bol", desc = "Move to the BOL" }, + { on = "", run = "move eol", desc = "Move to the EOL" }, + { on = "", run = "move bol", desc = "Move to the BOL" }, + { on = "", run = "move eol", desc = "Move to the EOL" }, + + # Delete + { on = "", run = "backspace", desc = "Delete the character before the cursor" }, + { on = "", run = "backspace --under", desc = "Delete the character under the cursor" }, + { on = "", run = "backspace", desc = "Delete the character before the cursor" }, + { on = "", run = "backspace --under", desc = "Delete the character under the cursor" }, + + # Kill + { on = "", run = "kill bol", desc = "Kill backwards to the BOL" }, + { on = "", run = "kill eol", desc = "Kill forwards to the EOL" }, + { on = "", run = "kill backward", desc = "Kill backwards to the start of the current word" }, + { on = "", run = "kill forward", desc = "Kill forwards to the end of the current word" }, + { on = "", run = "kill backward", desc = "Kill backwards to the start of the current word" }, + { on = "", run = "kill forward", desc = "Kill forwards to the end of the current word" }, + + # Cut/Yank/Paste + { on = "d", run = "delete --cut", desc = "Cut selected characters" }, + { on = "D", run = [ + "delete --cut", + "move eol", + ], desc = "Cut until EOL" }, + { on = "c", run = "delete --cut --insert", desc = "Cut selected characters, and enter insert mode" }, + { on = "C", run = [ + "delete --cut --insert", + "move eol", + ], desc = "Cut until EOL, and enter insert mode" }, + { on = "s", run = [ + "delete --cut --insert", + "move 1", + ], desc = "Cut current character, and enter insert mode" }, + { on = "S", run = [ + "move bol", + "delete --cut --insert", + "move eol", + ], desc = "Cut from BOL until EOL, and enter insert mode" }, + { on = "x", run = [ + "delete --cut", + "move 1 --in-operating", + ], desc = "Cut current character" }, + { on = "y", run = "yank", desc = "Copy selected characters" }, + { on = "p", run = "paste", desc = "Paste copied characters after the cursor" }, + { on = "P", run = "paste --before", desc = "Paste copied characters before the cursor" }, + + # Undo/Redo/Casefy + { on = "u", run = [ + "undo", + "casefy lower", + ], desc = "Undo, or lowercase if in visual mode" }, + { on = "U", run = "casefy upper", desc = "Uppercase" }, + { on = "", run = "redo", desc = "Redo the last operation" }, + + # History + { on = "k", run = "recall -1", desc = "Recall previous input" }, + { on = "j", run = "recall 1", desc = "Recall next input" }, + { on = "", run = "recall -1", desc = "Recall previous input" }, + { on = "", run = "recall 1", desc = "Recall next input" }, + { on = "", run = "recall -1", desc = "Recall previous input" }, + { on = "", run = "recall 1", desc = "Recall next input" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, ] -[completion] -prepend_keymap = [ - { on = [ - "", - ], run = "close --submit", desc = "Submit completion" }, - { on = [ - "k", - ], run = "arrow -1", desc = "Move cursor up" }, - { on = [ - "j", - ], run = "arrow 1", desc = "Move cursor down" }, +[confirm] + +keymap = [ + { on = "", run = "close", desc = "Cancel the confirm" }, + { on = "", run = "close", desc = "Cancel the confirm" }, + { on = "", run = "close", desc = "Cancel the confirm" }, + { on = "", run = "close --submit", desc = "Submit the confirm" }, + + { on = "n", run = "close", desc = "Cancel the confirm" }, + { on = "y", run = "close --submit", desc = "Submit the confirm" }, + + { on = "k", run = "arrow prev", desc = "Previous line" }, + { on = "j", run = "arrow next", desc = "Next line" }, + + { on = "", run = "arrow prev", desc = "Previous line" }, + { on = "", run = "arrow next", desc = "Next line" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, +] + +[cmp] + +keymap = [ + { on = "", run = "close", desc = "Cancel completion" }, + { on = "", run = "close --submit", desc = "Submit the completion" }, + { on = "", run = [ + "close --submit", + "input:close --submit", + ], desc = "Complete and submit the input" }, + + { on = "", run = "arrow prev", desc = "Previous item" }, + { on = "", run = "arrow next", desc = "Next item" }, + + { on = "", run = "arrow prev", desc = "Previous item" }, + { on = "", run = "arrow next", desc = "Next item" }, + + { on = "", run = "arrow prev", desc = "Previous item" }, + { on = "", run = "arrow next", desc = "Next item" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, ] [help] -prepend_keymap = [ - { on = [ - "", - ], 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" }, + +keymap = [ + { on = "", run = "escape", desc = "Enter normal mode, or hide help menu" }, + { on = "", run = "escape", desc = "Enter normal mode, or hide help menu" }, + { on = "", run = "close", desc = "Close help menu" }, + { on = "", run = "close --submit", desc = "Close help menu and run selected action(s)" }, + + # Navigation + { on = "k", run = "arrow prev", desc = "Previous line" }, + { on = "j", run = "arrow next", desc = "Next line" }, + + { on = "", run = "arrow prev", desc = "Previous line" }, + { on = "", run = "arrow next", desc = "Next line" }, + + { on = "", run = "arrow prev", desc = "Previous line" }, + { on = "", run = "arrow next", desc = "Next line" }, ] diff --git a/yazi/.config/yazi/package.toml b/yazi/.config/yazi/package.toml index 9026e65..9477c7d 100644 --- a/yazi/.config/yazi/package.toml +++ b/yazi/.config/yazi/package.toml @@ -1,174 +1,42 @@ +[[plugin.deps]] +use = "AnirudhG07/rich-preview" +rev = "02597c4" +hash = "33982a6e2d6df07619a2a0fc163a0950" + [[plugin.deps]] use = "yazi-rs/plugins:smart-enter" -rev = "4e55902" -hash = "56fdabc96fc1f4d53c96eb884b02a5be" - -[[plugin.deps]] -use = "yazi-rs/plugins:git" -rev = "4e55902" -hash = "36a484acf6a0a0219c543ccb4cee218f" - -[[plugin.deps]] -use = "yazi-rs/plugins:mount" -rev = "4e55902" -hash = "e2d4bfccf31e5ab05f46fee4797c5215" +rev = "c591a36" +hash = "187cc58ba7ac3befd49c342129e6f1b6" [[plugin.deps]] use = "yazi-rs/plugins:mime-ext" -rev = "4e55902" -hash = "48a77ab785d1ee73bc2d7bbe2a4c2090" - -[[plugin.deps]] -use = "ndtoan96/ouch" -rev = "cfb9140" -hash = "b5067143415bd2d46c0dfa57319ddcef" - -[[plugin.deps]] -use = "yazi-rs/plugins:piper" -rev = "4e55902" -hash = "2dd68af59418b631b9703a03f606b31f" +rev = "c591a36" +hash = "3bcc00df11efc3ae5d15f2dba860be7" [[plugin.deps]] use = "boydaihungst/mediainfo" -rev = "90a1652" -hash = "7a83bfab51d1d2ba6558cc56bd52539a" - -[[plugin.deps]] -use = "AnirudhG07/rich-preview" -rev = "573b275" -hash = "c3e2871c9ef244fd181f203791f9b0d2" - -[[plugin.deps]] -use = "stelcodes/bunny" -rev = "7137a44" -hash = "58bfa473bcac8574400e573787954512" - -[[plugin.deps]] -use = "Lil-Dank/lazygit" -rev = "0e56060" -hash = "ab2e66065a94cca4886b303518e44bd8" - -[[plugin.deps]] -use = "MasouShizuka/projects" -rev = "eed0657" -hash = "ca79de2b3bb2247906e01b2261e5d2bb" - -[[plugin.deps]] -use = "XYenon/yafg" -rev = "6034c67" -hash = "11543279fd83ad4cacdfb07d67016928" - -[[plugin.deps]] -use = "Shallow-Seek/fazif" -rev = "6e10488" -hash = "c549ff30fb1d1d97c734010fe82f6295" - -[[plugin.deps]] -use = "GianniBYoung/rsync" -rev = "14d2828" -hash = "7394430ba473dce1023721622438bf3d" - -[[plugin.deps]] -use = "llanosrocas/yaziline" -rev = "6266926" -hash = "9917ab5cb9bdbab7ca7f2501f84f0f11" - -[[plugin.deps]] -use = "boydaihungst/file-extra-metadata" -rev = "4fbb106" -hash = "8765a20c837f4be5718f89ab1cf33b4" - -[[plugin.deps]] -use = "yazi-rs/plugins:full-border" -rev = "68f7d48" -hash = "3996fc74044bc44144b323686f887e1" - -[[plugin.deps]] -use = "pirafrank/what-size" -rev = "179ebf6" -hash = "57056b9728006881d580ccabe8154a9c" - -[[plugin.deps]] -use = "yazi-rs/plugins:toggle-pane" -rev = "57f1863" -hash = "8a7c58225816a163a6e8730c0adafbc8" +rev = "73a3658" +hash = "fa787a8ef785f2976ea2861f62b8c6c4" [[plugin.deps]] use = "AminurAlam/yazi-plugins:fchar" -rev = "658a099" -hash = "b484ff674a30268991b67f819f4625da" +rev = "8c2ce96" +hash = "9f2598542a3263b5a03d7fb832b407c1" [[plugin.deps]] -use = "mshnwq/dupes" -rev = "18a9395" -hash = "730ddf5e1179f34ccb81f8d5ed564823" +use = "ahkohd/eza-preview" +rev = "e8fb6c8" +hash = "c238595c801caa4f28553b9af8cf8804" [[plugin.deps]] -use = "uhs-robert/recycle-bin" -rev = "69d7d4c" -hash = "38dac2fc8f4b50bbdaa46e699720a54a" +use = "WhoSowSee/whoosh" +rev = "43f3202" +hash = "da14dfe482886e756150ae4f5c2bb23" [[plugin.deps]] -use = "Rolv-Apneseth/bypass" -rev = "c1e5fcf" -hash = "208e77480824f62558a5b037c8569d61" +use = "yazi-rs/plugins:chmod" +rev = "c591a36" +hash = "87472b05a8c420100f6a2b9cde1e7746" -[[plugin.deps]] -use = "Ape/open-with-cmd" -rev = "e3d430f" -hash = "d29c1ffa99099181faf8c01d1f59aa4c" - -[[plugin.deps]] -use = "yazi-rs/plugins:smart-paste" -rev = "56971d0" -hash = "75fd60b2df7115e0a6ca31a6a804d320" - -[[plugin.deps]] -use = "larry-oates/open-git-remote" -rev = "95b3043" -hash = "8d17dcc063217b558a08ed8bb5e9e8ad" - -[[plugin.deps]] -use = "yazi-rs/plugins:vcs-files" -rev = "56971d0" -hash = "5cfd9b2bd3f8ec4e3245e3426a967f82" - -[[flavor.deps]] -use = "yazi-rs/flavors:catppuccin-mocha" -rev = "4a1802a" -hash = "b951d27afe197d154f6da9683b46c5f8" - -[[flavor.deps]] -use = "Mintass/rose-pine-moon" -rev = "94385fe" -hash = "edbe0dfb5db8ff37281dba62adc7e750" - -[[flavor.deps]] -use = "Chromium-3-Oxide/everforest-medium" -rev = "0158f0f" -hash = "a7c36abf0b13289aceb1c20a17c6a63e" - -[[flavor.deps]] -use = "bennyyip/gruvbox-dark" -rev = "619fdc5" -hash = "d4c7003ec6dc4efd5d3a050d5c2f2575" - -[[flavor.deps]] -use = "BennyOe/tokyo-night" -rev = "8e6296f" -hash = "d9da921c3bd37ba32ee27a8a7a40461f" - -[[flavor.deps]] -use = "dangooddd/kanagawa" -rev = "04985d1" -hash = "c69a63981e254a3a06a11c2bf0e6af1d" - -[[flavor.deps]] -use = "matt-dong-123/gruvbox-material" -rev = "6c36491" -hash = "564f2335540ee2c440c3b671bc28ac60" - -[[flavor.deps]] -use = "malick-tammal/monokai" -rev = "e59f6cb" -hash = "dcb449d46e30dc020e4b7b59202d2448" +[flavor] +deps = [] diff --git a/yazi/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE b/yazi/.config/yazi/plugins/chmod.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE rename to yazi/.config/yazi/plugins/chmod.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/chmod.yazi/README.md b/yazi/.config/yazi/plugins/chmod.yazi/README.md new file mode 100644 index 0000000..5a73207 --- /dev/null +++ b/yazi/.config/yazi/plugins/chmod.yazi/README.md @@ -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. diff --git a/yazi/.config/yazi/plugins/chmod.yazi/main.lua b/yazi/.config/yazi/plugins/chmod.yazi/main.lua new file mode 100644 index 0000000..51031f7 --- /dev/null +++ b/yazi/.config/yazi/plugins/chmod.yazi/main.lua @@ -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, +} diff --git a/yazi/.config/yazi/plugins/eza-preview.yazi/LICENSE b/yazi/.config/yazi/plugins/eza-preview.yazi/LICENSE new file mode 100644 index 0000000..914edb1 --- /dev/null +++ b/yazi/.config/yazi/plugins/eza-preview.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Victor Aremu + +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. diff --git a/yazi/.config/yazi/plugins/eza-preview.yazi/README.md b/yazi/.config/yazi/plugins/eza-preview.yazi/README.md new file mode 100644 index 0000000..09f54c1 --- /dev/null +++ b/yazi/.config/yazi/plugins/eza-preview.yazi/README.md @@ -0,0 +1,108 @@ +# eza-preview.yazi + +[Yazi](https://github.com/sxyazi/yazi) plugin to preview directories using [eza](https://github.com/eza-community/eza), can be switched between list and tree modes. + +List mode: +![list.png](list.png) + +Tree mode: +![tree.png](tree.png) + +## Requirements + +- [yazi (26.1.4+) or nightly](https://github.com/sxyazi/yazi) +- [eza (0.20+)](https://github.com/eza-community/eza) + +## Installation + +```sh +ya pkg add ahkohd/eza-preview +``` + +## Usage + +### Basic Setup + +Add `eza-preview` to previewers in `yazi.toml`: + +```toml +[[plugin.prepend_previewers]] +url = "*/" +run = "eza-preview" +``` + +### Key Bindings + +Set key bindings to control the preview in `keymap.toml`: + +```toml +[mgr] +prepend_keymap = [ + { on = [ "e", "t" ], run = "plugin eza-preview", desc = "Toggle tree/list dir preview" }, + { on = [ "e", "-" ], run = "plugin eza-preview inc-level", desc = "Increment tree level" }, + { on = [ "e", "_" ], run = "plugin eza-preview dec-level", desc = "Decrement tree level" }, + { on = [ "e", "$" ], run = "plugin eza-preview toggle-follow-symlinks", desc = "Toggle tree follow symlinks" }, + { on = [ "e", "*" ], run = "plugin eza-preview toggle-hidden", desc = "Toggle hidden files" }, + { on = [ "e", "g", "i" ], run = "plugin eza-preview toggle-git-ignore", desc = "Toggle .gitignore files" }, + { on = [ "e", "g", "s" ], run = "plugin eza-preview toggle-git-status", desc = "Toggle showing git status" }, +] +``` + +### Configuration + +Configure the plugin in `init.lua`: + +```lua +require("eza-preview"):setup({ + -- Set the tree preview to be default (default: true) + default_tree = true, + + -- Directory depth level for tree preview (default: 3) + level = 3, + + -- Show file icons + icons = true, + + -- Follow symlinks when previewing directories (default: true) + follow_symlinks = true, + + -- Show target file info instead of symlink info (default: false) + dereference = false, + + -- Show hidden files (default: true) + all = true, + + -- Ignore files matching patterns (default: {}) + -- ignore_glob = "*.log" + -- ignore_glob = { "*.tmp", "node_modules", ".git", ".DS_Store" } + -- SEE: https://www.linuxjournal.com/content/pattern-matching-bash to learn about glob patterns + ignore_glob = {}, + + -- Ignore files mentioned in '.gitignore' (default: true) + git_ignore = true, + + -- Show git status (default: false) + git_status = false +}) + +-- Or use default settings +require("eza-preview"):setup({}) +``` + +## Available Commands + +- `plugin eza-preview` - Toggle between tree and list modes +- `plugin eza-preview inc-level` - Increase tree depth level +- `plugin eza-preview dec-level` - Decrease tree depth level +- `plugin eza-preview toggle-follow-symlinks` - Toggle symlink following +- `plugin eza-preview toggle-hidden` - Toggle hidden file visibility +- `plugin eza-preview toggle-git-ignore` - Toggle ignore files mentioned in '.gitignore' +- `plugin eza-preview toggle-git-status` - Toggle showing git status + +## Contributing + +Feel free to contribute by opening issues or submitting pull requests! + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/yazi/.config/yazi/plugins/eza-preview.yazi/main.lua b/yazi/.config/yazi/plugins/eza-preview.yazi/main.lua new file mode 100644 index 0000000..01dfae6 --- /dev/null +++ b/yazi/.config/yazi/plugins/eza-preview.yazi/main.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/fchar.yazi/README.md b/yazi/.config/yazi/plugins/fchar.yazi/README.md index b687a92..7a347f5 100644 --- a/yazi/.config/yazi/plugins/fchar.yazi/README.md +++ b/yazi/.config/yazi/plugins/fchar.yazi/README.md @@ -15,15 +15,6 @@ ya pkg add AminurAlam/yazi-plugins:fchar # Usage -in `~/.config/yazi/keymap.toml` - -```toml -[mgr] -prepend_keymap = [ - { on = "f", run = "plugin fchar", desc = "Jump to char" }, -] -``` - in `~/.config/yazi/init.lua` ```lua @@ -35,11 +26,18 @@ require('fchar'):setup { skip_symbols = true, -- if {"yazi-"}: f -> file, yazi-file skip_prefix = {}, - -- start: f -> file - -- word: f -> file, also-file - -- all: f -> file, also-file, alsofile, elf - search_location = 'start', aliases = {}, + keys = {}, +} + +-- setup keymaps +require('fchar'):setup { + keys = { + start = 'f', -- ff -> file + ext = 'e', -- ef -> name.fs + word = 'F', -- Ff -> file, also-file + all = '', -- ^ff -> file, also-file, twofile, elf + }, } -- aliases for German @@ -79,8 +77,37 @@ require('fchar'):setup { }, } --- you may want to turn off the search regex from --- showing up in the header by doing this +-- aliases for Chinese, taken from https://github.com/DreamMaoMao/sjch.yazi +require('fchar'):setup { + aliases = { + a = "暗暧謸硙碍磑澳癌凹阿翱聱肮礙骜骯揞敖昂鏊鏖案桉菴獒瑷胺螯铵锕锿庵廒闇罯砹熬爱犴鼇黯鹌鶕鵪鴱鰲鮟驁隘隩霭靄靉鞍懊艾捱傲騃嗄嗌嗳嗷坳埃埯吖呵哀哎唉啊谙鳌鑀安矮皑盎盦盫韽俺奥媪嫒袄襖挨按拗蔼薆岙岸遨氨", + b = "泊波泵浜颁颩飆飑飙飚晡暴本杓杯杷板柄柏綼編緶縪繃繴绊绑绷飽餅渤饱饼饽駜變诐碑碚碥碧磅磦滗滨濒濞瀑灞炮炳焙笾筆筚箅箔甭畀畚疤病痹瘢瘪癍禀禆秉秕秘稖稗稟窆窇茇荜荸莂刨别剥办勃包匕北匾半卑博卜卞卟变叭躄軷輩輽闭阪陂陛翉耙聛背胞醭釆釛釟鈵鈸鈽鉍鉼鋇礡礴驳骠骳髀髆髉髌髕髟髲鬂鬓鬢魃搏搬摆摈摒播擘敝斌斑絆絣砏不丙逼遍錃鏢鏰鄙便标梆棒榜槟檗菝菠菢萆葆狴玢玻班璧瓣瓿甏钚钡钣钯钵钸钹铂铋脖脿箥箯篦篰篳簸簿螕蠙补表锛镈并庇庳弁闆缏编缤缽罢罷罼砭煲煸爆爸版狈龅鼻鼥鼊鼈麃鹎鹁鸨鷩佰鳪邲邴邶鳊鲾鲍鲌鲅鲃鱉鮊鮅鮁驆雹霸靶鞛憋舨般舭舶艊艑艕芭臏臕苾捭掰八兵駮饆餔竝笆笔笣笨鞴鞭閟閉镳嘣坂坌坝埔埠堡镚镖镔镑邉邊吡吧呗哔哺啵邠邦邫汴谤豰豳豹貏貝賁賓賲贁贆贝败贬贲赑鳔鳖鐴鐾鑣鑮鵏詖誖諘伴伯亳宾宝蝙悲藵惫步傧傍悖怖忭必彼彬彪弼弊蓖蓓白百睥臂粑粊粃頒倍俾保扒扁趵跁跋跛跸踄踣蹦蹩蹳壁备夯奔妣婊婢嬖孛孢冰粺膀膊膑膘蒡袌袐袚被補裨裱褊褒褓褙襞襬覇覍觱愎捕捌拨拜拔拌拂抱报把扳扮蔀蔔蔽蕃薄薜薭藣藨蚌蜌屏岜崩巴币布帛帮螁苯苞苄部辟辦辨辩辫辯边迸逋避殡比毕毖毙辈", + c = "沈沉沧测涌涔衬衰颤飭晁晟晨曹曾朝杈材村杵枞枨縩繟纏纒纔纯绌绰飺餈淙淬淳饬馇馋馳謥謿词诚诧谄谌硟碀碜碴磁磋磣滁漕潮潺澄澈澶灿炊炒炽焯筂策筴筹䌷畅畜畴疮疵痤痴瘁瘛瘥瘳祠禅禇禪秅秤称程稠稱穪穿窗窜茈茌茐茝茞茨茬茶茺荈草出刍创初刹刺剿匆匙卒厂厕厝厨参叉叱吃蹿躇躔躦車軙輳輴车闡闯阊阐陈陙除羼翀翅翆翠耖耡耻聡聦聪聰肏肞肠絺絾綝綴綵酢酬酲醇醊醋醕醜醻采重鈂鉏鋋磪磭礎礤驰骋骖骢骣鬯魑掺插揣搀搋搐搓搽摧撑撤撮撺操擦攒敕敞斥昌春昶純紣紬絒砗䲠丑丛丞串逞遄鋹錞錯鍐鍤鎚鏟鏦鏿郴鄛鄽酁酂侪促查柴柽棰椎椽椿楂楚楮榇榱槌槎槽樗樘橙橱檫次莼莿菖菙菜萃萗葱蒇猖猜猝猹琛琮璀璁璨瓷钗钞铖铛胵脆脞脣脨脭脺腄箠箣篡篨篪篸簇籌瞠螥螬螭蟬蟲蟾蠆蠢蠶衝衩铲铳锄锉错锤锸幢床廛閳閶绸缀缞缠础爨龊龀齿齼齣齜齒鼂黪黜麨麤麎麄鹾鹚鹑鸱鸧鸀鷘鶒鴟鴜鲿鲳鯙鯈驄驂陲陳雌雏雔雠憧憷舛舡舱船艖艙艚艟艸臎臣臭臿舂捶掣措催傺充冁册騁餐倡窾竄竌竾笞笧长長镵镲镩镡嗔嗤嘈嘬嘲噌嚓囱场坻坼垂城埕塍墀侈侧吵吹呈呲哧唇唱啐啜啻邨池汊汆谗谶豉豠豺貙財賜賨賰贂财赐鑱钏詞詧詫誠誴諂伺伧伥传仓从仇产残殂乘岔尝寸察宸宠蝉悴惆惝惨惩惭此储偿矬矗恻怵怊怆怅忱忡忖忏徜徂彻彩弛蓯蓛喳皴盛眵睉睬瞅瞋臅喘粣粚粗粎粋韂頳顀顣顫倘俦才赤赪趁超趡趩跐踀踅踆踌踔踟踧踩踸踹蹅蹉蹖蹙蹭蹰蹴戳尺尘处姹娼婵媸嫦存孱冲凑粲粹糍糙成腸膗膥膪膬膵蒭蒼袃裁裎裯裳褚褫觇触觸愁慈挫持拆抽抻抄承扯蓴蓸蔟蔡蔥蔯薋藏處虘虫虿蚕蚩蛏蜍层岑崇崔崭嵯巛川巢差帱常蝽腠苍苌苁芻郕郗鋤辍辏辙辞辭辰辵迟遲毳氅氚", + d = "沌沓洞涤淀顿颠朵杜柁緞締繨纛绐绖绦淡渎渡饤饳饾諦讀讜讟订诋诞读调谍酊酖硐碇碉碓碘碟碠碡碭碲滇滴澄澹灯炖点焘煅等答电甸疔疸痘瘅瘩癜祋祶祷禘禫禱秺稲稻窎窦荅荙荡荳荻凳凼刀刁到剁动单叠叨叮叼吊躉躭躱躲軃軇軧轪闍阇阘队阽陡翟翢耊耋耑耵耷耽聃聜聸肚胆胨釘釣釱鈍鈿磴磸磹磾礅礑驮骀骶髑髧鬄魛揲搭敌敦斗断旦糴絛石砀砘丁东丢逮逿遁道達錠鍍鍛鎝鐓郸都鄧柢栋档棣椟椴菂菄菧菪萏董葮蒂玎玳玷瓞甙钝钿铎铛铞胴脦脰箪箽篤篴篼簜簞簟簤簦簹瞪蠹衟铤铥铫锝锭锻镀底店度缍缎缔羝砥爹牍牒犊狄独齻鼑鼎黮黩黨點黛鸫鸟鶫鶇鵰鴭邸鲽鲷鱽鯛鮘陮隄隊隝隥雕電霘霮靛靮靼鞑憝懂舵艜芏掂掇掉掸儋兑党兜典駳饏餖竇端竺笃笗笛笪第籴鞮閗镻镫镦嗒嗲嘀嘟噔囤地坫坻垌垛垤垫埭堆堕堞堤堵镝侗邓吨呆呔咄咚哆哒哚啖啶喋鐤谛谠豆貂貸贉贷赌赕鑟钉钓詄誕調低但佃代仃殚殆歹丹对定宕蝀蝶悼惦惮惰傣短恫怼怠怛忉德得待当弹弟蓧蓞癫登的盗盜盪盯盹盾眈眣眰睇督睹膽导韥頂頓顛顶倒打趤趸跌跶跺踮踱蹀蹈蹎蹬蹲蹾戴墩多大夺奠妒妲娣嫡冬冻凋戥蒧袋袛裆裰褋褡襌襶覩觌觝訂訋捣挡担抵抖蔸虰蚪蛋蜑蜔蜨岛岱岽峒嶝巅帝带帱腣腚达迨迪迭追逇递逓逗遞遯段殿毒氐氘氡氮", + e = "洱颚额餌饵饿譌讍讹诶厄軛輀轭阏阨阸阿而耳鈪礘摁遏鍔鄂莪萼珥钶铒胹锇锷鹗鹅鸸鶚鵝佴鳄鲕鰪隲儿餓嗯噩垩邇呃哦谔貮贰詻恶恩睋二粫頋頞額顎俄趰尔娥婀腭蒽訛愕扼蕚薾蚵蛾屙峨苊迩", + f = "沣沸法泛浮涪風风飛飞服枋枫緋緐縛縫繁繙纷纺绂绋绯飯飰淝饭馥馮讣讽访诽酆酚碸滏烦烽焚筏甫畈番疯痱祓祔福禣稃茯茷凫分副匐匚匪反发軓軬輔輻轓阀阜防附羵翂翡翻肤肥肪肺綍綒酦醱釜釡鈇鈖驸髣髪髮髴魵放敷斐斧方紛矾丰逢鍑鐇鄜鄷桴梵棼榧樊莩菔菲葍葑玢珐钫脯範篚簠蠜蠭锋镄幞幡府废缚缝缶缻罘罚罰砜砝砩燔父犯狒鼢鼖黼黻黺麸麩鷭鶝鳳鳆鲼鲱鲋鲂鰟鯡鯆鮄騛雰霏非舫艂艴芙芬芳茀范符笰笲籓坊坟垡吠否吩呋呒咐唪啡邡鐨汾豊豐豧負販費賦负贩费赋赗赙钒佛伐伏仿份付乏富蝮蝠悱傅偾怫忿彷弗盕盽眆瞂封粪粉韍韨頫俸俯俘扉房赴趺趽跗蹯复夫奉奋妃妇妨孚孵冯凡凤糞腹膚膰膹袚袝袱裶複褔襆覂覅覆訃訉訜訪愤拊拂抚扶蕃蕜蕟蕡蕧薠藩蘩虙蚡蚨蜂蜉蜚峰帆幅腓腑腐苻芾郙郛鋒辐返氛氟辅", + g = "沟沩沽泔涡涫衮顾晷更杆杠杲构果枸柑緄緱緺縆縞纥纲绀给绠绲淦馃馆馉馘謴诂诖诟诡该诰酐碽磙港溉滚澉灌炅炔筈筦筶筸筻箇箍㭎疙疳痼祪祮祰祻祼禞禬秆稈稿穀窤茖茛荄刚刮刽刿剐割功勾匦匮卦古句各合躬軌輥輨輵轂轕轧轨關闺阁陔羮羹耇耈耕耿聒聝肛肝股肱胍綱綶酤釭鈣鉤銧骨骼骾高鬲鬶鬼掼搁搞搿擀改攻故敢旮旰紇紺絓給矸丐个過遘鋼錧鍋鎘鏆郭柜栝根格桂桄桧梏梗棍棺椁概槁槔橄莞莻菇菒菓菰葛葢蒄猓珙瑰瓜甘钙钢钩钴胱胳脵箛管篙篝簋簳鲠鲧瞡螝铬锅锆锢干广庋庚缑缟缸罁罆罐罟罡罣羔羙硅硌龟龜龚牯牿犷狗龔鼓鹳鹘鹒鹄鸽鸹鸪鷱鶊鴰鴣鴐鳤鳡鳜鲴鲑鱥鱤鱖鯝鯁鯀鮭鮌騧騔隔雇雊雚革鞏戆戈舡舸艮芥臌臯苽掴光公共关冈館竿笐笟笱鞲閤閣関嗝嘎嘏噶固国圪圭坩垓垢埂埚塥镐镉供告呙呱呷咕咣咯哏哥哽哿汩汞邽谷貢貫貴賅賈賡購贡购贯贵贾赅赓鳏鑵钆鴿訽詬詭該詿誥佝估仡乾乖寡宫官宄蝈蜾藳惯傀矔瞽冠恭怪归彀弓蓕歌癸皈皋盖盥盬睴睾亘粓韟顧倌扛赣赶趕跟跪尴尬尜尕够妫姑媾孤粿糓糕戤膈膏膕膭蓇蓋袞袧袬袼袿裓裹褂褠襘規覯觀观规觏觚觥感挂拱拐藁虢虼蚣蛄蛊蛤岗岣崞崮工巩帼腘苷苟郜辜过逛毂轱辊", + h = "沆沪河泓洄洪洹活浍浑浒浣浩海涣涵涸颃颌颔颢晃晖晗晦曷杭緩縠繉繣繯红纥纮绗绘餀淮混饸馄諱譁譓護讙讧讳诃诙话诨诲湖湟溷滑滹漶潢濠瀚灏火灰烀烘烩焊焓焕煌画痕痪瘊癀祜祸禍禾秳秽穢穫茴荒荟荤荭荷凰函划劐劾化卉华厂厚号合后軣輝轋轘轟闳阂阍阓阖阚阛翃翙翚翬翭翮翯翰耗肓肣胡綄綔酣酼醐醢釪銲鋎磺礉礐驩骅骇骸骺鬟魂魟魱魺魽撼擐攉斛旱昊昏紅紇紘紭絙砉逭遑鍧鍸鏸鐄鄗鄠侯核桁桓桦槐槲横欢获菏菡華萈萑葔葟葫葷猢猴猾獾环珩珲琥瑚璜瓠钬胲脝篁篌篲簧籇鲩瞣螖蟥蟪蠔蠖蠚行衡铧铪锪锽锾幻閽缋缓缳罕龢煳狐狠龁齁鼾黒黑黉黄黃麾麧鹱鹮鹤鹘鹖鹕鹄鸿鸻鸖鶴鶡鴴鳠邯鳇鲎鲄鱯鰉鯶鮰驊隍隳隺雘霍靃鞎鞨憨憾航艎艧花芴臛餱餬餫竑竤笏笐閧閤镮镬嗥嗨嗬嘿嚆嚎嚯回囫圜坏堠镐還邗吓含吭吼呵呼和咳咴哄哈哏哕哗哼唤唬唿喉喊鐬汞汗汉汇郈郇郃谎谼谽豁豗豢豪貉貋貛貨賀賄货贺贿鑊鴻詤詥詪詯話詼何会伙亨亥乎寰寒害宦宏蝴蝦蝗惑惚惠蘅藿惶患悔悍恨恢恚恒恍怙怀忽徽徨後徊很彗弧弘皇皓盇盉盍盒睅睆睧睺喙喝互粐韓韩頜頦頷顪顸候扈戽赫趪踝户壑壕壶夥夯奂好婚孩冱粭糀糇糊或膴蒿袆袔褐褱覈觳訇訌慌慧换捍挥护蔊蕙蕻薃薅薨藧虎虖虝號虹虺虾蚘蚝蚢蚵蚶蛔蛤岵幌郝鋐迒还迴逅毁毫氦轰轷辉", + j = "沮泾洁洎洚津浃浅浆浇济浚浸涓涧衸颈颉颊颎飓飢晋景晶暨机杰极枧架枷枸緊緝績繋繝繫繳繼纠级纪经结给绛绝绞绢继绩飷餃渐渠饥饯饺馂馑駃駉駕謇講謹謽警计讥讦记讲讵诀诘诫酒硷碅碣碱湔湫溅激灸炅炬炯烬焦煎筊筋筥简箋箕甲界畸畿疆疖疚疥疽疾痂痉瘕瘠祭祲禁禨秔秬积秸稘稭稷稼稽穄穊積究穽窌窖窘窭茄茍茎茧茭荆荊荐荚荠荩击刭剂剑剞剧剪剿劂加劫劲匠即卷卺厥厩及句叫叽吉躩車軍輂較轇轎车间阄阱阶际降羯翦耩聚肌肩肼胛胫絭絳絶絹經綗酱酵醮醵金釿鉀鉅鉴磯礀礁礓驥驹驾骄骏骑骥骱髻鬋鬏揪揭搅搛撅攫救教敫敬斤旌既旧糺系紀級紤紧絅結絕絜絞給矶䴔䴖䴗進鋸鋻錈錦鍓鍳鍵鎸鏡鐍鐎鄄鄿柜柩柬校桀桔桕桨桷检棘椐椒楗楫楷榉榘槛槿橘橛莒莖莙莢菁菅菊菌菨菫菹葌葏葭狷獍獗玑玖珈珏琚瑾钜钧钾铗胶脊脚脛脧脨脻腈箘箭箿節篯簡簥簴籍鲣蟜蟨蟭蟼蠒蠲街铰锏锔锦锩键锯廑建閰缄缉缙缣缰缴罝罥罽羁羇羈砛爝爵犄犋犍犟狙狡龃齽齑齏鼱麚麖麕麏麉麇麂鹼鹫鹪鹣鹡鹃鸡鸠鷲鷦鶺鶄鵳佳佶佼鴃鴂鳩鳉鲸鲫鲪鲛鲚鲒鱾鱂鱀鰿鰹鰜鯽鯨鯚鮈驚驕騎階際隦隽集雋雎雞霁靓靖静靜靳鞊鞠鞫憬戋舉舊舰艍艦艰艽节芥芨芰芵臮臶臼舅据捷掎掘接掬僦僬僭僵儆兢具兼冀駶饑借倦竞竟竣竭競竸笄笈笕笳笺鞿鞯鞬間閒镢嗟嘉嘏噍噘噤噱嚼囝圈圾均坚基堇境镜镓镌侥君咀咎咭哜唧啾喈鐧鐖汲江郏郊谏谨谫谲谻豇豭貑貗賈賮賷贱贾赆赍鳒鐻鑑钁钅钑鵁鵋詨詰誡諓伽伎价件介今仅京交亟歼乩九久举尖寄寂家蝍悸惊惧蘔矯矫矩矡矜矍瞿瞼瞷恝急忌徼径强蓟䝼䦆皆皎皲监盡監眷睊睑睛睠睫臇臄井军将粔韭韮韲頰頸倔俱俭俊扃赳赽趄趌趝足趹趼跂跏跔距跡跤践跻跼跽踐踖踘踞踦踺踽蹇蹐蹟蹶蹷蹻就墼夹奇奖奸妓妗姐姜姣姬娇娟婕婧嫁嫉孑孓季决净减几粳粷精糨戬截戢戟戛戒倨腱腳腵膠膲蒋蒟蒹蒺衿袀袈袓袷裥裾褧褯襟覊見覐覚覠覵覺见觉觊觐角觔觖解觭觼訆計訐記訣健偈假捡捐捃挤挢挟拮拣拘拒抉技蓳蓵蔣蕉蕝蕨蕳蕺薑薦藉藠虀虃虡虮蚧蛟蛣蛱蛶蜐蜠尽局居届屐屦岌螀岬峤峻崛嵇嵴巨己巾苴苣郟郡辑近进迥迦迳迹逕遽殛毽轿较", + k = "浍颏颗暌枯纩绔馈馗駃诓诳课硱碦磕磡渴溃溘炕烤筈筐筘筷㧟疴科秙稇稛稞空窟窠窥凯刊刳刻剀勘匡匮卡口叩可躻躿軠軻轗闚闶闿阃阔阚考聧聩聭聵肯肻胩綑酷醌釦鈧銙礊礦骒骙骷骻髁髋髠髡魁揆揩旷昆絓矻矿砍逵鄶鄺侩柯栲框棵楷槛葵蒈狯珂琨钪钶铐铠胯脍腃箜篑簻籄铿锎锞锟锴库康廓开闊缂砢硁硄龛犒狂龈齦黋鹍鷇鵼鵟佧侃鲲鲙鲓鱇騍隗霩靠鞚鞟慷艐芤臗控克饋餽倥窾竷開嗑困圹坎坑块坤坷垦垮垲堀堪侉吭咔咖咳哐哙哭啃喀邝郀邼豈贶鑛誇課佝会伉亢寇宽客蝌蝰傀矙瞰悝悃恺恳恪恐忾快款喹喾盔看眍眖眶睏睽瞆喟亏頍頦顆顑顝扣扛趶趷跍跨跬蹞壳夔夸夼奎孔况糠戡蒉蒯袴裈裉裤裩褌褲愦愧慨捆挎拷括抠抗扩蔲蔻薖虧蛞蜫尻岂岢岿崆嵌苦苛瞌郐适氪轲", + l = "沥沦泐泠泪泷泸泺洌洛流浏浪涝涞涟淋颅领飀飗晾朗李来林枥柃練縷繂繆繚繿纍纑纶练络绫渌饹馏馿謧謱讈讕论诔谅硫碌磊磠磥溜溧滤滥滦漉漏漓漤漯潋潞潦澜澧濂濑灵炉炼烂烈烙笿筣箓箖䁖留略疗疠疬痢痨瘌瘘瘤瘰癃癞祾祿禄禮离秢稂稆稑稜茏茢荔荖荦莅列刘利剌力劣励劳勒卢卤卵历厉厘另叻吏魿躏躐躒躘躶躼輘輪轥轮闌闾阆阑陆陇陋羷羸翎老耒耢耧耮聆聊聋联聫聯聾肋胧胪絽綟綠綡綹酪酹醁醨醪醴醽里量釐釠鈩鈴銮磷磿礌礨礱礼驡驢驴骆骊骝骡髅髏鬑鬛鬣鬲鬸魉揽搂摞撂撩撸擂敛斓料旅旒旯糷累絡絫䴕两临連逦逯逻遛錄録鍊鎌鎏鎘鎯鏈鏍鏐鏕鏤鏻鐐鄰酃柳栊栌栎栏栗栳栾梁梨棂棱椋椤楝楞楼榄榈榔榴橹檑檩莉莨莱莲菉菕菞菱萊萝落葎葻狸狼猁猎猡獠率玲珑珞琅理琉琏琳璃璐瓴铃铑铝脔脟脶脷脸脼腀腂箩篓篥篭篮篱簍簏簕簬簼簾籁鲡鲢鲤瞜螺蟧蠃蠊蠝蠟蠡蠣蠪衖铹铼链锂锊锍锒锣镂庐廉廊廖廪弄閬閭绺绿缆缕缡缧缪缭罍罗罱罶罹羀羅羉羚砢砬砺砻砾裏熘燎牢犁龙龓龒龍龄齢齡黧黎麟麜麗麓麐鹿鹭鹩鹨鹠鹂鸾鸰鸬鸓鸁鷺鶆鵣佬鴒鳢鳞鲮鲈鲁鱺鱲鱧鱗鰡鯬鯩鯏驘驑驎騾騼陵陸隆隣隶隸雒離雳零雷霖霝霤露霳靈靓鞡懒懔舲舻艆艣艫良芦臘臚臨捩掠掳僚儡六兰騄駵窿立笠笭笼籙籠籣籬鞻镴镰镭镧镥镣镠嘞嘹噜囵囹坜垃垄垅垆垒埒塄镙镏例侣邋邌邏吕吝呖呤咙咧咯哩唠唳啉啦啷喇鐳鐮郎邻谰豂豊貍賂賚賴赁赂赉赖鳓鑘鑢鑨鑪鑾钌鴷鴼詅詈諒論伶伦令仑仂亮殓乱乐丽尥寮寥蜽蝼蘆藺藶傈矝瞵瞭悝恋怜徕律录蓮蓠蓝蓏喱喽盝盠眬睐睖睙睞睩臉臈臁了粩粦粝粚粒类韷領類顟顱俪俩俚俐戾赲趔跉跞路踉蹓蹽墚奁姥娄娈娌婪嫘嫠孪冷冽凉凌凛粮粱粴粶粼糧戮膂膋膟膦膫蒌蒗蒞袊裂裏裡裢裣裲裸褛褴褵褸襕襱襽覙览愣偻倮捞捋挛拦拢拎拉抡蓼蓾蔂蔞蔺蕌蕗蕾薐藍藘藜藞藰蘭蘿虏虑虜蛉蛎蛚蛯蜊蜡屡履岚岭峦崂崃嵝嶙帘螂腊苙苓苈郘郞郦鋫鋰辌辘辚辢辣辽连遴氇氯轳轹辂辆", + m = "沐沔没沫泌泖泯浼颟暝暮曼朦木末杩杪枚某綿緍緜緡緲縻繆绵淼渑馍馒馬駡謀謨謩謬谋硭碼磨渺湄湎溟满漠漫漭灭焖瘼祕祙祢禖禡禰秒秘秣穆穈茅茆茉茗茫茻荬劢勉勐募卖卯名门闵闷闽陌耄耱酩酶醚醾醿鈱銘鋂礞祃马骂髦髳鬘鬽魅魔描摩摸摹敉敏旄明昧昴码万錨鍪鎇郿鄍鄚鄤鄮鄳梅梦棉楣模檬莓莫莽莾萌葞猕猛猫猸玛玟玫珉瑁甍钼铆铓脈脉脒篃篾簢瞐瞑瞒瞞瞢螞螟螨蟆蟊蟒蠎蠓蠛蠻铭锚锰镁镅镆幕庙缅缈缗缦缪罙罵美砪煤熳牟牡牦牧犸鼏黾黴默麿麽麼麻麵麰麦麥麋麊鹛鹋鸣鸍鶜鶓鴓鳴鳘鰻鰵驀雮霂霉霾靡面靣靦靺鞔鞪懋懑懵艋艒艨芈芒茂免駹饝饅笀米閔門嘛嘧坶埋墁镘侔邁邈吗命咩咪哞唛邙汨谜谟谧谩谬貉貊貌貓貘買貿賣贸鳗钔鴾詸詺们仫亩殁买乜么寞寐密宓蝥悯蘑矛矒矊矇矀忙弭弥蓦喵皿盟目盲盿眀眄眇眉眊眜眠眫眯眳眸睦瞀瞄瞇冕冒粖跊墓墨妈妙妹姆姥娩媒媚嫫嬷孟冥糜糢腼膜蒙蒾蓂袂袤覓覛觅愍慕慢拇抿抹扪蔄蔑蔓蔴蕒藐藦蘪蘼虋虻蚂蛑蛖蛮蜜蜢岷峁嵋帽幂幔苠苜苗芼迈迷母每毛民氓", + n = "泞泥浓涅淖衲颞暖曩纳纽馁馕讷诺碾溺甯男疟秾稬茑努匿南輗輦闹陧羺耐耨聂聍聣聶聹聻肭酿醲釀釢鈉鈕驽鬧搦撵攮旎昵糵納紐錗鍩侬柠柰楠菍萘猊猱瑙钕钠钮铌铙胬能脑脓脮脲脳籋螚蟯蠥衂衄锘年廿弄砮硇秊牛狃狞齯齧鼰鼐黏麑鸟鲶鲵鲇鯘鯓鮾鮎难難霓靵靹懦艿臑臬捺捻傩内餒嗫囊囔囝囡坭垴埝镎镍镊呐呢呶咛哝哪啮喃那貎鑷誽你佞伲乃宁孽蝻矃您恼恧恁怩怒忸念弩眲睨膿喏农粘顳赧跈蹑蹨奈女奴奶妞妮娘娜嫩嬲孥孬凝糯糱倪腻膩袅袦褦襛捏挪挠拿拧拟拗拈扭蔦蔫薿蘖虐蛲蜺尼尿腩腦苶苧辗農迺逆氖辇", + o = "沤讴筽禺区耦鏂欧瓯鸥噢呕哦怄喔偶蕅藕殴", + p = "沛泊泡泮泼派浦颇频颦飘普曝朋朴杷板枇枰縹纀纄纰淠馮譜譬评配硼碰磐磞湃湓溥滂漂潘澎濮瀑炮烹筢畔番疱疲痞癖票秠穙凭判刨剖剽劈匍匏匹匾叛叵魾軿輧闢陂陠翩翸耙耪聘聠肧胖胚酦酺醅醥醱釙磻礮骈骗骠骿髲髼鬅魄魒魸撇擗攀攴攵旁旆砒䴙丕錋鄱便棚榀榜莆莩菐菩萍萢葡葩珀琵琶璞瓢瓶甓钯钷铍胼脬脯脾篇篷篺簰瞟瞥螵蟛蟠蠙铺锫平庀庖庞闆缥缾罴砰砲破爬片牌牝犏狍龎鼙麅麃鹏鸊鷿鵬佩邳鲏鲆鰟鯆鮍騙陪陴雱霈霹鞄舖般芃芘捧掊排僻竂笸閛镨镤嘌嘭噗噼圃圮坡坪坯埔埤培堋堡否呸咆品哌啤啪谝谱貔貧贌贫赔鳑钋評仳仆殍乓乒蘋怦怕徘彷彭蓬喷皤皮盆盘盤盼眅睥粕韼頗頻俳俜扒扑扁趴跁跑跘跰踫蹁蹒蹣蹼姘娉婆媲嫔嫖冯膀膨蒪蒲袍袢裒裨裴褜襻偏拼拚拍披抨抛批扳蓱蚍蚾蛢蜱屁屏帔帕螃腗苹苤郫鋪鋬辔辟迫逄毗氆氕", + q = "沁沏泅泉泣洽浅淇衹颀颧晴曲朐期权杞枪柒緝繑纃纤绮清渠謦讫诎诠诮请酋硗硚确碁碃碕碛碶確碻磜磩溱漆潜炔炝筇筌签箐䓖畎畦痊瘸祛祺禥禽秋秌秦稽穐穷穹窃窍窮茄茕茜荃荞荠切券前劁劝劬勤区千卡却卿去取躯軀軡輕闕阒阕阙阡陗群翑翘翹耆聺肍肷胊胠絿綅綦綮醛釥鈆鈙鉛銎銓銶磬磲磽礄礐祁祇祈驱骎骐骑骞骹髂鬈鬐鬿揿搴撬擎擒敲旗絟矵砌䲡七且丘逡遒錆錢鍥鍬鎗鏘鐑鄡鄿侨侵栖桤桥棋椠楸榷槭樯樵橇檎檠欠菃菣菦菬菳萁萋萩葋葜葲葺球琦琪琴琼璩钤钦钱钳铅脐箝箞箧篟篬簯簽瞏瞧螓螧蟿蠄蠷蠼衢铨锓锖锜锲锵锹庆廑弃閴闃绻缉缱缲缺罄羌羗羟硂硈龝牵犬犰龋齲齐齊鼩鼜黥黢黔麹麴麯麡麒鹙鹐鹊鸲鶈鵲佥邱鳈鳅鲯鲭鰽鰸鰬鰜鰌鰁鯖鯕鮼鮨驅騫騎雀青鞒鞘鞦鞧憔憩艢艩芊芍芎芑芡芩芪芹臍臞掐掮全其駸竅竏竒笉籤镪嗪嘁噙器囚圈圊圻堑邔吣启呛邛鐰汽汔求郄谦谯谴豈赇鳍钎詮誳諆請企仟仇亲亟乾乞乔寝蝺蝤悭情惬愀瞿悫悛悄恰怯强歉欺䞍癯睘亓将韆頃頎顣顴顷俟俏俅扦起赹趄趋趣趥趨趬跂跄跒跫跷踆踡踥蹊蹌蹡蹻墙壳奇契妻妾娶嫱凄粸糗戚戗戕倩蒨衾袪裘裙褰襁親觑觓訄訅愆慊倾挈拳抢歧蔷蕁蕉蕖蕲薔藑藭藮蘧虔虬虯蚈蚑蚯蛆蛐蛣蛩蛬蛴蛷蜞蜣蜷蜸蜻屈屺岂岍岐岖峭崎嵌巧巯腔苘郪迁迄逑遣遷氍气氢氰轻辁", + r = "洳润颥飪朊枘染柔緌緛繞纕纫纴绒绕餁饪饶讓认让讱溶溽濡热然禳秹稔穃穰茸茹荏荛荣刃軔軟軠轫闰阮耎肉肜肰鈤銳礽髥髯魜揉攘日紝絍絨鎔桡榕葇葚瑞瓤箬篛融蠕铷锐缛熔燃狨鶸鴧鳰靭靱鞣戎芮儒入冉駥饒閏嚅嚷镕汝鴽鵀認任仞仍仁人乳容蘂惹忍弱蓐睿喏冗韌韧顬扔蹂壤壬女如妊娆孺糅腬膶蒘蒻蓉衽袇袵袽褣褥襦訒偌扰蕊蕋蕘蕤薷蘘蚋蚺嵘蝾若苒鋭辱込遶软", + s = "沈沙沭泗泷洒涉涑涩涮淑衫衰颂颡颵颼飒飔飕食飧晌晒晟暑曙朔术杀杉杓束松枢緦縮縿繀繌繐繕繖繩繬繸繺纱纾绅绍绥绱绳绶飼飾淞深渑渖渗饰饲馊馓首駛諡謏謚謪譅識譢讪讼设识诉试诗诜说诵谁谂谇谉硕碎碩磃磉湿溯溲滠漱潲潸澌澍濉炻烁烧筍筛筭筮筲㩳甩申畲疏疝痧瘙瘦祏祘神祟祳禅禗禠禩禪禭私秫稅稍税稣穑穗穟穯窣荪荽删刷刹剡剩劭势勺匙十卅升单厍厕厦厮叁参双叔受叟召史司身輋輸闍闩闪阇陕陝陞羧羴羶翛翜翣耍耜耸聖聲肃肅肆肾胂胜絲綀綏酥酸酾释釋釤鈰鈶鉂鉃鉐鉽銫示社祀驦驶驷骕骚骟髓髞髿鬆鬖鬙魫掺揲搔搜搠搡摄摅摔撒撕擅擞收散数斯施时是紓紗素索紳紹絁石砂䴓三上世丝丧逝速逤遂遈鍦鎍鎖鎪鏁鏉鄯柿树栓桑桫梢梭梳森椹榫槊莎莘莤莳菘菽萨葚葠葰葹蒁狲狻猞率珊琐瑟甚生甥铄铈脎脽算箾篩簌簑簨簺籂鲥鲨瞍瞓瞫螦螫蟀蟮術铩铯锁锶锼庶閯閷缌缩缫缮缲署砷煞煽熟熵燧爽牲狩狮鼶鼫鼪鼠黍麝鸶鸘鷫鷥鶳鶐鵨使鳾鳣鳝邵鳋鳃鲹鱣鱓鰰鰤鰠鯵鯋鯊鯅鯂鮻驌騻騷隃隋随隧隨隼雖雙雟霎霜霰靸鞖鞝戍舌舍舐舒舜舢艄艏艘艭色芍芟苼授傻僧僳兕兽騇駷餗竖竢竦笇笋笙笥籔籶閃嗄嗉嗍嗓嗖嗜嗣嗦嗽嗾嘶噬噻四圣垧埏埘埽塑塞塾侍邃吮呻咝哂哨唆唢售唰唼商啥啬善邥鐩汤汜汕邿谡谥豉豎豕賞賸贳赊赎赏赛钃钐設訴試詩詵誓說説誰佘似伺伸伤伞仨仕什殊殇书乘寺宿室审实宋守蝕蝨蘓蘇藷矧矢矟矂瞬悚恕恃思怂忪彡弑式蓡蓑蓍歙歃䥇䦂臊盛省眎眘眚眭睃睄睒睡睢事寿射粟粛韶順頌顺倏俟俞俗手扇所赡赦跚跾尸尚少墅墒士声夙失奢妁始姒姗姝娑娠婶嫂嬗孀孙孰凇糁糂腮膄膆膳膻蒐蒜蒴蓃袑裋裟裳褨襫視覗视觞觫訕訟愫慎慑损捎挲拾拴拭折抒扫死蔌蔎蔘蔬蔱蕂蕣蕵蕼薮薯藗虒虱虵虽蚀蛇蛥蛳蛸蜀蜃尿屎属山岁崧嵊嵩巳市帅师螋腨腧腎苫苕苏输辻述送适遡適殳毵毹氏水轼", + t = "沓沱沲泰洮涂涕涛淌淘颋颓题颱飥飩暾替条柁緰緹縚縢縧统绦绨飳飸餂餇添饧饨駝駞謄謟讨讬调谈硐碳碵磄湍溏溻滔滕滩漯潭潼澹炭炱烃烫筒筩㳠甬田町畋疃疼痛痰瘫祧祹禔禢禫禵禿秃秱稌穜突窕茼荑荡荼凸剃剔厅叨台叹同躺軆軘闒闛闼阗阘阤陀陁羰耥聴聽肽胎統綂綈綎酞酡酮酴醄醍醣醰鈦鈿鉄銅磹祂驮驼骀骰體髫鬀鬄魋提搪摊昙紏絛䴘逷遆鋵鋾鍎鍩鍮鎝鎲郯柝桃桐桶梃梯棠椭榻樘橐檀莛莵菟菭萄萚萜葖葶獭瑭甜钛钭钽钿铁铊铜脫脱腆箨鲦螗螣螳衕铤铽锑锬庭庹廷閮绹缇罈砣砤砼煺特龆鼮鼞鼗鼍鼉黮黇鹈鸵鷵鷉鶟鶗佻鴫邰鳀鲖鲐鲀鰷鰨鰧鯈鮀驖騰騠騊陶隤霆靦鞓鞗舔舦艇艼臋臺掏探推掭僮兔駣饕餮童笤笹籐籜鞺镡嗒嗵嚏团囤图土坍坛坦坨堂堍塌塔塘填镗侗邆吐吞听呔唐唾啕啼汰汤汀谭豚貒貣貪貼贉贪贴鳎鐵钍鴺詷誊誻調談佟佗体佃仝他亭殄乇它惕傥矘瞳悌恸恬态忝忒忑忐徒彤彖弹弟蓨蓚盪眮眺臀粡粜粏韬頭頹頺題倜倘托趟趧趯趿跅跆跎跳跶踏踢蹄蹆蹋蹚蹹天太头套她妥婷糖糰腾腿膛膯袉袒袥裼褪襢覃覥觍討託慝偷停捅挺挞挑拖拓抬抟投蓷蕛薙薹藤藫藬蚒蛻蜓蜕蜩蜪屉屠屯峒帑帖苕苔迏迢迯退逃透途逖通遝遢毯氽", + w = "沃沩洧洼浯涠涡顽晚晤望未杌枉緯纨纬纹维渥温謂讏诬误诿硙硪碔碗磈磑渭湾潍炜烷焐畏畹痦痿瘟祦禑稳穩穵窊窝窩窪茣莁凹刎剜务勿午卧卫危躛軎輐輓问闱闻阌阢翁翫聞聬肟胃維網鋈骛骩骫魍魏魰握文斡於无旺紊紋万丸逶違鋺鍏鏏鄬侮桅梧莞莴菀菵萎萬萵葦葨葳猥猬王玟玩玮琬璺瓦瓮钨脕脗脘螐螡衛铻庑閺閿闅绾罋网罔硊煨物牾龌齆鼯鼤鹟鹜鹉鹀鶲鶩佤鴮鳚邬鳂鳁鲔鰞鮪鮠鮇隈隗雘雯雾霚霧霨靰慰戊舞艉芄芜芴臒臥苿兀免餵嗡围圩圬坞吴吻吾呙呜味哇唔唯喂汶汪污谓豌貦誤諉位伪伟伍仵亡乌为寤宛完蝄蜿蝸蝟惋惘惟蘁悟恶怃忤忘微往弯䦟皖盌盳睕五尉韈韦韪頠顐尢外妄妩委威娃娓娩娲婉婺我腽蓊袜襪偎倭捂挽挝挖武歪蔚蔿蕰蕹薇薳藯蚊蛙蜈蜗蜼尾屋崴嵬巍巫帏帷幄腕苇辒迋迕违毋辋", + x = "泄泫泶泻洗洫洵浔浠消涎淅淆须顼颉飨晓晰暄暇暹曛曦朽杏析枭枵緒緗線緤縀縣縼繋繫繻纁續纕纖纤线绁细绚绡绣绤绪续餉饧饩饷饻馅馐香馨馴諧謏謝譃譞训讯许诇询详诩诶酗硒硖硝硣渫渲湘溆溪溴漩潇瀣炫烯煊筅筊筱䜣畜痃痫癣祥祫禊禤禧秀稀稄稧稴稸穴穸窨茜荀荇荥荨凶刑削勋勖勰匈匣协卸厢厦县叙叶吁躞軐軒轩闟闲阋陉降限陜羡羲翈習翔翕翖翛翧聟肖肸胁胥絮綊酰醎醑醒醯醺銊銑銒銜銝銷鋅礂祆驯骁骍骧骱髤髹鬚魆魈魖魣揎携撷擤效斜新旋旬旭昔昕星显系紃細絃絏絜矽砉下遐錫鍟鎋鎴鏭鏽鄉鄦鄩柙校栩械楔楦榍榭樨橡檄欣欷莕莘莶菥萧萱葙葸猃猩献獬獯玄现玺瑕璇钘铉铏胸脅脙脩箫箱篂篠簘簫鲞鲟瞎瞓螑蟂蟋蟓蟢蟳蟹蠍蠏血衅衋行衒衔衖衘铣铦销锈锌锡锨幸序庠庥廨闀缃缬缷罅羞硎龤煦熄熊熏熙熹燮燹牺犀狎狭齛鼷黠鹹鹇鸺鸴鸮鸂鷽鷼鷴鷳鴞鳛鲜鱻鱮鱌鰕鯗鮮騽险陷隙險隰雄雟雪需霄霞霫霰靴鞋懈戌興舋舷舾芎芗芯臐臖臭舃舄掀像僖儇兄先兮兴駨饟饈餼窸笑閒閑镶嗅嘘嘻嚣囟圩型埙镟侠邂向吓吸呷咸咻响哓哮唏啸邢邪汹汛汐郋郇郄谐谑谖谞谢谿豀象豨貅賢賯贤鳕鑫鑲鑴鴵鵂訮許訹詗詢詨詳詾諝諠休仙亵享殉乡习宿宵宪宣蝑蝎蝦悻惜想惺矎矄悬悉息恤恂性忻心循徙徐徇形弦歇歆皙盢盨盱相盻省県眩睍睎睱睻喜喧些写寻小粞籼韰韱響項須頊顕顨顯项修信跣跹踃踅蹊墟夏夕奚姓娴婿媳嫌嬉孝学冼粯糈糦糮戏腺膝膮蓄蓆袕袖袨袭裼褎褏褶襄襲西見见觋解觽觿訊訏訓訙訢偕挟蓰蓿蔙蕁蕈蕦蕿薂薛薢薤薪薰藇藓藖藚藛虈虗虚虛虲虾蚬蚿蛸蜁蜆蜥屑屣岘岫峋峡崤巡巷巽希席螅腥腊苋郗郤辖辛迅选逊逍遜選氙", + y = "沂沅油沿泱泳洇洋浴涌涯液预颍颐颖颙颜颺颻飏飖晏晔晕曜曰曳月有杨杳緣緰緷緼縁縈縕縜縯繇繧纡约纭绎飮飲餆養淤淫淹渊渔渝饫饮饴馀馧馻駚諹諺謚謠謡譯議譻譽讉讌讛讞议讶译诒诣语诱谀谊酉硏硬硽碒游湮源溢滟滢演漪漾潆瀛瀹炀炎烊烟烨焉焰焱煜筠筵筼䜩甬由畲疑疟疡疣疫痈痍痒痖瘀瘐瘗瘾瘿癔祎祐祤禋禐禜禦禹禺秧秵移穻窅窈窑窔窨窫窬窯窳茔茚茵荑荢荧荫药刈刖剡劓勇匀医卣印压厌原厣又友右叶吁吆躍軅軈軉軏輰輶輿轝轧闫阅阈阉阎阏阳阴院羠義羭羽羿翊翌翳翼耀耘耰耴耶聿肀肄肊肙育肴胤綖酝酳酽醃醖醞醫醼釉野釔釪鈆鈨鈺鈾銀銦鋆鋊礇礒礜礿祅驛驭驿骃验鬱鬳鬻魇魚魣掾揄揖揠援揶摇撄攸於旖昀易映昱約紻絪砑研䲟䲣䶮一与业严丫逸逾遇遊運遗遙鋺錏錥鍚鍱鎰郵郾鄘鄞鄢酀柚样桠椅椰楹榆樱樾橼檐欤欲欹莚莜莠莸莹莺菸萤营萦萸葉葯葾狱狳狺猗猷猿玉珧琊琰瑗瑛瑜瑶璎用钖钥钰钺铀铔铕铟胭胰脜箢箷箹簃簷籆螘螠螢蟫蟻蠅衍衙衣铫铱银锳幺幼幽应庸庾延异閱閹閻閾闄闉缊缘缢缨罂罃罨羊羑羕砚龠熠熨燕燠爰爷爻牖牙犹狁龑龉龈龂齾齸齮齦齗鼹鼬鼘鼋黿黶黡黟黝麣麀鹽鹰鹬鹦鹞鹓鹆鸳鸯鸭鸦鸢鷹鷧鷣鷖鷃鷁鶢鶠鶍鶃佯佾鴳鴪鴨鴦鴥鴢鴉鳿鳶鳙邮鲬鲉鱿鱼鰫鯒鮨鮣鮋驗驈陨陰陻陽隂隃隅隐隩隱雁雅雍雨雩雲霠霣霪靥靷靿鞅慵懿舣艈艞艤艳艶艷艺艾芋芫芸臾舀舁舆與掖掩允元兖养駰饜饔饐餫餚餘餍竽笎笖籞籥籲镱嘤噎噫因园囿圄圆圉圜圯垠垣垭垸埏域埸堙堰塬镛镒侑依邑邕邘吖吟听吲呀呓员呦咏咦咬咽咿哑哕哟唁唷喁郁邺谒谕谚谣谥谳豫豷貐貤貽賱賹贀贇贋贏贠贻赝赟鳐鐷鐿鑰钇鴹詇詒詠詣誉誘語誾諛佣佚余佑伢伛优伊仰仪以仡亿亦殒殃也乙乐义么寓寅宴宥宜宇蝓蝇蝹蝣蝝蝘蘌藴矣矞矅悦悠悒恿恽恹恙怿怨怡怏忧忆徭御徉役影彦彝引弋弈蓥蓣蓔喻䥺盂盈益盐盓眃眏眙眢眼睚睪臆臃喑亚云于予粥粤粌韗韫音韵韺韻頁頉預頤頨頵顏顒顔顗願顩页倚俨俣俞俑赢越趯跃踊踦踰踴尹尧尤尢墉壅壹夜夤夭央夷奄奕妍妖妤妪姚姨姻娅娱婴媛媵嫣嬴孕冤冶粵腰腴膺蒑蒕蒷袁袘袣裀裔裕裛裺褑褹襾要觎言訑訔訚訝訞訡愈愉意愚愠愿偃挹拽拥押抑扬蓺蔚蕰蕴蕷薀薏薗藀藙藝藥虞蚁蚓蚖蚜蚰蚴蛘蛜蛡蛹蜎蜏蜒蜮蜴尾屹屿岈岩岳峄峪崖崦崾嵛嶷已蝿螈腪腌腋英苡苑苅苃芽郓郢郧辕迂迆迎运迓远迤迻逌遠遥遹遺邀殪殷毅毓氤氧氩氲永轶轺", + z = "治沼沾注泽洙洲浊浙浞涨涿淄衷衹衼颛飐飦智暂曾最朕朝札术朱杂杖杼枕枝枣枳緅緵緻縂縋縡縱總繇繒織繜繤纂纉纗纘纣纵纸纻组织终绉渍渚渣饡馔駋駐諥諮諸謺譄證譔譗識譟譫譸讚讝证诅识诈诊诌诏诛诤诪诸诹诼谆酇酌酎酖碡磔磚湛溱滋滓滞漳潴澡濯灶灼灾炙炷炸烛筑筝筰箏箒甾畛疰疹痄症痔痣瘴瘵祖祗祚祝祩祭祯禃禇禌禎禔禛种租秩秭秲秶秼稓稙稚稡種稹穉穛穜穱窀窄窋窒茈茊茱茲茽茿荘荮凿则制助匝卒卓占卮只召躁躅躜躦転軸輈載輒輖輾轉轧转闸阤阯阵阻阼陟陣翐翟翥者耑职職肇肘肢肫肿胀胄胑胔胗胙胝絷絼綜綴酢酨酯醆醉醊醡重針鈡鉁鉄鉔銌磫磳礈礋祇祉驟驵驺驻骓骔骘骤髒髭髽鬃鬉魳揍揸搌摘摭摺撙撞撮撰擢攒攥支政整斋斟斩斫旃族旨早昃昝昨昭昼紂紖紙紩紫紮紵紾終組矺砖䦶丈专中造週鋷錐錣錾鍐鍼鎭鎮鏱鐏鄑鄟鄣鄫鄹酂柘柞查柱栀栅栈栉株栽桌桎桢桩梓棕棹植椎楂楱榛榨槠樟樽橥莊菆菑菚菹著葘葬葴蒃狰猪獐珍珠琢璋瓒甄甑钟钲钻铚胾脂脏脹箤箦箴箸篆築篜簪簻籀鲝瞕瞩螤螽蟅蟑蠈蠋蠌蠗蠩蠾術铡铢铮铸锃锗锥锧锱锺镃镇幛幢庄座综绽缀缁缒缜缯缵罇罩罪置罾羜砟砦砧砫砸硃照煮燥爪状龇齺齰齜齋齄鼅鼄黹麞麈麆鹯鹧鸼鸷鸩鸇鷷鷟鶎鴲鴙鳷鳣鳟邹鲻鲰鲗鲊鱵鱣鱄鯽鯮鯖鯔鯐鮡驙驇騿騺騣陬陼障隹隻雉雑雜霅震霑鞝憎戆舟舯舳舴艁芖芝芷臓臜臟臢臧自至致臻茁掌掷僮兆兹饘饌餦餟窼竈站竚章竹竺笊笍笜笫笮籒长長镯嘬嘱嘲嘴噪在圳址坐坠埴镞侄侏侦侧邅吒吱周呲咂咋咒咤咨咫咱哉哲哳唑唣啁啄啧啭鐲鐣鐘汁郅邾谘谪谮谵豬貞責貯貲資賊質賳賺贄贅贈贊贞责账质贮贼贽赀赃资赈赒赘赚赜赞赠鑄鑕鑽鑿针钊鵄診註証訾詀詐詛詝詟詹誌諄諈諏諑作佐住伫传众仲仗仔从仉仄殖乙乍之乇主寨宰宙宗宅宁藻惴止正矰知矚瞻恣总怔怎怍忮忪忠志徵征彰彘张蓙蓗蓔喳䦃皂皱盅盏盞盩直眕眞真眥眦眨眹眾着睁膼争再尊粧粥粢粘粍籽韴顓俎俊执扎赭走赵趑趙趦趮趱足趾跂跓跖跱踨踪踬踯踵蹔蹠蹤蹧蹱增壮奏奘妆妯姊姿嫜子字孜孳冢准粻粽糉糌糟战倬腫膱蒸蓁袗袟袠袩装裝製褚褶襍襢覟觜觯偬做值债捉振挣挝挚指拽拶拯择招拙拄折抓找蔁蔗蕞薝藏藢蘸蚤蚱蛀蛛蛭蛰蜇蜘展属屯峙峥崭崽嵫嶂州左帐帙帚帜帧帻腙苲苧苎郑鋕辎辗辙迍这迮追逐這遭遮遵毡氏轴轵轸载轾辀辄" + }, +} + +--[[ you may want to turn off the search regex from showing up + in the header by putting this in `~/.config/yazi/init.lua` ]] function Header:flags() local cwd = self._current.cwd local filter = self._current.files.filter diff --git a/yazi/.config/yazi/plugins/fchar.yazi/main.lua b/yazi/.config/yazi/plugins/fchar.yazi/main.lua index d7c6cea..52e9fce 100644 --- a/yazi/.config/yazi/plugins/fchar.yazi/main.lua +++ b/yazi/.config/yazi/plugins/fchar.yazi/main.lua @@ -1,4 +1,4 @@ ---- @since 25.5.31 +--- @since 26.8.15 local M = {} @@ -23,11 +23,9 @@ local get_config = ya.sync(function(st) skip_symbols = true, -- if {"yazi-"}: f -> file, yazi-file skip_prefix = {}, - -- start: f -> file - -- word: f -> file, also-file - -- all: f -> file, also-file, twofile, elf - search_location = 'start', + fallback = true, aliases = {}, + keys = {}, } end) @@ -47,10 +45,19 @@ end ---@type fun(self, config: FCharConf): nil function M:setup(config) set_config(tbl_deep_extend(get_config(), config)) + + local opts = get_config() + for action, key in pairs(opts.keys) do + km.mgr.rules:insert(1, { on = key, run = 'plugin fchar ' .. action }) + end end --- TODO: process `--flags` -function M:entry() +---@param job Job +function M:entry(job) + if not job.args[1] then + ya.err('type of search not specified') + end + local cands = { { on = '0' }, { on = '1' }, @@ -132,6 +139,7 @@ function M:entry() local loc = { start = '^', + ext = '\\.', word = '(^|\\W)', all = '', } @@ -142,13 +150,15 @@ function M:entry() prefixes = prefixes .. '(' .. prefix .. ')?' end - local re = loc[opts.search_location] - .. ((opts.search_location == 'start' and opts.skip_symbols) and [[\W?]] or '') + local re = loc[job.args[1]] + .. ((job.args[1] == 'start' and opts.skip_symbols) and [[\W?]] or '') .. prefixes .. '[' .. cands[idx].on .. (opts.aliases[cands[idx].on] or '') .. ']' + + -- TODO: fallback to word/all if no match was found if changed(re) then ya.emit('find_do', { re, insensitive = opts.insensitive }) else diff --git a/yazi/.config/yazi/plugins/file-extra-metadata.yazi/main.lua b/yazi/.config/yazi/plugins/file-extra-metadata.yazi/main.lua deleted file mode 100644 index cd37aed..0000000 --- a/yazi/.config/yazi/plugins/file-extra-metadata.yazi/main.lua +++ /dev/null @@ -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 diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/LICENSE b/yazi/.config/yazi/plugins/mediainfo.yazi/LICENSE index 0399f1c..8915680 100644 --- a/yazi/.config/yazi/plugins/mediainfo.yazi/LICENSE +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/LICENSE @@ -1,4 +1,5 @@ Copyright (c) 2024 Lauri Niskanen +Copyright (c) 2025 Huy Hoang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/README.md b/yazi/.config/yazi/plugins/mediainfo.yazi/README.md index 7ad6dc9..dc6df85 100644 --- a/yazi/.config/yazi/plugins/mediainfo.yazi/README.md +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/README.md @@ -13,7 +13,8 @@ This is a Yazi plugin for previewing media files. The preview shows thumbnail using `ffmpeg` if available and media metadata using `mediainfo`. > [!IMPORTANT] -> Minimum version: yazi v25.5.31. +> Minimum version: yazi v26.1.22. +> Check it via command `yazi --debug` ## Preview @@ -70,29 +71,58 @@ Config folder for each OS: https://yazi-rs.github.io/docs/configuration/overview Create `.../yazi/yazi.toml` and add: -> [!IMPORTANT] -> -> For yazi (>=v25.12.29) replace `name` with `url` +`prepend_preloaders` are optional, but highly recommended for faster preview. ```toml [plugin] prepend_preloaders = [ # Replace magick, image, video with mediainfo { mime = "{audio,video,image}/*", run = "mediainfo" }, - { mime = "application/subrip", run = "mediainfo" }, - # Adobe Illustrator, Adobe Photoshop is image/adobe.photoshop, already handled above - { mime = "application/postscript", run = "mediainfo" }, + # .srt subtitle, adobe illustrator, adobe photoshop file + { mime = "application/{subrip,postscript,illustrator,dvb.ait,vnd.adobe.illustrator,eps}", run = "mediainfo" }, + + # Sometimes AI file is recognized as "application/pdf". Lmao. + # In this case use file extension instead: + { url = "*.{ai,eps,ait}", run = "mediainfo" }, + + # Hide metadata by default. + # Example for image mimetype: + { mime = "{image}/*", run = "mediainfo --no-metadata" }, + + # Hide image preview by default. + # Example for video mimetype: + { mime = "{video}/*", run = "mediainfo --no-preview" }, + + # NOTE: Use both --no-metadata and --no-preview will display nothing. :) + # Make sure both of your previewers and preloaders has the same arguments (--no-metadata and --no-preview) ] + prepend_previewers = [ # Replace magick, image, video with mediainfo { mime = "{audio,video,image}/*", run = "mediainfo"}, - { mime = "application/subrip", run = "mediainfo" }, - # Adobe Illustrator, Adobe Photoshop is image/adobe.photoshop, already handled above - { mime = "application/postscript", run = "mediainfo" }, + # .srt subtitle, adobe illustrator, adobe photoshop file + { mime = "application/{subrip,postscript,illustrator,dvb.ait,vnd.adobe.illustrator,eps}", run = "mediainfo" }, + + # Sometimes AI file is recognized as "application/pdf". Lmao. + # In this case use file extension instead: + { url = "*.{ai,eps,ait}", run = "mediainfo" }, + + # Hide metadata by default. + # Example for image mimetype: + { mime = "{image}/*", run = "mediainfo --no-metadata" }, + + # Hide image preview by default. + # Example for video mimetype: + { mime = "{video}/*", run = "mediainfo --no-preview" }, + + # NOTE: Use both --no-metadata and --no-preview will display nothing. :) + # Make sure both of your previewers and preloaders has the same arguments (--no-metadata and --no-preview) ] - # There are more extensions which are supported by mediainfo. + + # There are more extensions, mime types which are supported by mediainfo. # Just add file's MIME type to `previewers`, `preloaders` above. # https://mediaarea.net/en/MediaInfo/Support/Formats + # If it's not working, file an issue at https://github.com/boydaihungst/mediainfo.yazi/issues # For a large file like Adobe Illustrator, Adobe Photoshop, etc # you may need to increase the memory limit if no image is rendered. @@ -102,9 +132,9 @@ Create `.../yazi/yazi.toml` and add: ``` -## Custom theme +## (Optional) Custom theme -Using the same style with spotter. [Read more](https://github.com/sxyazi/yazi/pull/2391) +Using the same style with spotter windows. [Read more](https://github.com/sxyazi/yazi/pull/2391) Edit or add `yazi/theme.toml`: @@ -119,7 +149,39 @@ title = { fg = "green" } tbl_col = { fg = "blue" } ``` -## (Optional) Keymaps to hide metadata and to preview images in full screen +## (Optional) Hide labels or section labels + +This `setup` function and all of its options are optional. +Modify your `~/.config/yazi/init.lua` to include: + +```lua +require("mediainfo"):setup({ + -- Auto hide the lines with these labels + -- Labels are the text with white color in preview images above (without colon ":") + -- Example: To hide `Format: FLAC` => "Format" + -- Default value: + skip_labels = { + "Complete name", + "CompleteName_Last", + "Unique ID", + "File size", + "Format/Info", + "Codec ID/Info", + "MD5 of the unencoded content" + -- "Format" -- Hide all lines with "Format" label + }, + -- skip_labels = false, -- Disable auto hide labels + + + -- Auto hide the section labels + -- Section labels are the text with green color in preview images above + -- Example: To hide `Image` => "Image" + -- Default value: {} + skip_section_labels = { "General", "Image", "Text" }, +}) +``` + +## (Optional) Keymaps to toggle/show/hide/reset metadata and preview image > [!IMPORTANT] > Use any key you want, but make sure there is no conflicts with [default Keybindings](https://github.com/sxyazi/yazi/blob/main/yazi-config/preset/keymap-default.toml). @@ -130,6 +192,23 @@ Or you can use `keymap` to replace all other keys ```toml [mgr] prepend_keymap = [ - { on = "", run = "plugin mediainfo -- toggle-metadata", desc = "Toggle media preview metadata" }, + { on = "", run = "plugin mediainfo -- toggle-metadata", desc = "Toggle media preview metadata" }, + { on = "", run = "plugin mediainfo -- toggle-preview", desc = "Toggle media preview image" }, + + # Hide and show metadata, image + { on = "", run = "plugin mediainfo -- hide-metadata", desc = "Hide media preview metadata" }, + { on = "", run = "plugin mediainfo -- hide-preview", desc = "Hide media preview image" }, + + { on = "", run = "plugin mediainfo -- show-metadata", desc = "Show media preview metadata" }, + { on = "", run = "plugin mediainfo -- show-preview", desc = "Show media preview image" }, + + # Reset to defautl settings in yazi.toml file + { on = "", run = "plugin mediainfo -- reset", desc = "Reset media preview to default settings" }, + + # Use multiple actions + { on = "", run = "plugin mediainfo -- --toggle-preview --toggle-metadata", desc = "Toggle both media preview image and metadata" }, + { on = "", 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 = "", run = "plugin mediainfo -- --show-preview --hide-metadata --reset --show-metadata --hide-preview", desc = "Show media preview image and hide metadata" }, ] ``` diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/adobe-old.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/adobe-old.lua new file mode 100644 index 0000000..f46ee03 --- /dev/null +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/adobe-old.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/adobe-stable.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/adobe-stable.lua new file mode 100644 index 0000000..30a23d6 --- /dev/null +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/adobe-stable.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/audio-old.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/audio-old.lua new file mode 100644 index 0000000..8352c59 --- /dev/null +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/audio-old.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/audio-stable.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/audio-stable.lua new file mode 100644 index 0000000..a6bf677 --- /dev/null +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/audio-stable.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/const.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/const.lua new file mode 100644 index 0000000..cef5c01 --- /dev/null +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/const.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/image-old.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/image-old.lua new file mode 100644 index 0000000..bd644d1 --- /dev/null +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/image-old.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/image-stable.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/image-stable.lua new file mode 100644 index 0000000..13f9227 --- /dev/null +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/image-stable.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/main.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/main.lua index 6082d51..fc064f0 100644 --- a/yazi/.config/yazi/plugins/mediainfo.yazi/main.lua +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/main.lua @@ -1,497 +1,233 @@ ---- @since 25.5.31 - -local skip_labels = { - ["Complete name"] = true, - ["CompleteName_Last"] = true, - ["Unique ID"] = true, - ["File size"] = true, - ["Format/Info"] = true, - ["Codec ID/Info"] = true, - ["MD5 of the unencoded content"] = true, -} - -local ENTRY_ACTION = { - toggle_metadata = "toggle-metadata", -} - -local STATE_KEY = { - units = "units", - hide_metadata = "hide_metadata", - prev_metadata_area = "prev_metadata_area", - prev_peek_data = "prev_peek_data", -} - -local magick_image_mimes = { - avif = true, - hei = true, - heic = true, - heif = true, - ["heif-sequence"] = true, - ["heic-sequence"] = true, - jxl = true, - xml = true, - ["svg+xml"] = true, - ["canon-cr2"] = true, -} - -local seekable_mimes = { - ["application/postscript"] = true, - ["image/adobe.photoshop"] = true, -} +--- @since 26.1.22 local M = {} -local suffix = "_mediainfo" -local SHELL = os.getenv("SHELL") or "" +local using_new_height_api = ui.lines ~= nil +local const +local utils +local adobe +local audio +local image +local video +local none_media_preview -local function is_valid_utf8(str) - return utf8.len(str) ~= nil -end +const = require(".const") +utils = require(".utils") -local function path_quote(path) - if not path or tostring(path) == "" then - return path - end - local result = "'" .. string.gsub(tostring(path), "'", "'\\''") .. "'" - return result -end - -local function read_mediainfo_cached_file(file_path) - -- Open the file in read mode - local file = io.open(file_path, "r") - - if file then - -- Read the entire file content - local content = file:read("*all") - file:close() - return content - end -end - -local set_state = ya.sync(function(state, key, value) - state[key] = value -end) - -local get_state = ya.sync(function(state, key) - return state[key] -end) - -local force_render = ya.sync(function(_, _) - (ui.render or ya.render)() -end) - -local function image_layer_count(job) - local cache = ya.file_cache({ file = job.file, skip = 0 }) - if not cache then - return 0 - end - local layer_count = get_state("f" .. tostring(cache)) - if layer_count then - return layer_count - end - local output, err = Command("identify") - :arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) }) - :output() - if err then - return 0 - end - layer_count = 0 - for line in output.stdout:gmatch("[^\r\n]+") do - if line:match("%S") then - layer_count = layer_count + 1 - end - end - set_state("f" .. tostring(cache), layer_count) - return layer_count +if using_new_height_api then + ya.dbg("mediainfo", "Using yazi version >= 26.5.6") + adobe = require(".adobe-stable") + audio = require(".audio-stable") + image = require(".image-stable") + video = require(".video-stable") + none_media_preview = require(".none-media-preview-stable") +else + ya.dbg("mediainfo", "Using yazi version <= 26.1.22") + adobe = require(".adobe-old") + audio = require(".audio-old") + image = require(".image-old") + video = require(".video-old") + none_media_preview = require(".none-media-preview-old") end function M:peek(job) + -- debounce peek local start = os.clock() - local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 }) + ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock())) + + -- Need mime to decide which module to use if not job.mime then return end - set_state(STATE_KEY.prev_peek_data, { - file = tostring(job.file.path or job.file.cache or job.file.url), - mime = job.mime, - area = { - x = job.area.x, - y = job.area.y, - w = job.area.w, - h = job.area.h, - }, - args = job.args, - skip = job.skip, - }) + + local no_metadata_user_cfg = utils.get_state(const.STATE_KEY.no_metadata) + if no_metadata_user_cfg ~= nil then + job.args.no_metadata = no_metadata_user_cfg + end + + local no_preview_user_cfg = utils.get_state(const.STATE_KEY.no_preview) + if no_preview_user_cfg ~= nil then + job.args.no_preview = no_preview_user_cfg + end + + utils.set_states(const.STATE_KEY.cached_job_args, tostring(job.file.url), job.args) + if job.args.no_preview and job.args.no_metadata then + ya.preview_widget(job, { + ui.Clear(job.area), + }) + return + end + + if job.args.no_preview then + return none_media_preview:peek(job) + end + local is_video = string.find(job.mime, "^video/") local is_audio = string.find(job.mime, "^audio/") local is_image = string.find(job.mime, "^image/") - local is_seekable = seekable_mimes[job.mime] or is_video - local cache_img_url = (is_audio or is_image) and cache_img_url_no_skip + local is_adobe = const.seekable_mimes[job.mime] - if is_seekable then - cache_img_url = ya.file_cache(job) - end - local preload_status, preload_err = self:preload(job) - if not preload_status then - return - end - ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock())) - local hide_metadata = get_state(STATE_KEY.hide_metadata) - local mediainfo_height = 0 - local lines = {} - local limit = job.area.h - local last_line = 0 - local is_wrap = rt.preview.wrap == "yes" - if not hide_metadata then - local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. suffix - local output = read_mediainfo_cached_file(cache_mediainfo_path) - if output then - local max_width = math.max(1, job.area.w) - if output:match("^Error:") then - job.args.force_reload_mediainfo = true - preload_status, preload_err = self:preload(job) - if not preload_status or preload_err then - return - end - output = read_mediainfo_cached_file(cache_mediainfo_path) - end - - for str in output:gsub("\n+$", ""):gmatch("[^\n]*") do - local label, value = str:match("(.*[^ ]) +: (.*)") - local line - if label then - if not skip_labels[label] then - line = ui.Line({ - ui.Span(label .. ": "):style(ui.Style():fg("reset"):bold()), - ui.Span(value):style(th.spot.tbl_col or ui.Style():fg("blue")), - }) - end - elseif str ~= "General" then - line = ui.Line({ ui.Span(str):style(th.spot.title or ui.Style():fg("green")) }) - end - - if line then - local line_height = math.max(1, is_wrap and math.ceil(ui.width(line) / max_width) or 1) - if (last_line + line_height) > job.skip then - table.insert(lines, line) - end - if (last_line + line_height) >= job.skip + limit then - last_line = job.skip + limit - break - end - last_line = last_line + line_height - end - end - end - mediainfo_height = math.min(limit, last_line) - end - - if - (job.skip > 0 and #lines == 0 and not hide_metadata) - and ( - not is_seekable - or (is_video and job.skip >= 90) - or ( - (job.mime == "image/adobe.photoshop" or job.mime == "application/postscript") - and image_layer_count(job) - < (1 + math.floor( - math.max(0, get_state(STATE_KEY.units) and (job.skip / get_state(STATE_KEY.units)) or 0) - )) - ) - ) - then - ya.emit("peek", { - math.max(0, job.skip - (get_state(STATE_KEY.units) or limit)), - only_if = job.file.url, - upper_bound = true, - }) - return - end - force_render() - -- NOTE: Hacky way to prevent image overlap with old metadata area - if hide_metadata and get_state(STATE_KEY.prev_metadata_area) then - ya.preview_widget(job, { - ui.Clear(ui.Rect(get_state(STATE_KEY.prev_metadata_area))), - }) - end - local rendered_img_rect = cache_img_url - and fs.cha(cache_img_url) - and ya.image_show( - cache_img_url, - ui.Rect({ - x = job.area.x, - y = job.area.y, - w = job.area.w, - h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h, - }) - ) - or nil - local image_height = rendered_img_rect and rendered_img_rect.h or 0 - - -- NOTE: Workaround case audio has no cover image. Prevent regenerate preview image - if is_audio and image_height == 1 then - local info = ya.image_info(cache_img_url) - if not info or (info.w == 1 and info.h == 1) then - image_height = 0 - end - end - - -- NOTE: Workaround case video.lua doesn't doesn't generate preview image because of `skip` overflow video duration - if is_video and not rendered_img_rect then - image_height = math.max(job.area.h - mediainfo_height, 0) - end - - -- Handle image preload error - if preload_err then - table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red"))) - end - - ya.preview_widget(job, { - ui.Text(lines) - :area(ui.Rect({ - x = job.area.x, - y = job.area.y + image_height, - w = job.area.w, - h = job.area.h - image_height, - })) - :wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO), - }) - -- NOTE: Hacky way to prevent image overlap with old metadata area - if not hide_metadata then - set_state(STATE_KEY.prev_metadata_area, { - x = job.area.x, - y = job.area.y + image_height, - w = job.area.w, - h = job.area.h - image_height, - }) + if is_adobe then + return adobe:peek(job) + elseif is_image then + return image:peek(job) + elseif is_video then + return video:peek(job) + elseif is_audio then + return audio:peek(job) + else + return none_media_preview:peek(job) end end function M:seek(job) local h = cx.active.current.hovered if h and h.url == job.file.url then - set_state(STATE_KEY.units, job.units) + utils.set_state(const.STATE_KEY.units, job.units) ya.emit("peek", { math.max(0, cx.active.preview.skip + job.units), only_if = job.file.url, }) end end - -function M:re_peek() - local prev_peek_data = get_state(STATE_KEY.prev_peek_data) - if prev_peek_data then - prev_peek_data.file = File({ - url = Url(prev_peek_data.file), - cha = fs.cha(Url(prev_peek_data.file)), - }) - prev_peek_data.area = ui.area and ui.area("preview") or ui.Rect(prev_peek_data.area) - - self:peek(prev_peek_data) - end -end - function M:preload(job) local cache_img_url = ya.file_cache({ file = job.file, skip = 0 }) if not cache_img_url then + ya.dbg("mediainfo", "Can't access yazi cache folder") return true end - local cache_mediainfo_url = Url(tostring(cache_img_url) .. suffix) - cache_img_url = seekable_mimes[job.mime] and ya.file_cache(job) or cache_img_url - local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url) - local err_msg = "" - local is_valid_utf8_path = is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url)) - -- video mimetype - if job.mime then - if string.find(job.mime, "^video/") then - local cache_img_status, video_preload_err = require("video"):preload(job) - if not cache_img_status and video_preload_err then - err_msg = err_msg - .. string.format("Failed to start `%s`, Do you have `%s` installed?\n", "ffmpeg", "ffmpeg") - end - -- audo and image mimetype - elseif cache_img_url and (not cache_img_url_cha or cache_img_url_cha.len <= 0) then - -- audio - if string.find(job.mime, "^audio/") then - local qv = 31 - math.floor(rt.preview.image_quality * 0.3) - local audio_preload_output, audio_preload_err = Command("ffmpeg"):arg({ - "-v", - "error", - "-threads", - 1, - "-skip_frame", - "nokey", - "-an", - "-sn", - "-dn", - "-i", - tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url), - "-vframes", - 1, - "-q:v", - qv, - "-vf", - string.format("scale=-1:'min(%d,ih)':flags=fast_bilinear", rt.preview.max_height / 2), - "-f", - "image2", - "-y", - tostring(cache_img_url), - }):output() - -- NOTE: Some audio types doesn't have cover image -> error "" - if - (audio_preload_output and audio_preload_output.stderr ~= nil and audio_preload_output.stderr ~= "") - or audio_preload_err - then - err_msg = err_msg - .. string.format("Failed to start `%s`, Do you have `%s` installed?\n", "ffmpeg", "ffmpeg") - else - cache_img_url_cha, _ = fs.cha(cache_img_url) - if not cache_img_url_cha then - -- NOTE: Workaround case audio has no cover image. Prevent regenerate preview image - audio_preload_output, audio_preload_err = require("magick") - .with_limit() - :arg({ - "-size", - "1x1", - "canvas:none", - string.format("PNG32:%s", cache_img_url), - }) - :output() - if - (audio_preload_output.stderr ~= nil and audio_preload_output.stderr ~= "") - or audio_preload_err - then - err_msg = err_msg - .. string.format( - "Failed to start `%s`, Do you have `%s` installed?\n", - "magick", - "magick" - ) - end - end - end - -- image - elseif string.find(job.mime, "^image/") or job.mime == "application/postscript" then - local svg_plugin_ok, svg_plugin = pcall(require, "svg") - local _, magick_plugin = pcall(require, "magick") - local mime = job.mime:match(".*/(.*)$") - - local image_plugin = magick_image_mimes[mime] - and ((mime == "svg+xml" and svg_plugin_ok) and svg_plugin or magick_plugin) - or require("image") - - local cache_img_status, image_preload_err - -- psd, ai, eps - if mime == "adobe.photoshop" or job.mime == "application/postscript" then - local layer_index = 0 - local units = get_state(STATE_KEY.units) - if units ~= nil then - local max_layer = image_layer_count(job) - layer_index = math.floor(math.max(0, job.skip / units)) - if layer_index + 1 > max_layer then - layer_index = max_layer - 1 - end - end - local cache_img_url_tmp = Url(cache_img_url .. ".tmp") - if fs.cha(cache_img_url_tmp) then - fs.remove("file", cache_img_url_tmp) - end - local tmp_file_path, _ = fs.unique_name(cache_img_url_tmp) - cache_img_status, image_preload_err = magick_plugin - .with_limit() - :arg({ - "-background", - "none", - tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) - .. "[" - .. tostring(layer_index) - .. "]", - "-auto-orient", - "-strip", - "-resize", - string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height), - "-quality", - rt.preview.image_quality, - string.format("PNG32:%s", tostring(tmp_file_path)), - }) - :status() - if cache_img_status then - os.rename(tostring(tmp_file_path), tostring(cache_img_url)) - end - elseif mime == "svg+xml" and not is_valid_utf8_path then - local cache_img_url_tmp = Url(cache_img_url .. ".tmp") - if fs.cha(cache_img_url_tmp) then - fs.remove("file", cache_img_url_tmp) - end - local tmp_file_path, _ = fs.unique_name(cache_img_url_tmp) - -- svg under invalid utf8 path - cache_img_status, image_preload_err = magick_plugin - .with_limit() - :arg({ - "-background", - "none", - tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url), - "-auto-orient", - "-strip", - "-flatten", - "-resize", - string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height), - "-quality", - rt.preview.image_quality, - string.format("PNG32:%s", tostring(tmp_file_path)), - }) - :status() - if cache_img_status then - os.rename(tostring(tmp_file_path), tostring(cache_img_url)) - end - else - -- other image - local no_skip_job = { skip = 0, file = job.file, args = {} } - cache_img_status, image_preload_err = image_plugin:preload(no_skip_job) - end - if not cache_img_status then - err_msg = err_msg .. (image_preload_err and (tostring(image_preload_err)) or "") - end - end - end + if not job.mime then + return false end - local cache_mediainfo_cha = fs.cha(cache_mediainfo_url) - if cache_mediainfo_cha and not job.args.force_reload_mediainfo then - return true, err_msg ~= "" and ("Error: " .. err_msg) or nil + + local no_metadata_user_cfg = utils.get_state(const.STATE_KEY.no_metadata) + if no_metadata_user_cfg ~= nil then + job.args.no_metadata = no_metadata_user_cfg end - local cmd = "mediainfo" - local output, err - if is_valid_utf8_path then - output, err = Command(cmd) - :arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) }) - :output() + + local no_preview_user_cfg = utils.get_state(const.STATE_KEY.no_preview) + if no_preview_user_cfg ~= nil then + job.args.no_preview = no_preview_user_cfg + end + + if job.args.no_preview and job.args.no_metadata then + return false, nil + end + if job.args.no_preview then + return none_media_preview:preload(job) + end + + local is_video = string.find(job.mime, "^video/") + local is_audio = string.find(job.mime, "^audio/") + local is_image = string.find(job.mime, "^image/") + local is_adobe = const.seekable_mimes[job.mime] + + if is_adobe then + return adobe:preload(job) + elseif is_image then + return image:preload(job) + elseif is_video then + return video:preload(job) + elseif is_audio then + return audio:preload(job) else - cmd = "cd " - .. path_quote(job.file.path or job.file.cache or (job.file.url.path or job.file.url).parent) - .. " && " - .. cmd - .. " " - .. path_quote(tostring(job.file.path or job.file.cache or job.file.url.name)) - output, err = Command(SHELL) - :arg({ "-c", cmd }) - :arg({ tostring(job.file.path or job.file.cache or (job.file.url.path or job.file.url)) }) - :output() + return none_media_preview:preload(job) end - if err then - err_msg = err_msg .. string.format("Failed to start `%s`, Do you have `%s` installed?\n", cmd, cmd) +end + +function M:setup(opts) + if not opts or type(opts) ~= "table" then + return end - return fs.write( - cache_mediainfo_url, - (err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "") + + if type(opts.skip_labels) == "table" then + opts.skip_labels = utils.tbl_to_set(utils.tbl_unique_strings(opts.skip_labels)) + end + + if type(opts.skip_section_labels) == "table" then + opts.skip_section_labels = utils.tbl_to_set(utils.tbl_unique_strings(opts.skip_section_labels)) + end + + utils.set_state( + const.STATE_KEY.skip_labels, + type(opts.skip_labels) == "table" and opts.skip_labels + or (opts.skip_labels == false and {} or const.skip_labels) + ) + + utils.set_state( + const.STATE_KEY.skip_section_labels, + type(opts.skip_section_labels) == "table" and opts.skip_section_labels or {} ) end function M:entry(job) local action = job.args[1] + if action == const.ENTRY_ACTION.reset or job.args.reset then + utils.set_state(const.STATE_KEY.no_preview, nil) + utils.set_state(const.STATE_KEY.no_metadata, nil) + ya.emit("peek", { + force = true, + }) + return + end - if action == ENTRY_ACTION.toggle_metadata then - set_state(STATE_KEY.hide_metadata, not get_state(STATE_KEY.hide_metadata)) - M:re_peek() + if action == const.ENTRY_ACTION.show_metadata or job.args.show_metadata then + utils.set_state(const.STATE_KEY.no_metadata, false) + ya.emit("peek", { + force = true, + }) + end + + if action == const.ENTRY_ACTION.show_preview or job.args.show_preview then + utils.set_state(const.STATE_KEY.no_preview, false) + ya.emit("peek", { + force = true, + }) + end + + if action == const.ENTRY_ACTION.hide_metadata or job.args.hide_metadata then + utils.set_state(const.STATE_KEY.no_metadata, true) + ya.emit("peek", { + force = true, + }) + end + if action == const.ENTRY_ACTION.hide_preview or job.args.hide_preview then + utils.set_state(const.STATE_KEY.no_preview, true) + ya.emit("peek", { + force = true, + }) + end + + job.retry = job.retry or 0 + if job.retry > 5 then + utils.error("Preview data is loading, try again later") + return + end + + local current_file = utils.current_file() + if not current_file then + ya.sleep(0.1) + job.retry = job.retry + 1 + return self:entry(job) + end + + local job_args = utils.get_states(const.STATE_KEY.cached_job_args, current_file) + if not job_args then + ya.sleep(0.1) + job.retry = job.retry + 1 + return self:entry(job) + end + + if action == const.ENTRY_ACTION.toggle_metadata or job.args.toggle_metadata then + utils.set_state(const.STATE_KEY.no_metadata, not job_args.no_metadata) + ya.emit("peek", { + force = true, + }) + end + if action == const.ENTRY_ACTION.toggle_preview or job.args.toggle_preview then + utils.set_state(const.STATE_KEY.no_preview, not job_args.no_preview) + ya.emit("peek", { + force = true, + }) end end diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/none-media-preview-old.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/none-media-preview-old.lua new file mode 100644 index 0000000..b88a9ec --- /dev/null +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/none-media-preview-old.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/none-media-preview-stable.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/none-media-preview-stable.lua new file mode 100644 index 0000000..11e09ca --- /dev/null +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/none-media-preview-stable.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/utils.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/utils.lua new file mode 100644 index 0000000..ea969ca --- /dev/null +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/utils.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/video-old.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/video-old.lua new file mode 100644 index 0000000..6f02de9 --- /dev/null +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/video-old.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/mediainfo.yazi/video-stable.lua b/yazi/.config/yazi/plugins/mediainfo.yazi/video-stable.lua new file mode 100644 index 0000000..4e174eb --- /dev/null +++ b/yazi/.config/yazi/plugins/mediainfo.yazi/video-stable.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/mime-ext.yazi/README.md b/yazi/.config/yazi/plugins/mime-ext.yazi/README.md index 65c6d20..b0b0401 100644 --- a/yazi/.config/yazi/plugins/mime-ext.yazi/README.md +++ b/yazi/.config/yazi/plugins/mime-ext.yazi/README.md @@ -1,6 +1,6 @@ # 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. @@ -16,10 +16,16 @@ Add this to your `~/.config/yazi/yazi.toml`: ```toml [[plugin.prepend_fetchers]] -id = "mime" -url = "*" -run = "mime-ext" -prio = "high" +url = "local://*" +run = "mime-ext.local" +prio = "high" +group = "mime" + +[[plugin.prepend_fetchers]] +url = "remote://*" +run = "mime-ext.remote" +prio = "high" +group = "mime" ``` ## Advanced @@ -27,21 +33,25 @@ prio = "high" You can also customize it in your `~/.config/yazi/init.lua` with: ```lua -require("mime-ext"):setup { - -- Expand the existing filename database (lowercase), for example: +require("mime-ext.local"):setup { + -- Expand the default filename database (lowercase), for example: with_files = { makefile = "text/makefile", -- ... }, - -- Expand the existing extension database (lowercase), for example: + -- Expand the default extension database (lowercase), for example: with_exts = { mk = "text/makefile", -- ... }, - -- If the mime-type is not in both filename and extension databases, - -- then fallback to Yazi's preset `mime` plugin, which uses `file(1)` + -- 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, } ``` @@ -49,7 +59,7 @@ require("mime-ext"):setup { ## TODO - Add more file types (PRs welcome!). -- Compress mime-type tables. +- Compress MIME type tables. ## License diff --git a/yazi/.config/yazi/plugins/mime-ext.yazi/local.lua b/yazi/.config/yazi/plugins/mime-ext.yazi/local.lua new file mode 100644 index 0000000..ae0fd1e --- /dev/null +++ b/yazi/.config/yazi/plugins/mime-ext.yazi/local.lua @@ -0,0 +1,1146 @@ +--- @since 26.8.15 +--- See https://www.iana.org/assignments/media-types/media-types.xhtml + +local FILES = { + [".envrc"] = "text/plain", + [".gitconfig"] = "text/plain", + [".gitignore"] = "text/plain", + [".luacheckrc"] = "text/lua", + [".npmrc"] = "text/plain", + [".styluaignore"] = "text/plain", + [".zshenv"] = "text/plain", + [".zshrc"] = "text/plain", + ["cargo.lock"] = "application/json", + ["flake.lock"] = "application/json", + license = "text/plain", +} + +local EXTS = { + ["123"] = "application/lotus-1-2-3", + ["3dml"] = "text/in3d.3dml", + ["3ds"] = "image/3ds", + ["3g2"] = "video/3gpp2", + ["3gp"] = "video/3gpp", + ["3mf"] = "model/3mf", + ["7z"] = "application/7z-compressed", + ["for"] = "text/fortran", + ["in"] = "text/plain", + ["n-gage"] = "application/nokia.n-gage.symbian.install", + ["sfd-hdstx"] = "application/hydrostatix.sof-data", + aab = "application/authorware-bin", + aac = "audio/aac", + aam = "application/authorware-map", + aas = "application/authorware-seg", + abw = "application/abiword", + ac = "application/pkix-attr-cert", + acc = "application/americandynamics.acc", + ace = "application/ace-compressed", + acu = "application/acucobol", + acutc = "application/acucorp", + adp = "audio/adpcm", + aep = "application/audiograph", + afm = "application/font-type1", + afp = "application/ibm.modcap", + ahead = "application/ahead.space", + ahk = "text/plain", + ai = "application/postscript", + aif = "audio/aiff", + aifc = "audio/aiff", + aiff = "audio/aiff", + air = "application/adobe.air-application-installer-package+zip", + ait = "application/dvb.ait", + ami = "application/amiga.ami", + apk = "application/android.package-archive", + appcache = "text/cache-manifest", + application = "application/ms-application", + apr = "application/lotus-approach", + arc = "application/freearc", + asc = "application/pgp-signature", + asf = "video/ms-asf", + asm = "text/asm", + aso = "application/accpac.simply.aso", + ass = "text/ass", + asx = "video/ms-asf", + atc = "application/acucorp", + atom = "application/atom+xml", + atomcat = "application/atomcat+xml", + atomsvc = "application/atomsvc+xml", + atx = "application/antix.game-component", + au = "audio/basic", + avi = "video/msvideo", + avif = "image/avif", + aw = "application/applixware", + azf = "application/airzip.filesecure.azf", + azs = "application/airzip.filesecure.azs", + azw = "application/amazon.ebook", + bash = "text/shellscript", + bat = "application/msdownload", + bcpio = "application/bcpio", + bdf = "application/font-bdf", + bdm = "application/syncml.dm+wbxml", + bean = "text/plain", + beancount = "text/plain", + bed = "application/realvnc.bed", + bh2 = "application/fujitsu.oasysprs", + bin = "application/octet-stream", + blb = "application/blorb", + blend = "application/blender", + blend1 = "application/blender", + blorb = "application/blorb", + bmi = "application/bmi", + bmp = "image/bmp", + book = "application/framemaker", + box = "application/previewsystems.box", + boz = "application/bzip2", + bpk = "application/octet-stream", + btif = "image/prs.btif", + bz = "application/bzip", + bz2 = "application/bzip2", + c = "text/c", + c11amc = "application/cluetrust.cartomobile-config", + c11amz = "application/cluetrust.cartomobile-config-pkg", + c4d = "application/clonk.c4group", + c4f = "application/clonk.c4group", + c4g = "application/clonk.c4group", + c4p = "application/clonk.c4group", + c4u = "application/clonk.c4group", + cab = "application/ms-cab-compressed", + caf = "audio/caf", + cap = "application/tcpdump.pcap", + car = "application/curl.car", + cat = "application/ms-pki.seccat", + cb7 = "application/cbr", + cba = "application/cbr", + cbr = "application/cbr", + cbt = "application/cbr", + cbz = "application/cbr", + cc = "text/c", + cct = "application/director", + ccxml = "application/ccxml+xml", + cdbcmsg = "application/contact.cmsg", + cdf = "application/netcdf", + cdkey = "application/mediastation.cdkey", + cdmia = "application/cdmi-capability", + cdmic = "application/cdmi-container", + cdmid = "application/cdmi-domain", + cdmio = "application/cdmi-object", + cdmiq = "application/cdmi-queue", + cdx = "chemical/cdx", + cdxml = "application/chemdraw+xml", + cdy = "application/cinderella", + cer = "application/pkix-cert", + cfg = "text/plain", + cfs = "application/cfs-compressed", + cgm = "image/cgm", + chat = "application/chat", + chm = "application/ms-htmlhelp", + chrt = "application/kde.kchart", + cif = "chemical/cif", + cii = "application/anser-web-certificate-issue-initiation", + cil = "application/ms-artgalry", + cla = "application/claymore", + class = "application/java-vm", + clkk = "application/crick.clicker.keyboard", + clkp = "application/crick.clicker.palette", + clkt = "application/crick.clicker.template", + clkw = "application/crick.clicker.wordbank", + clkx = "application/crick.clicker", + clp = "application/msclip", + cmc = "application/cosmocaller", + cmdf = "chemical/cmdf", + cml = "chemical/cml", + cmp = "application/yellowriver-custom-menu", + cmx = "image/cmx", + cod = "application/rim.cod", + com = "application/msdownload", + conf = "text/plain", + cpio = "application/cpio", + cpp = "text/c", + cpt = "application/mac-compactpro", + crd = "application/mscardfile", + crl = "application/pkix-crl", + crt = "application/x509-ca-cert", + cryptonote = "application/rig.cryptonote", + csh = "application/csh", + csml = "chemical/csml", + csp = "application/commonspace", + css = "text/css", + cst = "application/director", + csv = "text/csv", + cu = "application/cu-seeme", + curl = "text/curl", + cww = "application/prs.cww", + cxt = "application/director", + cxx = "text/c", + dae = "model/collada+xml", + daf = "application/mobius.daf", + dart = "application/dart", + dataless = "application/fdsn.seed", + davmount = "application/davmount+xml", + dbk = "application/docbook+xml", + dcr = "application/director", + dcurl = "text/curl.dcurl", + dd2 = "application/oma.dd2+xml", + ddd = "application/fujixerox.ddd", + deb = "application/debian-package", + def = "text/plain", + deploy = "application/octet-stream", + der = "application/x509-ca-cert", + dfac = "application/dreamfactory", + dgc = "application/dgc-compressed", + dic = "text/c", + dir = "application/director", + dis = "application/mobius.dis", + dist = "application/octet-stream", + distz = "application/octet-stream", + djv = "image/djvu", + djvu = "image/djvu", + dll = "application/msdownload", + dmg = "application/apple-diskimage", + dmp = "application/tcpdump.pcap", + dms = "application/octet-stream", + dna = "application/dna", + doc = "application/msword", + docm = "application/ms-word.document.macroenabled.12", + docx = "application/openxmlformats-officedocument.wordprocessingml.document", + dot = "application/msword", + dotm = "application/ms-word.template.macroenabled.12", + dotx = "application/openxmlformats-officedocument.wordprocessingml.template", + dp = "application/osgi.dp", + dpg = "application/dpgraph", + dra = "audio/dra", + dsc = "text/prs.lines.tag", + dssc = "application/dssc+der", + dtb = "application/dtbook+xml", + dtd = "application/xml-dtd", + dts = "audio/dts", + dtshd = "audio/dts.hd", + dump = "application/octet-stream", + dvb = "video/dvb.file", + dvi = "application/dvi", + dwf = "model/dwf", + dwg = "image/dwg", + dxf = "image/dxf", + dxp = "application/spotfire.dxp", + dxr = "application/director", + ebuild = "application/gentoo.ebuild", + ecelp4800 = "audio/nuera.ecelp4800", + ecelp7470 = "audio/nuera.ecelp7470", + ecelp9600 = "audio/nuera.ecelp9600", + eclass = "application/gentoo.eclass", + ecma = "application/ecmascript", + edm = "application/novadigm.edm", + edx = "application/novadigm.edx", + efif = "application/picsel", + ei6 = "application/pg.osasli", + el = "text/plain", + elc = "application/octet-stream", + emf = "application/msmetafile", + eml = "message/rfc822", + emma = "application/emma+xml", + emz = "application/msmetafile", + env = "text/plain", + eol = "audio/digital-winds", + eot = "application/ms-fontobject", + eps = "application/postscript", + epub = "application/epub+zip", + es3 = "application/eszigno3+xml", + esa = "application/osgi.subsystem", + esf = "application/epson.esf", + et3 = "application/eszigno3+xml", + etx = "text/setext", + eva = "application/eva", + evy = "application/envoy", + exe = "application/msdownload", + exi = "application/exi", + ext = "application/novadigm.ext", + ez = "application/andrew-inset", + ez2 = "application/ezpix-album", + ez3 = "application/ezpix-package", + f = "text/fortran", + f4v = "video/f4v", + f77 = "text/fortran", + f90 = "text/fortran", + fbs = "image/fastbidsheet", + fcdt = "application/adobe.formscentral.fcdt", + fcs = "application/isac.fcs", + fdf = "application/fdf", + fe_launch = "application/denovo.fcselayout-link", + fg5 = "application/fujitsu.oasysgp", + fgd = "application/director", + fh = "image/freehand", + fh4 = "image/freehand", + fh5 = "image/freehand", + fh7 = "image/freehand", + fhc = "image/freehand", + fig = "application/xfig", + fish = "text/shellscript", + flac = "audio/flac", + fli = "video/fli", + flo = "application/micrografx.flo", + flv = "video/flv", + flw = "application/kde.kivio", + flx = "text/fmi.flexstor", + fly = "text/fly", + fm = "application/framemaker", + fnc = "application/frogans.fnc", + fpx = "image/fpx", + frame = "application/framemaker", + fsc = "application/fsc.weblaunch", + fst = "image/fst", + ftc = "application/fluxtime.clip", + fti = "application/anser-web-funds-transfer-initiation", + fvt = "video/fvt", + fxp = "application/adobe.fxp", + fxpl = "application/adobe.fxp", + fzs = "application/fuzzysheet", + g2w = "application/geoplan", + g3 = "image/g3fax", + g3w = "application/geospace", + gac = "application/groove-account", + gam = "application/tads", + gbr = "application/rpki-ghostbusters", + gca = "application/gca-compressed", + gdl = "model/gdl", + geo = "application/dynageo", + gex = "application/geometry-explorer", + ggb = "application/geogebra.file", + ggs = "application/geogebra.slides", + ggt = "application/geogebra.tool", + ghf = "application/groove-help", + gif = "image/gif", + gim = "application/groove-identity-message", + gml = "application/gml+xml", + gmx = "application/gmx", + gnumeric = "application/gnumeric", + go = "text/go", + gph = "application/flographit", + gpx = "application/gpx+xml", + gqf = "application/grafeq", + gqs = "application/grafeq", + gram = "application/srgs", + gramps = "application/gramps-xml", + gre = "application/geometry-explorer", + grv = "application/groove-injector", + grxml = "application/srgs+xml", + gsf = "application/font-ghostscript", + gtar = "application/gtar", + gtm = "application/groove-tool-message", + gtw = "model/gtw", + gv = "text/graphviz", + gxf = "application/gxf", + gxt = "application/geonext", + gz = "application/gzip", + h = "text/c", + h261 = "video/h261", + h263 = "video/h263", + h264 = "video/h264", + hal = "application/hal+xml", + hbci = "application/hbci", + hcl = "text/hcl", + hdf = "application/hdf", + hh = "text/c", + hlp = "application/winhlp", + hpgl = "application/hp-hpgl", + hpid = "application/hp-hpid", + hpp = "text/c", + hps = "application/hp-hps", + hqx = "application/mac-binhex40", + htke = "application/kenameaapp", + htm = "text/html", + html = "text/html", + hvd = "application/yamaha.hv-dic", + hvp = "application/yamaha.hv-voice", + hvs = "application/yamaha.hv-script", + i2g = "application/intergeo", + icc = "application/iccprofile", + ice = "conference/cooltalk", + icm = "application/iccprofile", + ico = "image/icon", + ics = "text/calendar", + ief = "image/ief", + ifb = "text/calendar", + ifm = "application/shana.informed.formdata", + iges = "model/iges", + igl = "application/igloader", + igm = "application/insors.igm", + igs = "model/iges", + igx = "application/micrografx.igx", + iif = "application/shana.informed.interchange", + imp = "application/accpac.simply.imp", + ims = "application/ms-ims", + ini = "text/plain", + ink = "application/inkml+xml", + inkml = "application/inkml+xml", + install = "application/install-instructions", + iota = "application/astraea-software.iota", + ipfix = "application/ipfix", + ipk = "application/shana.informed.package", + irm = "application/ibm.rights-management", + irp = "application/irepository.package+xml", + iso = "application/iso9660-image", + itp = "application/shana.informed.formtemplate", + ivp = "application/immervision-ivp", + ivu = "application/immervision-ivu", + jad = "text/sun.j2me.app-descriptor", + jam = "application/jam", + jar = "application/java-archive", + java = "text/java-source", + jisp = "application/jisp", + jlt = "application/hp-jlyt", + jnlp = "application/java-jnlp-file", + joda = "application/joost.joda-archive", + jpe = "image/jpeg", + jpeg = "image/jpeg", + jpg = "image/jpeg", + jpgm = "video/jpm", + jpgv = "video/jpeg", + jpm = "video/jpm", + js = "text/javascript", + json = "application/json", + jsonc = "application/json", + jsonml = "application/jsonml+json", + jsx = "text/jsx", + jxl = "image/jxl", + kar = "audio/midi", + karbon = "application/kde.karbon", + kfo = "application/kde.kformula", + kia = "application/kidspiration", + kml = "application/google-earth.kml+xml", + kmz = "application/google-earth.kmz", + kne = "application/kinar", + knp = "application/kinar", + kon = "application/kde.kontour", + kpr = "application/kde.kpresenter", + kpt = "application/kde.kpresenter", + kpxx = "application/ds-keypoint", + kra = "application/krita", + ["kra~"] = "application/krita", + ksp = "application/kde.kspread", + ktr = "application/kahootz", + ktx = "image/ktx", + ktz = "application/kahootz", + kwd = "application/kde.kword", + kwt = "application/kde.kword", + lasxml = "application/las.las+xml", + latex = "application/latex", + lbd = "application/llamagraphics.life-balance.desktop", + lbe = "application/llamagraphics.life-balance.exchange+xml", + les = "application/hhe.lesson-player", + lha = "application/lzh-compressed", + link66 = "application/route66.link66+xml", + list = "text/plain", + list3820 = "application/ibm.modcap", + listafp = "application/ibm.modcap", + lnk = "application/ms-shortcut", + log = "text/plain", + lostxml = "application/lost+xml", + lrf = "application/octet-stream", + lrm = "application/ms-lrm", + lrz = "application/lrzip", + ltf = "application/frogans.ltf", + lua = "text/lua", + lvp = "audio/lucent.voice", + lwp = "application/lotus-wordpro", + lz = "application/lzip", + lz4 = "application/lz4", + lzh = "application/lzh-compressed", + lzma = "application/lzma", + lzo = "application/lzop", + m13 = "application/msmediaview", + m14 = "application/msmediaview", + m1v = "video/mpeg", + m21 = "application/mp21", + m2a = "audio/mpeg", + m2t = "video/mp2t", + m2ts = "video/mp2t", + m2v = "video/mpeg", + m3a = "audio/mpeg", + m3u = "audio/mpegurl", + m3u8 = "application/apple.mpegurl", + m4a = "audio/mp4", + m4u = "video/mpegurl", + m4v = "video/m4v", + ma = "application/mathematica", + mads = "application/mads+xml", + mag = "application/ecowin.chart", + maker = "application/framemaker", + man = "text/troff", + mar = "application/octet-stream", + mathml = "application/mathml+xml", + mb = "application/mathematica", + mbk = "application/mobius.mbk", + mbox = "application/mbox", + mc1 = "application/medcalcdata", + mcd = "application/mcd", + mcurl = "text/curl.mcurl", + md = "text/markdown", + mdb = "application/msaccess", + mdi = "image/ms-modi", + me = "text/troff", + mesh = "model/mesh", + meta4 = "application/metalink4+xml", + metalink = "application/metalink+xml", + mets = "application/mets+xml", + mfm = "application/mfmp", + mft = "application/rpki-manifest", + mgp = "application/osgeo.mapguide.package", + mgz = "application/proteus.magazine", + mid = "audio/midi", + midi = "audio/midi", + mie = "application/mie", + mif = "application/mif", + mime = "message/rfc822", + mj2 = "video/mj2", + mjp2 = "video/mj2", + mjs = "text/javascript", + mk3d = "video/matroska", + mka = "audio/matroska", + mks = "video/matroska", + mkv = "video/matroska", + mlp = "application/dolby.mlp", + mmd = "application/chipnuts.karaoke-mmd", + mmf = "application/smaf", + mmr = "image/fujixerox.edmics-mmr", + mng = "video/mng", + mny = "application/msmoney", + mobi = "application/mobipocket-ebook", + mods = "application/mods+xml", + mov = "video/quicktime", + movie = "video/sgi-movie", + mp2 = "audio/mpeg", + mp21 = "application/mp21", + mp2a = "audio/mpeg", + mp3 = "audio/mpeg", + mp4 = "video/mp4", + mp4a = "audio/mp4", + mp4s = "application/mp4", + mp4v = "video/mp4", + mpc = "application/mophun.certificate", + mpe = "video/mpeg", + mpeg = "video/mpeg", + mpg = "video/mpeg", + mpg4 = "video/mp4", + mpga = "audio/mpeg", + mpkg = "application/apple.installer+xml", + mpm = "application/blueice.multipass", + mpn = "application/mophun.application", + mpp = "application/ms-project", + mpt = "application/ms-project", + mpy = "application/ibm.minipay", + mqy = "application/mobius.mqy", + mrc = "application/marc", + mrcx = "application/marcxml+xml", + ms = "text/troff", + mscml = "application/mediaservercontrol+xml", + mseed = "application/fdsn.mseed", + mseq = "application/mseq", + msf = "application/epson.msf", + msh = "model/mesh", + msi = "application/msdownload", + msl = "application/mobius.msl", + msty = "application/muvee.style", + mtl = "model/mtl", + mts = "video/mp2t", + mus = "application/musician", + musicxml = "application/recordare.musicxml+xml", + mvb = "application/msmediaview", + mwf = "application/mfer", + mxf = "application/mxf", + mxl = "application/recordare.musicxml", + mxml = "application/xv+xml", + mxs = "application/triscape.mxs", + mxu = "video/mpegurl", + n3 = "text/n3", + nb = "application/mathematica", + nbp = "application/wolfram.player", + nc = "application/netcdf", + ncx = "application/dtbncx+xml", + nfo = "text/nfo", + ngdat = "application/nokia.n-gage.data", + nitf = "application/nitf", + nix = "text/nix", + nlu = "application/neurolanguage.nlu", + nml = "application/enliven", + nnd = "application/noblenet-directory", + nns = "application/noblenet-sealer", + nnw = "application/noblenet-web", + npx = "image/net-fpx", + nsc = "application/conference", + nsf = "application/lotus-notes", + ntf = "application/nitf", + nzb = "application/nzb", + oa2 = "application/fujitsu.oasys2", + oa3 = "application/fujitsu.oasys3", + oas = "application/fujitsu.oasys", + obd = "application/msbinder", + obj = "model/obj", + oda = "application/oda", + odb = "application/oasis.opendocument.database", + odc = "application/oasis.opendocument.chart", + odf = "application/oasis.opendocument.formula", + odft = "application/oasis.opendocument.formula-template", + odg = "application/oasis.opendocument.graphics", + odi = "application/oasis.opendocument.image", + odm = "application/oasis.opendocument.text-master", + odp = "application/oasis.opendocument.presentation", + ods = "application/oasis.opendocument.spreadsheet", + odt = "application/oasis.opendocument.text", + oga = "audio/ogg", + ogg = "audio/ogg", + ogv = "video/ogg", + ogx = "application/ogg", + omdoc = "application/omdoc+xml", + onepkg = "application/onenote", + onetmp = "application/onenote", + onetoc = "application/onenote", + onetoc2 = "application/onenote", + opf = "application/oebps-package+xml", + opml = "text/opml", + oprc = "application/palm", + opus = "audio/ogg", + org = "application/lotus-organizer", + osf = "application/yamaha.openscoreformat", + osfpvg = "application/yamaha.openscoreformat.osfpvg+xml", + otc = "application/oasis.opendocument.chart-template", + otf = "font/otf", + otg = "application/oasis.opendocument.graphics-template", + oth = "application/oasis.opendocument.text-web", + oti = "application/oasis.opendocument.image-template", + otp = "application/oasis.opendocument.presentation-template", + ots = "application/oasis.opendocument.spreadsheet-template", + ott = "application/oasis.opendocument.text-template", + oxps = "application/oxps", + oxt = "application/openofficeorg.extension", + p = "text/pascal", + p10 = "application/pkcs10", + p12 = "application/pkcs12", + p7b = "application/pkcs7-certificates", + p7c = "application/pkcs7-mime", + p7m = "application/pkcs7-mime", + p7r = "application/pkcs7-certreqresp", + p7s = "application/pkcs7-signature", + p8 = "application/pkcs8", + pas = "text/pascal", + patch = "text/diff", + paw = "application/pawaafile", + pbd = "application/powerbuilder6", + pbm = "image/portable-bitmap", + pcap = "application/tcpdump.pcap", + pcf = "application/font-pcf", + pcl = "application/hp-pcl", + pclxl = "application/hp-pclxl", + pct = "image/pict", + pcurl = "application/curl.pcurl", + pcx = "image/pcx", + pdb = "application/palm", + pdf = "application/pdf", + pfa = "application/font-type1", + pfb = "application/font-type1", + pfm = "application/font-type1", + pfr = "application/font-tdpfr", + pfx = "application/pkcs12", + pgm = "image/portable-graymap", + pgn = "application/chess-pgn", + pgp = "application/pgp-encrypted", + php = "text/php", + pic = "image/pict", + pkg = "application/octet-stream", + pki = "application/pkixcmp", + pkipath = "application/pkix-pkipath", + plb = "application/3gpp.pic-bw-large", + plc = "application/mobius.plc", + plf = "application/pocketlearn", + pls = "application/pls+xml", + pml = "application/ctc-posml", + png = "image/png", + pnm = "image/portable-anymap", + portpkg = "application/macports.portpkg", + pot = "application/ms-powerpoint", + potm = "application/ms-powerpoint.template.macroenabled.12", + potx = "application/openxmlformats-officedocument.presentationml.template", + ppam = "application/ms-powerpoint.addin.macroenabled.12", + ppd = "application/cups-ppd", + ppm = "image/portable-pixmap", + pps = "application/ms-powerpoint", + ppsm = "application/ms-powerpoint.slideshow.macroenabled.12", + ppsx = "application/openxmlformats-officedocument.presentationml.slideshow", + ppt = "application/ms-powerpoint", + pptm = "application/ms-powerpoint.presentation.macroenabled.12", + pptx = "application/openxmlformats-officedocument.presentationml.presentation", + pqa = "application/palm", + prc = "application/mobipocket-ebook", + pre = "application/lotus-freelance", + prf = "application/pics-rules", + ps = "application/postscript", + psb = "application/3gpp.pic-bw-small", + psd = "image/adobe.photoshop", + psf = "application/font-linux-psf", + pskcxml = "application/pskc+xml", + ptid = "application/pvi.ptid1", + pub = "application/mspublisher", + pvb = "application/3gpp.pic-bw-var", + pwn = "application/3m.post-it-notes", + py = "text/python", + pya = "audio/ms-playready.media.pya", + pyv = "video/ms-playready.media.pyv", + qam = "application/epson.quickanime", + qbo = "application/intu.qbo", + qfx = "application/intu.qfx", + qml = "text/qml", + qps = "application/publishare-delta-tree", + qt = "video/quicktime", + qwd = "application/quark.quarkxpress", + qwt = "application/quark.quarkxpress", + qxb = "application/quark.quarkxpress", + qxd = "application/quark.quarkxpress", + qxl = "application/quark.quarkxpress", + qxt = "application/quark.quarkxpress", + r = "text/r", + ra = "audio/pn-realaudio", + ram = "audio/pn-realaudio", + rar = "application/rar", + ras = "image/cmu-raster", + rb = "text/ruby", + rcprofile = "application/ipunplugged.rcprofile", + rdf = "application/rdf+xml", + rdz = "application/data-vision.rdz", + rep = "application/businessobjects", + res = "application/dtbresource+xml", + rgb = "image/rgb", + rif = "application/reginfo+xml", + rip = "audio/rip", + ris = "application/research-info-systems", + rl = "application/resource-lists+xml", + rlc = "image/fujixerox.edmics-rlc", + rld = "application/resource-lists-diff+xml", + rm = "application/rn-realmedia", + rmi = "audio/midi", + rmp = "audio/pn-realaudio-plugin", + rms = "application/jcp.javame.midlet-rms", + rmvb = "application/rn-realmedia-vbr", + rnc = "application/relax-ng-compact-syntax", + roa = "application/rpki-roa", + roff = "text/troff", + rp9 = "application/cloanto.rp9", + rpm = "application/rpm", + rpss = "application/nokia.radio-presets", + rpst = "application/nokia.radio-preset", + rq = "application/sparql-query", + rs = "text/rust", + rsd = "application/rsd+xml", + rss = "application/rss+xml", + rtf = "application/rtf", + rtx = "text/richtext", + s = "text/asm", + s3m = "audio/s3m", + saf = "application/yamaha.smaf-audio", + sbml = "application/sbml+xml", + sc = "application/ibm.secure-container", + scd = "application/msschedule", + scm = "application/lotus-screencam", + scq = "application/scvp-cv-request", + scs = "application/scvp-cv-response", + scss = "text/scss", + scurl = "text/curl.scurl", + sda = "application/stardivision.draw", + sdc = "application/stardivision.calc", + sdd = "application/stardivision.impress", + sdkd = "application/solent.sdkm+xml", + sdkm = "application/solent.sdkm+xml", + sdp = "application/sdp", + sdw = "application/stardivision.writer", + see = "application/seemail", + seed = "application/fdsn.seed", + sema = "application/sema", + semd = "application/semd", + semf = "application/semf", + ser = "application/java-serialized-object", + setpay = "application/set-payment-initiation", + setreg = "application/set-registration-initiation", + sfs = "application/spotfire.sfs", + sfv = "text/sfv", + sgi = "image/sgi", + sgl = "application/stardivision.writer-global", + sgm = "text/sgml", + sgml = "text/sgml", + sh = "text/shellscript", + shar = "application/shar", + shf = "application/shf+xml", + sid = "image/mrsid-image", + sig = "application/pgp-signature", + sil = "audio/silk", + silo = "model/mesh", + sis = "application/symbian.install", + sisx = "application/symbian.install", + sit = "application/stuffit", + sitx = "application/stuffitx", + skd = "application/koan", + skm = "application/koan", + skp = "application/koan", + skt = "application/koan", + sldm = "application/ms-powerpoint.slide.macroenabled.12", + sldx = "application/openxmlformats-officedocument.presentationml.slide", + slt = "application/epson.salt", + sm = "application/stepmania.stepchart", + smf = "application/stardivision.math", + smi = "application/smil+xml", + smil = "application/smil+xml", + smv = "video/smv", + smzip = "application/stepmania.package", + snd = "audio/basic", + snf = "application/font-snf", + so = "application/octet-stream", + spc = "application/pkcs7-certificates", + spf = "application/yamaha.smaf-phrase", + spl = "application/futuresplash", + spot = "text/in3d.spot", + spp = "application/scvp-vp-response", + spq = "application/scvp-vp-request", + spx = "audio/ogg", + sql = "application/sql", + src = "application/wais-source", + srt = "application/subrip", + sru = "application/sru+xml", + srx = "application/sparql-results+xml", + ssdl = "application/ssdl+xml", + sse = "application/kodak-descriptor", + ssf = "application/epson.ssf", + ssml = "application/ssml+xml", + st = "application/sailingtracker.track", + stc = "application/sun.xml.calc.template", + std = "application/sun.xml.draw.template", + step = "model/step", + stf = "application/wt.stf", + sti = "application/sun.xml.impress.template", + stk = "application/hyperstudio", + stl = "model/stl", + stp = "model/step", + str = "application/pg.format", + stw = "application/sun.xml.writer.template", + sub = "text/dvb.subtitle", + sus = "application/sus-calendar", + susp = "application/sus-calendar", + sv4cpio = "application/sv4cpio", + sv4crc = "application/sv4crc", + svc = "application/dvb.service", + svd = "application/svd", + svg = "image/svg+xml", + svgz = "image/svg+xml", + swa = "application/director", + swf = "application/shockwave-flash", + swi = "application/aristanetworks.swi", + sxc = "application/sun.xml.calc", + sxd = "application/sun.xml.draw", + sxg = "application/sun.xml.writer.global", + sxi = "application/sun.xml.impress", + sxm = "application/sun.xml.math", + sxw = "application/sun.xml.writer", + t = "text/troff", + t3 = "application/t3vm-image", + taglet = "application/mynfc", + tao = "application/tao.intent-module-archive", + tar = "application/tar", + tcap = "application/3gpp2.tcap", + tcl = "application/tcl", + teacher = "application/smart.teacher", + tei = "application/tei+xml", + teicorpus = "application/tei+xml", + tex = "application/tex", + texi = "application/texinfo", + texinfo = "application/texinfo", + text = "text/plain", + tf = "text/hcl", + tfi = "application/thraud+xml", + tfm = "application/tex-tfm", + tfrc = "text/hcl", + tfstate = "application/json", + tfvars = "text/hcl", + tga = "image/tga", + thmx = "application/ms-officetheme", + tif = "image/tiff", + tiff = "image/tiff", + tmo = "application/tmobile-livetv", + tofu = "text/hcl", + toml = "text/toml", + torrent = "application/bittorrent", + tpl = "application/groove-tool-template", + tpt = "application/trid.tpt", + tr = "text/troff", + tra = "application/trueapp", + trm = "application/msterminal", + ts = "text/typescript", + tsd = "application/timestamped-data", + tsv = "text/tab-separated-values", + tsx = "text/tsx", + ttc = "font/collection", + ttf = "font/ttf", + ttl = "text/turtle", + twd = "application/simtech-mindmapper", + twds = "application/simtech-mindmapper", + txd = "application/genomatix.tuxedo", + txf = "application/mobius.txf", + txt = "text/plain", + u32 = "application/authorware-bin", + udeb = "application/debian-package", + ufd = "application/ufdl", + ufdl = "application/ufdl", + ulx = "application/glulx", + umj = "application/umajin", + unityweb = "application/unity", + uoml = "application/uoml+xml", + uri = "text/uri-list", + uris = "text/uri-list", + urls = "text/uri-list", + ustar = "application/ustar", + utz = "application/uiq.theme", + uu = "text/uuencode", + uva = "audio/dece.audio", + uvd = "application/dece.data", + uvf = "application/dece.data", + uvg = "image/dece.graphic", + uvh = "video/dece.hd", + uvi = "image/dece.graphic", + uvm = "video/dece.mobile", + uvp = "video/dece.pd", + uvs = "video/dece.sd", + uvt = "application/dece.ttml+xml", + uvu = "video/uvvu.mp4", + uvv = "video/dece.video", + uvva = "audio/dece.audio", + uvvd = "application/dece.data", + uvvf = "application/dece.data", + uvvg = "image/dece.graphic", + uvvh = "video/dece.hd", + uvvi = "image/dece.graphic", + uvvm = "video/dece.mobile", + uvvp = "video/dece.pd", + uvvs = "video/dece.sd", + uvvt = "application/dece.ttml+xml", + uvvu = "video/uvvu.mp4", + uvvv = "video/dece.video", + uvvx = "application/dece.unspecified", + uvvz = "application/dece.zip", + uvx = "application/dece.unspecified", + uvz = "application/dece.zip", + vcard = "text/vcard", + vcd = "application/cdlink", + vcf = "text/vcard", + vcg = "application/groove-vcard", + vcs = "text/vcalendar", + vcx = "application/vcx", + vim = "text/plain", + vis = "application/visionary", + viv = "video/vivo", + vob = "video/ms-vob", + vor = "application/stardivision.writer", + vox = "application/authorware-bin", + vrml = "model/vrml", + vsd = "application/visio", + vsf = "application/vsf", + vss = "application/visio", + vst = "application/visio", + vsw = "application/visio", + vtu = "model/vtu", + vxml = "application/voicexml+xml", + w3d = "application/director", + wad = "application/doom", + wasm = "application/wasm", + wav = "audio/wav", + wax = "audio/ms-wax", + wbmp = "image/wap.wbmp", + wbs = "application/criticaltools.wbs+xml", + wbxml = "application/wap.wbxml", + wcm = "application/ms-works", + wdb = "application/ms-works", + wdp = "image/ms-photo", + weba = "audio/webm", + webm = "video/webm", + webp = "image/webp", + wg = "application/pmi.widget", + wgt = "application/widget", + wks = "application/ms-works", + wm = "video/ms-wm", + wma = "audio/ms-wma", + wmd = "application/ms-wmd", + wmf = "application/msmetafile", + wml = "text/wap.wml", + wmlc = "application/wap.wmlc", + wmls = "text/wap.wmlscript", + wmlsc = "application/wap.wmlscriptc", + wmv = "video/ms-wmv", + wmx = "video/ms-wmx", + wmz = "application/ms-wmz", + woff = "font/woff", + woff2 = "font/woff2", + wpd = "application/wordperfect", + wpl = "application/ms-wpl", + wps = "application/ms-works", + wqd = "application/wqd", + wri = "application/mswrite", + wrl = "model/vrml", + wsdl = "application/wsdl+xml", + wspolicy = "application/wspolicy+xml", + wtb = "application/webturbo", + wvx = "video/ms-wvx", + x32 = "application/authorware-bin", + x3d = "model/x3d+xml", + x3db = "model/x3d+binary", + x3dbz = "model/x3d+binary", + x3dv = "model/x3d+vrml", + x3dvz = "model/x3d+vrml", + x3dz = "model/x3d+xml", + xaml = "application/xaml+xml", + xap = "application/silverlight-app", + xar = "application/xara", + xbap = "application/ms-xbap", + xbd = "application/fujixerox.docuworks.binder", + xbm = "image/xbitmap", + xdf = "application/xcap-diff+xml", + xdm = "application/syncml.dm+xml", + xdp = "application/adobe.xdp+xml", + xdssc = "application/dssc+xml", + xdw = "application/fujixerox.docuworks", + xenc = "application/xenc+xml", + xer = "application/patch-ops-error+xml", + xfdf = "application/adobe.xfdf", + xfdl = "application/xfdl", + xht = "application/xhtml+xml", + xhtml = "application/xhtml+xml", + xhvml = "application/xv+xml", + xif = "image/xiff", + xla = "application/ms-excel", + xlam = "application/ms-excel.addin.macroenabled.12", + xlc = "application/ms-excel", + xlf = "application/xliff+xml", + xlm = "application/ms-excel", + xls = "application/ms-excel", + xlsb = "application/ms-excel.sheet.binary.macroenabled.12", + xlsm = "application/ms-excel.sheet.macroenabled.12", + xlsx = "application/openxmlformats-officedocument.spreadsheetml.sheet", + xlt = "application/ms-excel", + xltm = "application/ms-excel.template.macroenabled.12", + xltx = "application/openxmlformats-officedocument.spreadsheetml.template", + xlw = "application/ms-excel", + xm = "audio/xm", + xml = "application/xml", + xo = "application/olpc-sugar", + xop = "application/xop+xml", + xpak = "application/gentoo.xpak", + xpi = "application/xpinstall", + xpl = "application/xproc+xml", + xpm = "image/xpixmap", + xpr = "application/is-xpr", + xps = "application/ms-xpsdocument", + xpw = "application/intercon.formnet", + xpx = "application/intercon.formnet", + xsl = "application/xml", + xslt = "application/xslt+xml", + xsm = "application/syncml+xml", + xspf = "application/xspf+xml", + xul = "application/mozilla.xul+xml", + xvm = "application/xv+xml", + xvml = "application/xv+xml", + xwd = "image/xwindowdump", + xyz = "chemical/xyz", + xz = "application/xz", + yaml = "text/yaml", + yang = "application/yang", + yin = "application/yin+xml", + yml = "text/yaml", + z1 = "application/zmachine", + z2 = "application/zmachine", + z3 = "application/zmachine", + z4 = "application/zmachine", + z5 = "application/zmachine", + z6 = "application/zmachine", + z7 = "application/zmachine", + z8 = "application/zmachine", + zaz = "application/zzazz.deck+xml", + zip = "application/zip", + zir = "application/zul", + zirz = "application/zul", + zmm = "application/handheld-entertainment+xml", + zsh = "text/shellscript", + zst = "application/zstd", +} + +local options = ya.sync( + function(st) + return { + with_files = st.with_files, + with_exts = st.with_exts, + custom_only = st.custom_only, + fallback_file1 = st.fallback_file1, + } + end +) + +local M = {} + +function M:setup(opts) + opts = opts or {} + + self.with_files = opts.with_files + self.with_exts = opts.with_exts + self.custom_only = opts.custom_only + self.fallback_file1 = opts.fallback_file1 +end + +function M:fetch(job) + local opts = options() + local merged_files = opts.with_files or {} + local merged_exts = opts.with_exts or {} + + if not opts.custom_only then + merged_files = ya.dict_merge(FILES, merged_files) + merged_exts = ya.dict_merge(EXTS, merged_exts) + end + + return ya.co(function() + local updates, unknown = {}, {} + for _, file in ipairs(job.files) do + if file.cha.is_dummy then + coroutine.yield(file, { retry = true }) + goto continue + end + + local mime + if file.cha.len == 0 then + mime = "inode/empty" + else + mime = merged_files[(file.url.name or ""):lower()] + mime = mime or merged_exts[(file.url.ext or ""):lower()] + end + + if not mime and opts.fallback_file1 then + unknown[#unknown + 1] = file + elseif not mime then + mime = "application/octet-stream" + end + + if mime and coroutine.yield(file, { mime }) then + updates[file.url] = mime + end + ::continue:: + end + + if next(updates) then + ya.emit("update_mimes", { updates = updates }) + end + + if #unknown > 0 then + self.fallback_builtin(job, unknown) + end + end) +end + +function M.fallback_builtin(job, unknown) + local next = require("mime.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 diff --git a/yazi/.config/yazi/plugins/mime-ext.yazi/main.lua b/yazi/.config/yazi/plugins/mime-ext.yazi/main.lua index abe183b..a564707 100644 --- a/yazi/.config/yazi/plugins/mime-ext.yazi/main.lua +++ b/yazi/.config/yazi/plugins/mime-ext.yazi/main.lua @@ -1,1130 +1 @@ ---- @since 25.12.29 - -local FILES = { - [".envrc"] = "text/plain", - [".gitconfig"] = "text/plain", - [".gitignore"] = "text/plain", - [".luacheckrc"] = "text/lua", - [".npmrc"] = "text/plain", - [".styluaignore"] = "text/plain", - [".zshenv"] = "text/plain", - [".zshrc"] = "text/plain", - ["cargo.lock"] = "application/json", - ["flake.lock"] = "application/json", - license = "text/plain", - config = "text/plain", -} - -local EXTS = { - ["123"] = "application/lotus-1-2-3", - ["3dml"] = "text/in3d.3dml", - ["3ds"] = "image/3ds", - ["3g2"] = "video/3gpp2", - ["3gp"] = "video/3gpp", - ["7z"] = "application/7z-compressed", - ["for"] = "text/fortran", - ["in"] = "text/plain", - ["n-gage"] = "application/nokia.n-gage.symbian.install", - ["sfd-hdstx"] = "application/hydrostatix.sof-data", - aab = "application/authorware-bin", - aac = "audio/aac", - aam = "application/authorware-map", - aas = "application/authorware-seg", - abw = "application/abiword", - ac = "application/pkix-attr-cert", - acc = "application/americandynamics.acc", - ace = "application/ace-compressed", - acu = "application/acucobol", - acutc = "application/acucorp", - adp = "audio/adpcm", - aep = "application/audiograph", - afm = "application/font-type1", - afp = "application/ibm.modcap", - ahead = "application/ahead.space", - ai = "application/postscript", - aif = "audio/aiff", - aifc = "audio/aiff", - aiff = "audio/aiff", - air = "application/adobe.air-application-installer-package+zip", - ait = "application/dvb.ait", - ami = "application/amiga.ami", - apk = "application/android.package-archive", - appcache = "text/cache-manifest", - application = "application/ms-application", - apr = "application/lotus-approach", - arc = "application/freearc", - asc = "application/pgp-signature", - asf = "video/ms-asf", - asm = "text/asm", - aso = "application/accpac.simply.aso", - ass = "text/ass", - asx = "video/ms-asf", - atc = "application/acucorp", - atom = "application/atom+xml", - atomcat = "application/atomcat+xml", - atomsvc = "application/atomsvc+xml", - atx = "application/antix.game-component", - au = "audio/basic", - avi = "video/msvideo", - avif = "image/avif", - aw = "application/applixware", - azf = "application/airzip.filesecure.azf", - azs = "application/airzip.filesecure.azs", - azw = "application/amazon.ebook", - bash = "text/shellscript", - bat = "application/msdownload", - bcpio = "application/bcpio", - bdf = "application/font-bdf", - bdm = "application/syncml.dm+wbxml", - bean = "text/plain", - beancount = "text/plain", - bed = "application/realvnc.bed", - bh2 = "application/fujitsu.oasysprs", - bin = "application/octet-stream", - blb = "application/blorb", - blorb = "application/blorb", - bmi = "application/bmi", - bmp = "image/bmp", - book = "application/framemaker", - box = "application/previewsystems.box", - boz = "application/bzip2", - bpk = "application/octet-stream", - btif = "image/prs.btif", - bz = "application/bzip", - bz2 = "application/bzip2", - c = "text/c", - c11amc = "application/cluetrust.cartomobile-config", - c11amz = "application/cluetrust.cartomobile-config-pkg", - c4d = "application/clonk.c4group", - c4f = "application/clonk.c4group", - c4g = "application/clonk.c4group", - c4p = "application/clonk.c4group", - c4u = "application/clonk.c4group", - cab = "application/ms-cab-compressed", - caf = "audio/caf", - cap = "application/tcpdump.pcap", - car = "application/curl.car", - cat = "application/ms-pki.seccat", - cb7 = "application/cbr", - cba = "application/cbr", - cbr = "application/cbr", - cbt = "application/cbr", - cbz = "application/cbr", - cc = "text/c", - cct = "application/director", - ccxml = "application/ccxml+xml", - cdbcmsg = "application/contact.cmsg", - cdf = "application/netcdf", - cdkey = "application/mediastation.cdkey", - cdmia = "application/cdmi-capability", - cdmic = "application/cdmi-container", - cdmid = "application/cdmi-domain", - cdmio = "application/cdmi-object", - cdmiq = "application/cdmi-queue", - cdx = "chemical/cdx", - cdxml = "application/chemdraw+xml", - cdy = "application/cinderella", - cer = "application/pkix-cert", - cfg = "text/plain", - cfs = "application/cfs-compressed", - cgm = "image/cgm", - chat = "application/chat", - chm = "application/ms-htmlhelp", - chrt = "application/kde.kchart", - cif = "chemical/cif", - cii = "application/anser-web-certificate-issue-initiation", - cil = "application/ms-artgalry", - cla = "application/claymore", - class = "application/java-vm", - clkk = "application/crick.clicker.keyboard", - clkp = "application/crick.clicker.palette", - clkt = "application/crick.clicker.template", - clkw = "application/crick.clicker.wordbank", - clkx = "application/crick.clicker", - clp = "application/msclip", - cmc = "application/cosmocaller", - cmdf = "chemical/cmdf", - cml = "chemical/cml", - cmp = "application/yellowriver-custom-menu", - cmx = "image/cmx", - cod = "application/rim.cod", - com = "application/msdownload", - conf = "text/plain", - config = "text/plain", - cpio = "application/cpio", - cpp = "text/c", - cpt = "application/mac-compactpro", - crd = "application/mscardfile", - crl = "application/pkix-crl", - crt = "application/x509-ca-cert", - cryptonote = "application/rig.cryptonote", - csh = "application/csh", - csml = "chemical/csml", - csp = "application/commonspace", - css = "text/css", - cst = "application/director", - csv = "text/csv", - cu = "application/cu-seeme", - curl = "text/curl", - cww = "application/prs.cww", - cxt = "application/director", - cxx = "text/c", - dae = "model/collada+xml", - daf = "application/mobius.daf", - dart = "application/dart", - dataless = "application/fdsn.seed", - davmount = "application/davmount+xml", - dbk = "application/docbook+xml", - dcr = "application/director", - dcurl = "text/curl.dcurl", - dd2 = "application/oma.dd2+xml", - ddd = "application/fujixerox.ddd", - deb = "application/debian-package", - def = "text/plain", - deploy = "application/octet-stream", - der = "application/x509-ca-cert", - dfac = "application/dreamfactory", - dgc = "application/dgc-compressed", - dic = "text/c", - dir = "application/director", - dis = "application/mobius.dis", - dist = "application/octet-stream", - distz = "application/octet-stream", - djv = "image/djvu", - djvu = "image/djvu", - dll = "application/msdownload", - dmg = "application/apple-diskimage", - dmp = "application/tcpdump.pcap", - dms = "application/octet-stream", - dna = "application/dna", - doc = "application/msword", - docm = "application/ms-word.document.macroenabled.12", - docx = "application/openxmlformats-officedocument.wordprocessingml.document", - dot = "application/msword", - dotm = "application/ms-word.template.macroenabled.12", - dotx = "application/openxmlformats-officedocument.wordprocessingml.template", - dp = "application/osgi.dp", - dpg = "application/dpgraph", - dra = "audio/dra", - dsc = "text/prs.lines.tag", - dssc = "application/dssc+der", - dtb = "application/dtbook+xml", - dtd = "application/xml-dtd", - dts = "audio/dts", - dtshd = "audio/dts.hd", - dump = "application/octet-stream", - dvb = "video/dvb.file", - dvi = "application/dvi", - dwf = "model/dwf", - dwg = "image/dwg", - dxf = "image/dxf", - dxp = "application/spotfire.dxp", - dxr = "application/director", - ebuild = "application/gentoo.ebuild", - ecelp4800 = "audio/nuera.ecelp4800", - ecelp7470 = "audio/nuera.ecelp7470", - ecelp9600 = "audio/nuera.ecelp9600", - eclass = "application/gentoo.eclass", - ecma = "application/ecmascript", - edm = "application/novadigm.edm", - edx = "application/novadigm.edx", - efif = "application/picsel", - ei6 = "application/pg.osasli", - elc = "application/octet-stream", - emf = "application/msmetafile", - eml = "message/rfc822", - emma = "application/emma+xml", - emz = "application/msmetafile", - env = "text/plain", - eol = "audio/digital-winds", - eot = "application/ms-fontobject", - eps = "application/postscript", - epub = "application/epub+zip", - es3 = "application/eszigno3+xml", - esa = "application/osgi.subsystem", - esf = "application/epson.esf", - et3 = "application/eszigno3+xml", - etx = "text/setext", - eva = "application/eva", - evy = "application/envoy", - exe = "application/msdownload", - exi = "application/exi", - ext = "application/novadigm.ext", - ez = "application/andrew-inset", - ez2 = "application/ezpix-album", - ez3 = "application/ezpix-package", - f = "text/fortran", - f4v = "video/f4v", - f77 = "text/fortran", - f90 = "text/fortran", - fbs = "image/fastbidsheet", - fcdt = "application/adobe.formscentral.fcdt", - fcs = "application/isac.fcs", - fdf = "application/fdf", - fe_launch = "application/denovo.fcselayout-link", - fg5 = "application/fujitsu.oasysgp", - fgd = "application/director", - fh = "image/freehand", - fh4 = "image/freehand", - fh5 = "image/freehand", - fh7 = "image/freehand", - fhc = "image/freehand", - fig = "application/xfig", - fish = "text/shellscript", - flac = "audio/flac", - fli = "video/fli", - flo = "application/micrografx.flo", - flv = "video/flv", - flw = "application/kde.kivio", - flx = "text/fmi.flexstor", - fly = "text/fly", - fm = "application/framemaker", - fnc = "application/frogans.fnc", - fpx = "image/fpx", - frame = "application/framemaker", - fsc = "application/fsc.weblaunch", - fst = "image/fst", - ftc = "application/fluxtime.clip", - fti = "application/anser-web-funds-transfer-initiation", - fvt = "video/fvt", - fxp = "application/adobe.fxp", - fxpl = "application/adobe.fxp", - fzs = "application/fuzzysheet", - g2w = "application/geoplan", - g3 = "image/g3fax", - g3w = "application/geospace", - gac = "application/groove-account", - gam = "application/tads", - gbr = "application/rpki-ghostbusters", - gca = "application/gca-compressed", - gdl = "model/gdl", - geo = "application/dynageo", - gex = "application/geometry-explorer", - ggb = "application/geogebra.file", - ggs = "application/geogebra.slides", - ggt = "application/geogebra.tool", - ghf = "application/groove-help", - gif = "image/gif", - gim = "application/groove-identity-message", - gml = "application/gml+xml", - gmx = "application/gmx", - gnumeric = "application/gnumeric", - go = "text/go", - gph = "application/flographit", - gpx = "application/gpx+xml", - gqf = "application/grafeq", - gqs = "application/grafeq", - gram = "application/srgs", - gramps = "application/gramps-xml", - gre = "application/geometry-explorer", - grv = "application/groove-injector", - grxml = "application/srgs+xml", - gsf = "application/font-ghostscript", - gtar = "application/gtar", - gtm = "application/groove-tool-message", - gtw = "model/gtw", - gv = "text/graphviz", - gxf = "application/gxf", - gxt = "application/geonext", - h = "text/c", - h261 = "video/h261", - h263 = "video/h263", - h264 = "video/h264", - hal = "application/hal+xml", - hbci = "application/hbci", - hcl = "text/hcl", - hdf = "application/hdf", - hh = "text/c", - hlp = "application/winhlp", - hpgl = "application/hp-hpgl", - hpid = "application/hp-hpid", - hpp = "text/c", - hps = "application/hp-hps", - hqx = "application/mac-binhex40", - htke = "application/kenameaapp", - htm = "text/html", - html = "text/html", - hvd = "application/yamaha.hv-dic", - hvp = "application/yamaha.hv-voice", - hvs = "application/yamaha.hv-script", - i2g = "application/intergeo", - icc = "application/iccprofile", - ice = "conference/cooltalk", - icm = "application/iccprofile", - ico = "image/icon", - ics = "text/calendar", - ief = "image/ief", - ifb = "text/calendar", - ifm = "application/shana.informed.formdata", - iges = "model/iges", - igl = "application/igloader", - igm = "application/insors.igm", - igs = "model/iges", - igx = "application/micrografx.igx", - iif = "application/shana.informed.interchange", - imp = "application/accpac.simply.imp", - ims = "application/ms-ims", - ini = "text/plain", - ink = "application/inkml+xml", - inkml = "application/inkml+xml", - install = "application/install-instructions", - iota = "application/astraea-software.iota", - ipfix = "application/ipfix", - ipk = "application/shana.informed.package", - irm = "application/ibm.rights-management", - irp = "application/irepository.package+xml", - iso = "application/iso9660-image", - itp = "application/shana.informed.formtemplate", - ivp = "application/immervision-ivp", - ivu = "application/immervision-ivu", - jad = "text/sun.j2me.app-descriptor", - jam = "application/jam", - jar = "application/java-archive", - java = "text/java-source", - jisp = "application/jisp", - jlt = "application/hp-jlyt", - jnlp = "application/java-jnlp-file", - joda = "application/joost.joda-archive", - jpe = "image/jpeg", - jpeg = "image/jpeg", - jpg = "image/jpeg", - jpgm = "video/jpm", - jpgv = "video/jpeg", - jpm = "video/jpm", - js = "text/javascript", - json = "application/json", - jsonc = "application/json", - jsonml = "application/jsonml+json", - jsx = "text/jsx", - jxl = "image/jxl", - kar = "audio/midi", - karbon = "application/kde.karbon", - kdl = "text/kdl", - kfo = "application/kde.kformula", - kia = "application/kidspiration", - kml = "application/google-earth.kml+xml", - kmz = "application/google-earth.kmz", - kne = "application/kinar", - knp = "application/kinar", - kon = "application/kde.kontour", - kpr = "application/kde.kpresenter", - kpt = "application/kde.kpresenter", - kpxx = "application/ds-keypoint", - ksp = "application/kde.kspread", - ktr = "application/kahootz", - ktx = "image/ktx", - ktz = "application/kahootz", - kwd = "application/kde.kword", - kwt = "application/kde.kword", - lasxml = "application/las.las+xml", - latex = "application/latex", - lbd = "application/llamagraphics.life-balance.desktop", - lbe = "application/llamagraphics.life-balance.exchange+xml", - les = "application/hhe.lesson-player", - lha = "application/lzh-compressed", - link66 = "application/route66.link66+xml", - list = "text/plain", - list3820 = "application/ibm.modcap", - listafp = "application/ibm.modcap", - lnk = "application/ms-shortcut", - log = "text/plain", - lostxml = "application/lost+xml", - lrf = "application/octet-stream", - lrm = "application/ms-lrm", - ltf = "application/frogans.ltf", - lua = "text/lua", - lvp = "audio/lucent.voice", - lwp = "application/lotus-wordpro", - lzh = "application/lzh-compressed", - m13 = "application/msmediaview", - m14 = "application/msmediaview", - m1v = "video/mpeg", - m21 = "application/mp21", - m2a = "audio/mpeg", - m2t = "video/mp2t", - m2ts = "video/mp2t", - m2v = "video/mpeg", - m3a = "audio/mpeg", - m3u = "audio/mpegurl", - m3u8 = "application/apple.mpegurl", - m4a = "audio/mp4", - m4u = "video/mpegurl", - m4v = "video/m4v", - ma = "application/mathematica", - mads = "application/mads+xml", - mag = "application/ecowin.chart", - maker = "application/framemaker", - man = "text/troff", - mar = "application/octet-stream", - mathml = "application/mathml+xml", - mb = "application/mathematica", - mbk = "application/mobius.mbk", - mbox = "application/mbox", - mc1 = "application/medcalcdata", - mcd = "application/mcd", - mcurl = "text/curl.mcurl", - md = "text/markdown", - mdb = "application/msaccess", - mdi = "image/ms-modi", - me = "text/troff", - mesh = "model/mesh", - meta4 = "application/metalink4+xml", - metalink = "application/metalink+xml", - mets = "application/mets+xml", - mfm = "application/mfmp", - mft = "application/rpki-manifest", - mgp = "application/osgeo.mapguide.package", - mgz = "application/proteus.magazine", - mid = "audio/midi", - midi = "audio/midi", - mie = "application/mie", - mif = "application/mif", - mime = "message/rfc822", - mj2 = "video/mj2", - mjp2 = "video/mj2", - mjs = "text/javascript", - mk3d = "video/matroska", - mka = "audio/matroska", - mks = "video/matroska", - mkv = "video/matroska", - mlp = "application/dolby.mlp", - mmd = "application/chipnuts.karaoke-mmd", - mmf = "application/smaf", - mmr = "image/fujixerox.edmics-mmr", - mng = "video/mng", - mny = "application/msmoney", - mobi = "application/mobipocket-ebook", - mods = "application/mods+xml", - mov = "video/quicktime", - movie = "video/sgi-movie", - mp2 = "audio/mpeg", - mp21 = "application/mp21", - mp2a = "audio/mpeg", - mp3 = "audio/mpeg", - mp4 = "video/mp4", - mp4a = "audio/mp4", - mp4s = "application/mp4", - mp4v = "video/mp4", - mpc = "application/mophun.certificate", - mpe = "video/mpeg", - mpeg = "video/mpeg", - mpg = "video/mpeg", - mpg4 = "video/mp4", - mpga = "audio/mpeg", - mpkg = "application/apple.installer+xml", - mpm = "application/blueice.multipass", - mpn = "application/mophun.application", - mpp = "application/ms-project", - mpt = "application/ms-project", - mpy = "application/ibm.minipay", - mqy = "application/mobius.mqy", - mrc = "application/marc", - mrcx = "application/marcxml+xml", - ms = "text/troff", - mscml = "application/mediaservercontrol+xml", - mseed = "application/fdsn.mseed", - mseq = "application/mseq", - msf = "application/epson.msf", - msh = "model/mesh", - msi = "application/msdownload", - msl = "application/mobius.msl", - msty = "application/muvee.style", - mts = "video/mp2t", - mus = "application/musician", - musicxml = "application/recordare.musicxml+xml", - mvb = "application/msmediaview", - mwf = "application/mfer", - mxf = "application/mxf", - mxl = "application/recordare.musicxml", - mxml = "application/xv+xml", - mxs = "application/triscape.mxs", - mxu = "video/mpegurl", - n3 = "text/n3", - nb = "application/mathematica", - nbp = "application/wolfram.player", - nc = "application/netcdf", - ncx = "application/dtbncx+xml", - nfo = "text/nfo", - ngdat = "application/nokia.n-gage.data", - nitf = "application/nitf", - nix = "text/nix", - nlu = "application/neurolanguage.nlu", - nml = "application/enliven", - nnd = "application/noblenet-directory", - nns = "application/noblenet-sealer", - nnw = "application/noblenet-web", - npx = "image/net-fpx", - nsc = "application/conference", - nsf = "application/lotus-notes", - ntf = "application/nitf", - nu = "text/nu", - nzb = "application/nzb", - oa2 = "application/fujitsu.oasys2", - oa3 = "application/fujitsu.oasys3", - oas = "application/fujitsu.oasys", - obd = "application/msbinder", - obj = "application/tgif", - oda = "application/oda", - odb = "application/oasis.opendocument.database", - odc = "application/oasis.opendocument.chart", - odf = "application/oasis.opendocument.formula", - odft = "application/oasis.opendocument.formula-template", - odg = "application/oasis.opendocument.graphics", - odi = "application/oasis.opendocument.image", - odm = "application/oasis.opendocument.text-master", - odp = "application/oasis.opendocument.presentation", - ods = "application/oasis.opendocument.spreadsheet", - odt = "application/oasis.opendocument.text", - oga = "audio/ogg", - ogg = "audio/ogg", - ogv = "video/ogg", - ogx = "application/ogg", - omdoc = "application/omdoc+xml", - onepkg = "application/onenote", - onetmp = "application/onenote", - onetoc = "application/onenote", - onetoc2 = "application/onenote", - opf = "application/oebps-package+xml", - opml = "text/opml", - oprc = "application/palm", - opus = "audio/ogg", - org = "application/lotus-organizer", - osf = "application/yamaha.openscoreformat", - osfpvg = "application/yamaha.openscoreformat.osfpvg+xml", - otc = "application/oasis.opendocument.chart-template", - otf = "font/otf", - otg = "application/oasis.opendocument.graphics-template", - oth = "application/oasis.opendocument.text-web", - oti = "application/oasis.opendocument.image-template", - otp = "application/oasis.opendocument.presentation-template", - ots = "application/oasis.opendocument.spreadsheet-template", - ott = "application/oasis.opendocument.text-template", - oxps = "application/oxps", - oxt = "application/openofficeorg.extension", - p = "text/pascal", - p10 = "application/pkcs10", - p12 = "application/pkcs12", - p7b = "application/pkcs7-certificates", - p7c = "application/pkcs7-mime", - p7m = "application/pkcs7-mime", - p7r = "application/pkcs7-certreqresp", - p7s = "application/pkcs7-signature", - p8 = "application/pkcs8", - pas = "text/pascal", - patch = "text/diff", - paw = "application/pawaafile", - pbd = "application/powerbuilder6", - pbm = "image/portable-bitmap", - pcap = "application/tcpdump.pcap", - pcf = "application/font-pcf", - pcl = "application/hp-pcl", - pclxl = "application/hp-pclxl", - pct = "image/pict", - pcurl = "application/curl.pcurl", - pcx = "image/pcx", - pdb = "application/palm", - pdf = "application/pdf", - pfa = "application/font-type1", - pfb = "application/font-type1", - pfm = "application/font-type1", - pfr = "application/font-tdpfr", - pfx = "application/pkcs12", - pgm = "image/portable-graymap", - pgn = "application/chess-pgn", - pgp = "application/pgp-encrypted", - php = "text/php", - pic = "image/pict", - pkg = "application/octet-stream", - pki = "application/pkixcmp", - pkipath = "application/pkix-pkipath", - plb = "application/3gpp.pic-bw-large", - plc = "application/mobius.plc", - plf = "application/pocketlearn", - pls = "application/pls+xml", - pml = "application/ctc-posml", - png = "image/png", - pnm = "image/portable-anymap", - portpkg = "application/macports.portpkg", - pot = "application/ms-powerpoint", - potm = "application/ms-powerpoint.template.macroenabled.12", - potx = "application/openxmlformats-officedocument.presentationml.template", - ppam = "application/ms-powerpoint.addin.macroenabled.12", - ppd = "application/cups-ppd", - ppm = "image/portable-pixmap", - pps = "application/ms-powerpoint", - ppsm = "application/ms-powerpoint.slideshow.macroenabled.12", - ppsx = "application/openxmlformats-officedocument.presentationml.slideshow", - ppt = "application/ms-powerpoint", - pptm = "application/ms-powerpoint.presentation.macroenabled.12", - pptx = "application/openxmlformats-officedocument.presentationml.presentation", - pqa = "application/palm", - prc = "application/mobipocket-ebook", - pre = "application/lotus-freelance", - prf = "application/pics-rules", - ps = "application/postscript", - psb = "application/3gpp.pic-bw-small", - psd = "image/adobe.photoshop", - psf = "application/font-linux-psf", - pskcxml = "application/pskc+xml", - ptid = "application/pvi.ptid1", - pub = "application/mspublisher", - pvb = "application/3gpp.pic-bw-var", - pwn = "application/3m.post-it-notes", - py = "text/python", - pya = "audio/ms-playready.media.pya", - pyv = "video/ms-playready.media.pyv", - qam = "application/epson.quickanime", - qbo = "application/intu.qbo", - qfx = "application/intu.qfx", - qml = "text/qml", - qps = "application/publishare-delta-tree", - qt = "video/quicktime", - qwd = "application/quark.quarkxpress", - qwt = "application/quark.quarkxpress", - qxb = "application/quark.quarkxpress", - qxd = "application/quark.quarkxpress", - qxl = "application/quark.quarkxpress", - qxt = "application/quark.quarkxpress", - r = "text/r", - ra = "audio/pn-realaudio", - ram = "audio/pn-realaudio", - rar = "application/rar", - ras = "image/cmu-raster", - rb = "text/nu", - rcprofile = "application/ipunplugged.rcprofile", - rdf = "application/rdf+xml", - rdz = "application/data-vision.rdz", - rep = "application/businessobjects", - res = "application/dtbresource+xml", - rgb = "image/rgb", - rif = "application/reginfo+xml", - rip = "audio/rip", - ris = "application/research-info-systems", - rl = "application/resource-lists+xml", - rlc = "image/fujixerox.edmics-rlc", - rld = "application/resource-lists-diff+xml", - rm = "application/rn-realmedia", - rmi = "audio/midi", - rmp = "audio/pn-realaudio-plugin", - rms = "application/jcp.javame.midlet-rms", - rmvb = "application/rn-realmedia-vbr", - rnc = "application/relax-ng-compact-syntax", - roa = "application/rpki-roa", - roff = "text/troff", - rp9 = "application/cloanto.rp9", - rpm = "application/rpm", - rpss = "application/nokia.radio-presets", - rpst = "application/nokia.radio-preset", - rq = "application/sparql-query", - rs = "text/rust", - rsd = "application/rsd+xml", - rss = "application/rss+xml", - rtf = "application/rtf", - rtx = "text/richtext", - s = "text/asm", - s3m = "audio/s3m", - saf = "application/yamaha.smaf-audio", - sbml = "application/sbml+xml", - sc = "application/ibm.secure-container", - scd = "application/msschedule", - scm = "application/lotus-screencam", - scq = "application/scvp-cv-request", - scs = "application/scvp-cv-response", - scss = "text/scss", - scurl = "text/curl.scurl", - sda = "application/stardivision.draw", - sdc = "application/stardivision.calc", - sdd = "application/stardivision.impress", - sdkd = "application/solent.sdkm+xml", - sdkm = "application/solent.sdkm+xml", - sdp = "application/sdp", - sdw = "application/stardivision.writer", - see = "application/seemail", - seed = "application/fdsn.seed", - sema = "application/sema", - semd = "application/semd", - semf = "application/semf", - ser = "application/java-serialized-object", - setpay = "application/set-payment-initiation", - setreg = "application/set-registration-initiation", - sfs = "application/spotfire.sfs", - sfv = "text/sfv", - sgi = "image/sgi", - sgl = "application/stardivision.writer-global", - sgm = "text/sgml", - sgml = "text/sgml", - sh = "text/shellscript", - shar = "application/shar", - shf = "application/shf+xml", - sid = "image/mrsid-image", - sig = "application/pgp-signature", - sil = "audio/silk", - silo = "model/mesh", - sis = "application/symbian.install", - sisx = "application/symbian.install", - sit = "application/stuffit", - sitx = "application/stuffitx", - skd = "application/koan", - skm = "application/koan", - skp = "application/koan", - skt = "application/koan", - sldm = "application/ms-powerpoint.slide.macroenabled.12", - sldx = "application/openxmlformats-officedocument.presentationml.slide", - slt = "application/epson.salt", - sm = "application/stepmania.stepchart", - smf = "application/stardivision.math", - smi = "application/smil+xml", - smil = "application/smil+xml", - smv = "video/smv", - smzip = "application/stepmania.package", - snd = "audio/basic", - snf = "application/font-snf", - so = "application/octet-stream", - spc = "application/pkcs7-certificates", - spf = "application/yamaha.smaf-phrase", - spl = "application/futuresplash", - spot = "text/in3d.spot", - spp = "application/scvp-vp-response", - spq = "application/scvp-vp-request", - spx = "audio/ogg", - sql = "application/sql", - src = "application/wais-source", - srt = "application/subrip", - sru = "application/sru+xml", - srx = "application/sparql-results+xml", - ssdl = "application/ssdl+xml", - sse = "application/kodak-descriptor", - ssf = "application/epson.ssf", - ssml = "application/ssml+xml", - st = "application/sailingtracker.track", - stc = "application/sun.xml.calc.template", - std = "application/sun.xml.draw.template", - stf = "application/wt.stf", - sti = "application/sun.xml.impress.template", - stk = "application/hyperstudio", - stl = "application/ms-pki.stl", - str = "application/pg.format", - stw = "application/sun.xml.writer.template", - sub = "text/dvb.subtitle", - sus = "application/sus-calendar", - susp = "application/sus-calendar", - sv4cpio = "application/sv4cpio", - sv4crc = "application/sv4crc", - svc = "application/dvb.service", - svd = "application/svd", - svg = "image/svg+xml", - svgz = "image/svg+xml", - swa = "application/director", - swf = "application/shockwave-flash", - swi = "application/aristanetworks.swi", - sxc = "application/sun.xml.calc", - sxd = "application/sun.xml.draw", - sxg = "application/sun.xml.writer.global", - sxi = "application/sun.xml.impress", - sxm = "application/sun.xml.math", - sxw = "application/sun.xml.writer", - t = "text/troff", - t3 = "application/t3vm-image", - taglet = "application/mynfc", - tao = "application/tao.intent-module-archive", - tar = "application/tar", - tcap = "application/3gpp2.tcap", - tcl = "application/tcl", - teacher = "application/smart.teacher", - tei = "application/tei+xml", - teicorpus = "application/tei+xml", - tex = "application/tex", - texi = "application/texinfo", - texinfo = "application/texinfo", - text = "text/plain", - tf = "text/hcl", - tfi = "application/thraud+xml", - tfm = "application/tex-tfm", - tfrc = "text/hcl", - tfstate = "application/json", - tfvars = "text/hcl", - tga = "image/tga", - theme = "application/plain", - thmx = "application/ms-officetheme", - tif = "image/tiff", - tiff = "image/tiff", - tmo = "application/tmobile-livetv", - tmTheme = "text/plain", - toml = "text/toml", - torrent = "application/bittorrent", - tpl = "application/groove-tool-template", - tpt = "application/trid.tpt", - tr = "text/troff", - tra = "application/trueapp", - trm = "application/msterminal", - ts = "text/typescript", - tsd = "application/timestamped-data", - tsv = "text/tab-separated-values", - tsx = "text/tsx", - ttc = "font/collection", - ttf = "font/ttf", - ttl = "text/turtle", - twd = "application/simtech-mindmapper", - twds = "application/simtech-mindmapper", - txd = "application/genomatix.tuxedo", - txf = "application/mobius.txf", - txt = "text/plain", - u32 = "application/authorware-bin", - udeb = "application/debian-package", - ufd = "application/ufdl", - ufdl = "application/ufdl", - ulx = "application/glulx", - umj = "application/umajin", - unityweb = "application/unity", - uoml = "application/uoml+xml", - uri = "text/uri-list", - uris = "text/uri-list", - urls = "text/uri-list", - ustar = "application/ustar", - utz = "application/uiq.theme", - uu = "text/uuencode", - uva = "audio/dece.audio", - uvd = "application/dece.data", - uvf = "application/dece.data", - uvg = "image/dece.graphic", - uvh = "video/dece.hd", - uvi = "image/dece.graphic", - uvm = "video/dece.mobile", - uvp = "video/dece.pd", - uvs = "video/dece.sd", - uvt = "application/dece.ttml+xml", - uvu = "video/uvvu.mp4", - uvv = "video/dece.video", - uvva = "audio/dece.audio", - uvvd = "application/dece.data", - uvvf = "application/dece.data", - uvvg = "image/dece.graphic", - uvvh = "video/dece.hd", - uvvi = "image/dece.graphic", - uvvm = "video/dece.mobile", - uvvp = "video/dece.pd", - uvvs = "video/dece.sd", - uvvt = "application/dece.ttml+xml", - uvvu = "video/uvvu.mp4", - uvvv = "video/dece.video", - uvvx = "application/dece.unspecified", - uvvz = "application/dece.zip", - uvx = "application/dece.unspecified", - uvz = "application/dece.zip", - vcard = "text/vcard", - vcd = "application/cdlink", - vcf = "text/vcard", - vcg = "application/groove-vcard", - vcs = "text/vcalendar", - vcx = "application/vcx", - vis = "application/visionary", - viv = "video/vivo", - vob = "video/ms-vob", - vor = "application/stardivision.writer", - vox = "application/authorware-bin", - vrml = "model/vrml", - vsd = "application/visio", - vsf = "application/vsf", - vss = "application/visio", - vst = "application/visio", - vsw = "application/visio", - vtu = "model/vtu", - vxml = "application/voicexml+xml", - w3d = "application/director", - wad = "application/doom", - wasm = "application/wasm", - wav = "audio/wav", - wax = "audio/ms-wax", - wbmp = "image/wap.wbmp", - wbs = "application/criticaltools.wbs+xml", - wbxml = "application/wap.wbxml", - wcm = "application/ms-works", - wdb = "application/ms-works", - wdp = "image/ms-photo", - weba = "audio/webm", - webm = "video/webm", - webp = "image/webp", - wg = "application/pmi.widget", - wgt = "application/widget", - wks = "application/ms-works", - wm = "video/ms-wm", - wma = "audio/ms-wma", - wmd = "application/ms-wmd", - wmf = "application/msmetafile", - wml = "text/wap.wml", - wmlc = "application/wap.wmlc", - wmls = "text/wap.wmlscript", - wmlsc = "application/wap.wmlscriptc", - wmv = "video/ms-wmv", - wmx = "video/ms-wmx", - wmz = "application/ms-wmz", - woff = "font/woff", - woff2 = "font/woff2", - wpd = "application/wordperfect", - wpl = "application/ms-wpl", - wps = "application/ms-works", - wqd = "application/wqd", - wri = "application/mswrite", - wrl = "model/vrml", - wsdl = "application/wsdl+xml", - wspolicy = "application/wspolicy+xml", - wtb = "application/webturbo", - wvx = "video/ms-wvx", - x32 = "application/authorware-bin", - x3d = "model/x3d+xml", - x3db = "model/x3d+binary", - x3dbz = "model/x3d+binary", - x3dv = "model/x3d+vrml", - x3dvz = "model/x3d+vrml", - x3dz = "model/x3d+xml", - xaml = "application/xaml+xml", - xap = "application/silverlight-app", - xar = "application/xara", - xbap = "application/ms-xbap", - xbd = "application/fujixerox.docuworks.binder", - xbm = "image/xbitmap", - xdf = "application/xcap-diff+xml", - xdm = "application/syncml.dm+xml", - xdp = "application/adobe.xdp+xml", - xdssc = "application/dssc+xml", - xdw = "application/fujixerox.docuworks", - xenc = "application/xenc+xml", - xer = "application/patch-ops-error+xml", - xfdf = "application/adobe.xfdf", - xfdl = "application/xfdl", - xht = "application/xhtml+xml", - xhtml = "application/xhtml+xml", - xhvml = "application/xv+xml", - xif = "image/xiff", - xla = "application/ms-excel", - xlam = "application/ms-excel.addin.macroenabled.12", - xlc = "application/ms-excel", - xlf = "application/xliff+xml", - xlm = "application/ms-excel", - xls = "application/ms-excel", - xlsb = "application/ms-excel.sheet.binary.macroenabled.12", - xlsm = "application/ms-excel.sheet.macroenabled.12", - xlsx = "application/openxmlformats-officedocument.spreadsheetml.sheet", - xlt = "application/ms-excel", - xltm = "application/ms-excel.template.macroenabled.12", - xltx = "application/openxmlformats-officedocument.spreadsheetml.template", - xlw = "application/ms-excel", - xm = "audio/xm", - xml = "application/xml", - xo = "application/olpc-sugar", - xop = "application/xop+xml", - xpak = "application/gentoo.xpak", - xpi = "application/xpinstall", - xpl = "application/xproc+xml", - xpm = "image/xpixmap", - xpr = "application/is-xpr", - xps = "application/ms-xpsdocument", - xpw = "application/intercon.formnet", - xpx = "application/intercon.formnet", - xsl = "application/xml", - xslt = "application/xslt+xml", - xsm = "application/syncml+xml", - xspf = "application/xspf+xml", - xul = "application/mozilla.xul+xml", - xvm = "application/xv+xml", - xvml = "application/xv+xml", - xwd = "image/xwindowdump", - xyz = "chemical/xyz", - xz = "application/xz", - yaml = "text/yaml", - yang = "application/yang", - yin = "application/yin+xml", - yml = "text/yaml", - z1 = "application/zmachine", - z2 = "application/zmachine", - z3 = "application/zmachine", - z4 = "application/zmachine", - z5 = "application/zmachine", - z6 = "application/zmachine", - z7 = "application/zmachine", - z8 = "application/zmachine", - zaz = "application/zzazz.deck+xml", - zip = "application/zip", - zir = "application/zul", - zirz = "application/zul", - zmm = "application/handheld-entertainment+xml", - zsh = "text/shellscript", -} - -local options = ya.sync(function(st) - return { - with_files = st.with_files, - with_exts = st.with_exts, - fallback_file1 = st.fallback_file1, - } -end) - -local M = {} - -function M:setup(opts) - opts = opts or {} - - self.with_files = opts.with_files - self.with_exts = opts.with_exts - self.fallback_file1 = opts.fallback_file1 -end - -function M:fetch(job) - local opts = options() - local merged_files = ya.dict_merge(FILES, opts.with_files or {}) - local merged_exts = ya.dict_merge(EXTS, opts.with_exts or {}) - - local updates, unknown, state = {}, {}, {} - for i, file in ipairs(job.files) do - if file.cha.is_dummy then - state[i] = false - goto continue - end - - local mime - if file.cha.len == 0 then - mime = "inode/empty" - else - mime = merged_files[(file.url.name or ""):lower()] - mime = mime or merged_exts[(file.url.ext or ""):lower()] - end - - if mime then - updates[tostring(file.url)], state[i] = mime, true - elseif opts.fallback_file1 then - unknown[#unknown + 1] = file - else - updates[tostring(file.url)], state[i] = "application/octet-stream", true - end - ::continue:: - end - - if next(updates) then - ya.emit("update_mimes", { updates = updates }) - end - - if #unknown > 0 then - return self.fallback_builtin(job, unknown, state) - end - - return state -end - -function M.fallback_builtin(job, unknown, state) - local indices = {} - for i, f in ipairs(job.files) do - indices[f:hash()] = i - end - - local result = require("mime.local"):fetch(ya.dict_merge(job, { files = unknown })) - for i, f in ipairs(unknown) do - if type(result) == "table" then - state[indices[f:hash()]] = result[i] - else - state[indices[f:hash()]] = result - end - end - return state -end - -return M +return {} diff --git a/yazi/.config/yazi/plugins/mime-ext.yazi/remote.lua b/yazi/.config/yazi/plugins/mime-ext.yazi/remote.lua new file mode 100644 index 0000000..7626434 --- /dev/null +++ b/yazi/.config/yazi/plugins/mime-ext.yazi/remote.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/rich-preview.yazi/main.lua b/yazi/.config/yazi/plugins/rich-preview.yazi/main.lua index a75003f..dfc56dc 100644 --- a/yazi/.config/yazi/plugins/rich-preview.yazi/main.lua +++ b/yazi/.config/yazi/plugins/rich-preview.yazi/main.lua @@ -2,6 +2,7 @@ local M = {} function M:peek(job) local child = Command("rich") + :env("COLUMNS", tostring(job.area.w)) :arg({ "-j", "--left", @@ -11,7 +12,7 @@ function M:peek(job) "--guides", "--max-width", tostring(job.area.w), - tostring(job.file.url), + tostring(job.file.url.path), }) :stdout(Command.PIPED) :stderr(Command.PIPED) @@ -22,22 +23,27 @@ function M:peek(job) end local limit = job.area.h - local i, lines = 0, "" + local i, lines, errs = 0, "", 0 repeat local next, event = child:read_line() if event == 1 then - return require("code"):peek(job) + errs = errs + 1 elseif event ~= 0 then break - end - - i = i + 1 - if i > job.skip then - lines = lines .. next + else + i = i + 1 + if i > job.skip then + lines = lines .. next + end end until i >= job.skip + limit 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 ya.emit("peek", { math.max(0, i - limit), only_if = job.file.url, upper_bound = true }) else diff --git a/yazi/.config/yazi/plugins/smart-enter.yazi/README.md b/yazi/.config/yazi/plugins/smart-enter.yazi/README.md index 742f2e1..9c19bc7 100644 --- a/yazi/.config/yazi/plugins/smart-enter.yazi/README.md +++ b/yazi/.config/yazi/plugins/smart-enter.yazi/README.md @@ -21,7 +21,7 @@ desc = "Enter the child directory, or open the file" ## 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. If you still want `open` to target multiple selected files, add this to your `~/.config/yazi/init.lua`: diff --git a/yazi/.config/yazi/plugins/whoosh.yazi/.luarc.json b/yazi/.config/yazi/plugins/whoosh.yazi/.luarc.json deleted file mode 100644 index ceb4ebc..0000000 --- a/yazi/.config/yazi/plugins/whoosh.yazi/.luarc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", - "diagnostics.globals": ["ya", "ui", "cx", "ps"] -} diff --git a/yazi/.config/yazi/plugins/whoosh.yazi/LICENSE b/yazi/.config/yazi/plugins/whoosh.yazi/LICENSE index c4eb9b8..9393e1a 100644 --- a/yazi/.config/yazi/plugins/whoosh.yazi/LICENSE +++ b/yazi/.config/yazi/plugins/whoosh.yazi/LICENSE @@ -1,6 +1,6 @@ MIT License - Copyright (c) 2025 Hunter Hwang + Copyright (c) 2026 WhoSowSee Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/yazi/.config/yazi/plugins/whoosh.yazi/README-RU.md b/yazi/.config/yazi/plugins/whoosh.yazi/README-RU.md deleted file mode 100644 index 3726455..0000000 --- a/yazi/.config/yazi/plugins/whoosh.yazi/README-RU.md +++ /dev/null @@ -1,458 +0,0 @@ -

🌀 whoosh.yazi

-

- Молниеносный менеджер закладок для Yazi
- Сохраняйте, ищите и мгновенно переходите к любимым путям -

- ---- -> [!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` - -
- Превью плагина -
- -## Установка - -> [!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 = "", -- Создание временной закладки - fuzzy_search = "", -- Нечеткий поиск (fzf) - history = "", -- Открыть историю директорий - previous_dir = "", -- Вернуться в предыдущую директорию - }, - - -- Путь к файлу для хранения пользовательских закладок - 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 = "" -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` - -### История директорий - -
- Превью истории директорий -
- -Плагин поддерживает умную систему истории директорий: - -- **Независимая история для каждой вкладки** - Каждая вкладка имеет свою собственную историю -- **Автоматическое отслеживание** - История обновляется при переходах между директориями -- **Фильтрация текущей директории** - Текущая директория исключается из отображения истории -- **Настраиваемый размер** - Количество сохраняемых директорий настраивается (по умолчанию 10) -- **Отдельные настройки сокращения** - Независимые настройки отображения путей для истории - -**Поведение системы:** - -- При первом запуске yazi история пуста -- Предыдущие директории добавляются в историю только при переходе в новую директорию -- Новые элементы добавляются в начало списка (сортировка от новых к старым) -- При превышении лимита самые старые элементы удаляются -- Дубликаты автоматически удаляются и перемещаются наверх - -### Функции меню навигации - -При использовании `jump_by_key` вы получаете доступ к умному меню навигации с: - -- **Создание временной закладки** - Нажмите `` для быстрого создания закладки текущей директории -- **Нечеткий поиск** - Нажмите `` для открытия поиска fzf -- **История директорий** - Нажмите `` для просмотра истории через fzf (только если есть история) -- **Предыдущая директория** - Нажмите `` для возврата к предыдущей директории (только если доступно) -- **Все закладки** - Как постоянные, так и временные закладки с четким визуальным различием - -### Навигация по истории директорий - -Плагин предоставляет два способа навигации по истории: - -1. **Через меню навигации** - При использовании `jump_by_key` нажмите `` для доступа к истории -2. **Прямой доступ** - Используйте настроенную специальную клавишу истории (по умолчанию ``) для прямого доступа к fzf с историей - -#### Примечание о клавише `` в Neovim (yazi.nvim) - -При запуске Whoosh внутри [mikavilpas/yazi.nvim](https://github.com/mikavilpas/yazi.nvim) стандартная привязка `` (`cycle_open_buffers`) обрабатывается самим Neovim, поэтому Yazi не получает нажатие. Если нажатие `` возвращает вас в буфер, из которого открывался Yazi, отключите или переназначьте эту клавишу в настройках yazi.nvim, чтобы корректно вызывать историю директорий: - -```lua - opts = { - keymaps = { - cycle_open_buffers = false, - }, - -- OR - keymaps = { - cycle_open_buffers = "", - }, - }, -``` - -Полный пример конфигурации: - -```lua -return { - "mikavilpas/yazi.nvim", - version = "*", - event = "VeryLazy", - dependencies = { { "nvim-lua/plenary.nvim", lazy = true } }, - keys = { - { "-", mode = { "n", "v" }, "Yazi", desc = "Open Yazi" }, - { "cw", "Yazi cwd", desc = "Open Yazi at CWD" }, - }, - opts = { - open_for_directories = false, - keymaps = { - cycle_open_buffers = false, - }, - }, - - init = function() vim.g.loaded_netrwPlugin = 1 end, -} -``` - -Если вы хотите сохранить привязку `` за Neovim, но при этом иметь доступ к истории директорий, переназначьте горячую клавишу whoosh через `special_keys` в файле `init.lua`: - -```lua -require("whoosh"):setup { - special_keys = { - history = "", - }, -} -``` - -### Типы закладок - -Плагин поддерживает три типа закладок: - -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_` | Мгновенный переход к закладке по указанной последовательности клавиш | -| `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_` - последовательность внутри аргумента, например `jump_key_k`, `jump_key_`, `jump_key_bb`. - -Последовательности должны передаваться единым аргументом; формы с разделением пробелами не поддерживаются. Формат совпадает с окном редактирования закладки, поэтому можно комбинировать отдельные символы, значения через запятую и специальные клавиши вроде `` или ``. - -### Управление меню навигации - -При использовании `jump_by_key` доступны следующие специальные элементы управления: - -| Клавиша по умолчанию | Действие | -| -------------------- | -------------------------------------------------------- | -| `` | Создать временную закладку для текущей директории | -| `` | Открыть нечеткий поиск закладок | -| `` | Открыть историю директорий (только если есть история) | -| `` | Вернуться к предыдущей директории (только если доступно) | -| `[a-zA-Z0-9]` | Перейти к закладке с соответствующей клавишей | - -## Вдохновлено - -- [yamb](https://github.com/h-hg/yamb.yazi) -- [bunny](https://github.com/stelcodes/bunny.yazi) diff --git a/yazi/.config/yazi/plugins/whoosh.yazi/README.md b/yazi/.config/yazi/plugins/whoosh.yazi/README.md index 407ee22..47be647 100644 --- a/yazi/.config/yazi/plugins/whoosh.yazi/README.md +++ b/yazi/.config/yazi/plugins/whoosh.yazi/README.md @@ -4,6 +4,10 @@ Save, search, and jump to your favorite paths in a blink

+

+ Plugin preview +

+ --- > [!TIP] @@ -22,25 +26,26 @@ > - **Directory history** - Navigate back to previous directory with Backspace > - **Tab history navigation** - Browse and jump to recently visited directories with Tab key > - **Quick bookmark creation** - Create temporary bookmarks directly from navigation menu -> - **Configurable menu shortcuts** - Override the default Tab/Backspace/Enter/Space bindings from `init.lua` - -
- Plugin preview -
+> - **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` ## Installation > [!IMPORTANT] > Requires Yazi v26.1.4+ +```sh +ya pkg add WhoSowSee/whoosh +``` + ```sh # Manual installation # 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 -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 @@ -94,6 +99,7 @@ require("whoosh"):setup { fuzzy_search = "", -- Launch fuzzy search (fzf) history = "", -- Open directory history previous_dir = "", -- Jump back to the previous directory + project_root = "-", -- Jump to the current Git repository root }, -- 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 `` to open fzf search - **Directory history** - Press `` to browse history via fzf (only if history exists) - **Previous directory** - Press `` 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 ### 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 `` instead of `` 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 `` and ``. Assign one of the +bookmarks a different key combination. + ### Bookmark Types 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) | | `jump_notify` | boolean | `false` | Show notification when jumping to a bookmark | | `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 | | `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 | @@ -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 `` or ``. +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 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: | `` | Open fuzzy search | | `` | Open directory history (only if history exists) | | `` | Return to previous directory (if available) | +| `-` | Jump to current Git repository root | | `[a-zA-Z0-9]` | Jump to bookmark with corresponding key | ## Inspiration - [yamb](https://github.com/h-hg/yamb.yazi) - [bunny](https://github.com/stelcodes/bunny.yazi) + +## Star History + +

+ + + + + Stargazers over time + + +

+ +

+ catppuccin +

+ +

+ © 2026-present WhoSowSee +

+ +

+ LICENSE +

diff --git a/yazi/.config/yazi/plugins/whoosh.yazi/bookmarks b/yazi/.config/yazi/plugins/whoosh.yazi/bookmarks index 5181019..e69de29 100644 --- a/yazi/.config/yazi/plugins/whoosh.yazi/bookmarks +++ b/yazi/.config/yazi/plugins/whoosh.yazi/bookmarks @@ -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 diff --git a/yazi/.config/yazi/plugins/whoosh.yazi/image/history.png b/yazi/.config/yazi/plugins/whoosh.yazi/image/history.png deleted file mode 100644 index 3a00481..0000000 Binary files a/yazi/.config/yazi/plugins/whoosh.yazi/image/history.png and /dev/null differ diff --git a/yazi/.config/yazi/plugins/whoosh.yazi/image/plugin.png b/yazi/.config/yazi/plugins/whoosh.yazi/image/plugin.png deleted file mode 100644 index 4bba237..0000000 Binary files a/yazi/.config/yazi/plugins/whoosh.yazi/image/plugin.png and /dev/null differ diff --git a/yazi/.config/yazi/plugins/whoosh.yazi/main.lua b/yazi/.config/yazi/plugins/whoosh.yazi/main.lua index a53d242..c4cec2d 100644 --- a/yazi/.config/yazi/plugins/whoosh.yazi/main.lua +++ b/yazi/.config/yazi/plugins/whoosh.yazi/main.lua @@ -5,6 +5,7 @@ local path_sep = package.config:sub(1, 1) local DEFAULT_SPECIAL_KEYS = { create_temp = "", fuzzy_search = "", + project_root = "-", history = "", previous_dir = "", } @@ -18,14 +19,6 @@ local function default(value, fallback) return value 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 h = cx.active.current.hovered if h then @@ -110,13 +103,15 @@ end) local function ensure_directory(path) local dir_path = path:match("(.+)[\\/][^\\/]*$") if not dir_path then - return + return true end - if ya.target_family() == "windows" then - os.execute('mkdir "' .. dir_path:gsub("/", "\\") .. '" 2>nul') - else - os.execute('mkdir -p "' .. dir_path .. '"') + + local ok, err = fs.create("dir_all", Url(dir_path)) + if not ok then + notify("Failed to create directory: " .. dir_path .. "\nError: " .. tostring(err), "error", 2) + return false end + return true end local function normalize_path(path) @@ -138,6 +133,81 @@ local function normalize_path(path) return normalized_path 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) if not path or path == "" then return path @@ -518,11 +588,14 @@ local function deserialize_key_from_file(key_str) end local save_to_file = function(mb_path, bookmarks) - ensure_directory(mb_path) - local file = io.open(mb_path, "w") + if not ensure_directory(mb_path) then + return false + end + + local file, open_err = io.open(mb_path, "w") if file == nil then - notify("Cannot create bookmark file: " .. mb_path, "error", 2) - return + notify("Cannot create bookmark file: " .. mb_path .. "\nError: " .. tostring(open_err), "error", 2) + return false end local array = {} for _, item in pairs(bookmarks) do @@ -531,9 +604,55 @@ local save_to_file = function(mb_path, bookmarks) sort_bookmarks(array, "tag", "key", true) for _, item in ipairs(array) do 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 - 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 -- Unified fzf bookmark picker. opts: @@ -550,69 +669,71 @@ local function run_fzf_picker(opts) local temp_bookmarks = get_temp_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 temp_file_path = os.tmpname() - local temp_file = io.open(temp_file_path, "w") - local cmd - if temp_file then - local items = {} - local max_tag_w, max_path_w = 0, 0 + local args = { + "--prompt=" .. prompt, + } - 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 + if #items > 0 then + args = { + table.unpack(args), + "--delimiter=\t", + "--with-nth=1", + } + if opts.multi then + table.insert(args, "--multi") 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 - - 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) + local input = {} + 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) + table.insert(input, formatted_line .. "\t" .. item.path) end else - cmd = string.format("echo %s | fzf --prompt=\"%s\"", empty_msg, prompt) + table.insert(input, empty_msg) end - local handle = io.popen(cmd, "r") - local raw = "" - if handle then - raw = handle:read("*all") or "" - handle:close() - end - - if temp_file_path then os.remove(temp_file_path) end + local output, err = run_fzf_with(args, input) permit:drop() + if not output then + notify(err, "error", 2) + return {} + end 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") if line ~= "" and line ~= empty_msg then local tab_pos = line:find("\t") @@ -655,37 +776,33 @@ fzf_history = function() end local permit = ui.hide() - local temp_file_path = os.tmpname() - local temp_file = io.open(temp_file_path, "w") - if temp_file then - 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) - temp_file:write(formatted_line .. "\t" .. path .. "\n") + local args = { + "--delimiter=\t", + "--with-nth=1", + "--prompt=History > ", + } + + 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 - 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 return nil @@ -694,6 +811,8 @@ end local create_special_menu_items = function() local special_items = {} 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 if create_temp_key then 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 history = get_tab_history(current_tab) - local current_path = normalize_path(get_current_dir_path()) local filtered_history = {} 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 }) 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 end @@ -1097,8 +1227,9 @@ action_save = function(path, is_temp) else set_bookmarks(path, { tag = tag, path = path, key = key }) local user_bookmarks = get_state_attr("bookmarks") - save_to_file(mb_path, user_bookmarks) - notify('"' .. tag .. '" saved') + if save_to_file(mb_path, user_bookmarks) then + notify('"' .. tag .. '" saved') + end end end @@ -1123,8 +1254,9 @@ action_delete = function(path) else set_bookmarks(path, nil) local updated_user_bookmarks = get_state_attr("bookmarks") - save_to_file(mb_path, updated_user_bookmarks) - notify('"' .. tag .. '" deleted') + if save_to_file(mb_path, updated_user_bookmarks) then + notify('"' .. tag .. '" deleted') + end end end @@ -1162,7 +1294,9 @@ action_delete_multi = function(paths) if deleted_count > 0 then 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 local total_deleted = deleted_count + deleted_temp_count @@ -1203,8 +1337,9 @@ local action_delete_all = function(temp_only) notify("All temporary bookmarks deleted") else set_state_attr("bookmarks", {}) - save_to_file(mb_path, {}) - notify("All user-created bookmarks deleted") + if save_to_file(mb_path, {}) then + notify("All user-created bookmarks deleted") + end end end @@ -1246,7 +1381,6 @@ return { end state.special_keys = special_keys - ensure_directory(state.path) local keys = options.keys or "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" state.keys, state.key2rank = {}, {} for i = 1, #keys do diff --git a/yazi/.config/yazi/yazi.toml b/yazi/.config/yazi/yazi.toml index 4407362..12ea8a9 100644 --- a/yazi/.config/yazi/yazi.toml +++ b/yazi/.config/yazi/yazi.toml @@ -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] -# it also support --mode=or when using with --input or --keys=!1q or --key=!1q or --tag=!1q or --tags=!1q -ratio = [2, 3, 5] -sort_by = "natural" +ratio = [1, 2, 3] +sort_by = "alphabetical" sort_sensitive = false sort_reverse = false sort_dir_first = true +sort_translit = false +sort_fallback = "alphabetical" linemode = "none" -show_hidden = true +show_hidden = false show_symlink = true scrolloff = 5 +mouse_events = ["click", "scroll", "drag"] [preview] +wrap = "no" tab_size = 2 max_width = 600 max_height = 900 @@ -18,127 +26,331 @@ cache_dir = "" image_delay = 30 image_filter = "triangle" image_quality = 75 +ueberzug_scale = 1 +ueberzug_offset = [0, 0, 0, 0] [opener] -# edit = [ -# { run = '$EDITOR "$@"', block = true, for = "unix" }, -# ] -open = [{ run = 'xdg-open "$1"', desc = "Open", orphan = true }] -edit = [{ run = 'nvim "$@"', block = true, desc = "nvim", for = "unix" }] +edit = [ + { run = "${EDITOR:-vi} %s", desc = "$EDITOR", for = "unix", block = true }, + { run = "code %s", desc = "code", for = "windows", orphan = true }, + { run = "code -w %s", desc = "code (block)", for = "windows", block = true }, +] +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 = [ - { 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] rules = [ - # Empty files - { mime = "inode/empty", use = "edit" }, - # { mime = "inode/x-empty", use = "edit" }, - # Text files - use edit (which uses $EDITOR/nvim) - { mime = "text/*", use = "edit" }, # Changed from "text" to "edit" - # { mime = "text/x-tex", use = "edit" }, # Changed from "text" to "edit" - { mime = "application/json", use = "edit" }, - { mime = "application/x-sh", use = "edit" }, - { mime = "application/x-shellscript", use = "edit" }, - { mime = "application/javascript", use = "edit" }, - # Fallback by extension - { url = "*.sh", use = "edit" }, - { url = "*.json", use = "edit" }, - { url = "*.md", use = "edit" }, - { url = "*.txt", use = "edit" }, - { url = "*.css", use = "edit" }, - { url = "*.js", use = "edit" }, - { url = "*.toml", use = "edit" }, - { url = "*.yaml", use = "edit" }, - { url = "*.yml", use = "edit" }, - { url = "*.tex", use = "edit" }, # Uncommented! - { url = "*.text", use = "edit" }, - { url = "*.epub", use = "edit" }, - { url = "*,kdl", use = "edit" }, - # Media files - { mime = "image/*", use = "open" }, - { mime = "video/*", use = "play" }, - { mime = "audio/*", use = "play" }, - { mime = "application/pdf", use = "pdf" }, + # Folder + { url = "*/", use = [ + "edit", + "open", + "reveal", + ] }, + # Text + { mime = "text/*", use = [ + "edit", + "reveal", + ] }, + # Image + { mime = "image/*", use = [ + "open", + "reveal", + ] }, + # Media + { mime = "{audio,video}/*", use = [ + "play", + "reveal", + ] }, + # Code + { mime = "application/{json,ndjson,javascript,wine-extension-ini}", use = [ + "edit", + "reveal", + ] }, + # Archive + { mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", use = [ + "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 - { mime = "*", use = "open" }, + { url = "*", use = [ + "open", + "reveal", + ] }, ] [tasks] -micro_workers = 10 -macro_workers = 25 -bizarre_retry = 5 +file_workers = 3 +plugin_workers = 5 +fetch_workers = 5 +preload_workers = 2 +process_workers = 5 +bizarre_retry = 3 +image_alloc = 536870912 # 512MB +image_bound = [10000, 10000] suppress_preload = false [plugin] -prepend_fetchers = [ - { group = "mime", id = "mime", url = "*", run = "mime-ext", prio = "high" }, - # git plugin - { group = "mime", id = "git", url = "*", run = "git" }, - { group = "mime", id = "git", url = "*/", run = "git" }, - # mime plugin - { group = "mime", id = "simple-tag", url = "*", run = "simple-tag" }, - { group = "mime", id = "simple-tag", url = "*/", run = "simple-tag" }, +fetchers = [ + # MIME-type + { url = "*/", run = "mime.dir", prio = "high", group = "mime" }, + { url = "local://*", run = "mime.local", prio = "high", group = "mime" }, + { url = "trash://*", run = "mime.trash", prio = "high", group = "mime" }, + { url = "remote://*", run = "mime.remote", prio = "high", group = "mime" }, +] + +### 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 = [ # 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" }, # Video - # { mime = "video/*", run = "video" }, + { mime = "video/*", run = "video" }, # PDF { mime = "application/pdf", run = "pdf" }, # Font { mime = "font/*", run = "font" }, { mime = "application/ms-opentype", run = "font" }, -] -prepend_preloaders = [ - # Replace magick, image, video with mediainfo - { mime = "{audio,video,image}/*", run = "mediainfo" }, - { mime = "application/subrip", run = "mediainfo" }, - # Adobe Illustrator, Adobe Photoshop is image/adobe.photoshop, already handled above - { mime = "application/postscript", run = "mediainfo" }, -] -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" }, + # Trash + { mime = "trash/**", run = "trash" }, ] prepend_previewers = [ - # mediainfo MUST come before the catch-all piper rule - { 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 + ### rich-preview ### { url = "*.csv", run = "rich-preview" }, # for csv files { url = "*.md", run = "rich-preview" }, # for markdown (.md) files { url = "*.rst", run = "rich-preview" }, # for restructured text (.rst) files { url = "*.ipynb", run = "rich-preview" }, # for jupyter notebooks (.ipynb) { 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 diff --git a/yazi/.config/yazi/plugins/full-border.yazi/LICENSE b/yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/full-border.yazi/LICENSE rename to yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE diff --git a/yazi/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE-tmtheme b/yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE-tmtheme similarity index 100% rename from yazi/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE-tmtheme rename to yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE-tmtheme diff --git a/yazi/.config/yazi/flavors/catppuccin-mocha.yazi/README.md b/yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/README.md similarity index 100% rename from yazi/.config/yazi/flavors/catppuccin-mocha.yazi/README.md rename to yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/README.md diff --git a/yazi/.config/yazi/flavors/catppuccin-mocha.yazi/flavor.toml b/yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/flavor.toml similarity index 100% rename from yazi/.config/yazi/flavors/catppuccin-mocha.yazi/flavor.toml rename to yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/flavor.toml diff --git a/yazi/.config/yazi/flavors/catppuccin-mocha.yazi/preview.png b/yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/preview.png similarity index 100% rename from yazi/.config/yazi/flavors/catppuccin-mocha.yazi/preview.png rename to yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/preview.png diff --git a/yazi/.config/yazi/flavors/catppuccin-mocha.yazi/tmtheme.xml b/yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/tmtheme.xml similarity index 100% rename from yazi/.config/yazi/flavors/catppuccin-mocha.yazi/tmtheme.xml rename to yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/tmtheme.xml diff --git a/yazi/.config/yazi/flavors/dracula.yazi b/yazi_1/.config/yazi/flavors/dracula.yazi similarity index 100% rename from yazi/.config/yazi/flavors/dracula.yazi rename to yazi_1/.config/yazi/flavors/dracula.yazi diff --git a/yazi/.config/yazi/flavors/everforest-medium.yazi/LICENSE b/yazi_1/.config/yazi/flavors/everforest-medium.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/flavors/everforest-medium.yazi/LICENSE rename to yazi_1/.config/yazi/flavors/everforest-medium.yazi/LICENSE diff --git a/yazi/.config/yazi/flavors/everforest-medium.yazi/LICENSE-tmtheme b/yazi_1/.config/yazi/flavors/everforest-medium.yazi/LICENSE-tmtheme similarity index 100% rename from yazi/.config/yazi/flavors/everforest-medium.yazi/LICENSE-tmtheme rename to yazi_1/.config/yazi/flavors/everforest-medium.yazi/LICENSE-tmtheme diff --git a/yazi/.config/yazi/flavors/everforest-medium.yazi/README.md b/yazi_1/.config/yazi/flavors/everforest-medium.yazi/README.md similarity index 100% rename from yazi/.config/yazi/flavors/everforest-medium.yazi/README.md rename to yazi_1/.config/yazi/flavors/everforest-medium.yazi/README.md diff --git a/yazi/.config/yazi/flavors/everforest-medium.yazi/flavor.toml b/yazi_1/.config/yazi/flavors/everforest-medium.yazi/flavor.toml similarity index 100% rename from yazi/.config/yazi/flavors/everforest-medium.yazi/flavor.toml rename to yazi_1/.config/yazi/flavors/everforest-medium.yazi/flavor.toml diff --git a/yazi/.config/yazi/flavors/everforest-medium.yazi/preview.png b/yazi_1/.config/yazi/flavors/everforest-medium.yazi/preview.png similarity index 100% rename from yazi/.config/yazi/flavors/everforest-medium.yazi/preview.png rename to yazi_1/.config/yazi/flavors/everforest-medium.yazi/preview.png diff --git a/yazi/.config/yazi/flavors/everforest-medium.yazi/tmtheme.xml b/yazi_1/.config/yazi/flavors/everforest-medium.yazi/tmtheme.xml similarity index 100% rename from yazi/.config/yazi/flavors/everforest-medium.yazi/tmtheme.xml rename to yazi_1/.config/yazi/flavors/everforest-medium.yazi/tmtheme.xml diff --git a/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/LICENSE b/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/LICENSE new file mode 100644 index 0000000..e6984c6 --- /dev/null +++ b/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/LICENSE @@ -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. + diff --git a/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/LICENSE-tmtheme b/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/LICENSE-tmtheme new file mode 100644 index 0000000..c996915 --- /dev/null +++ b/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/LICENSE-tmtheme @@ -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. diff --git a/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/README.md b/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/README.md new file mode 100644 index 0000000..a6d6964 --- /dev/null +++ b/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/README.md @@ -0,0 +1,32 @@ +
+Yazi logo +
+ +

+ Gruvbox Dark Flavor for Yazi +

+ +## 👀 Preview + + + +## 🎨 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. diff --git a/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/flavor.toml b/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/flavor.toml new file mode 100644 index 0000000..c0aeb59 --- /dev/null +++ b/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/flavor.toml @@ -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" }, +] + +# : }}} + diff --git a/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/preview.png b/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/preview.png new file mode 100644 index 0000000..48cdce8 --- /dev/null +++ b/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/preview.png @@ -0,0 +1 @@ +placeholder diff --git a/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/tmtheme.xml b/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/tmtheme.xml new file mode 100644 index 0000000..59c1ecd --- /dev/null +++ b/yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/tmtheme.xml @@ -0,0 +1,1509 @@ + + + + + comment + Based on gruvbox for Vim (https://github.com/morhetz/gruvbox) + originalAuthor + Pavel Pertsev (https://github.com/morhetz) + author + Brian Reilly (https://github.com/Briles/gruvbox) + name + gruvbox (Dark) (Medium) + colorSpaceName + sRGB + settings + + + settings + + background + #222222 + caret + #a89984 + foreground + #ebdbb280 + invisibles + #ebdbb226 + lineHighlight + #3c3836 + selection + #3c3836 + inactiveSelection + #3c3836 + guide + #ebdbb226 + activeGuide + #ebdbb280 + stackGuide + #ebdbb240 + bracketContentsOptions + underline + bracketContentsForeground + #bdae93 + bracketsOptions + underline + bracketsForeground + #bdae93 + gutterForeground + #928374 + highlight + #fbf1c7 + highlightForeground + #fbf1c7 + findHighlight + #d79921 + findHighlightForeground + #222222 + tagsOptions + underline + selectionBorder + #3c3836 + popupCss + + html { + background-color: #1b1b1b; + color: #fbf1c7; + padding: 12px; + } + + a { + color: #8ec07c; + } + + .error, .deleted { + color: #fb4934; + } + + .success, .inserted, .name { + color: #b8bb26; + } + + .warning, .modified { + color: #fabd2f; + } + + .type { + color: #fabd2f; + font-style: italic; + } + + .param { + color: #fbf1c7; + } + + .current { + text-decoration: underline; + } + + + + + name + Text and Source Base Colors + scope + meta.group, meta.method-call.source.cs, meta.method.attribute.source.cs, meta.method.body.java, meta.method.body.source.cs, meta.method.source.cs, none, source, text + settings + + foreground + #fbf1c7 + + + + name + Punctuation + scope + entity.quasi.element meta.group.braces, keyword.operator keyword.operator.neon, keyword.operator operator.neon, keyword.operator.accessor, keyword.other.accessor, meta.attribute-selector keyword.operator.stylus, meta.brace, meta.delimiter, meta.group.braces, meta.punctuation.separator, meta.separator, punctuation + settings + + foreground + #fbf1c7 + + + + name + Comments + scope + comment, comment text, markup.strikethrough, punctuation.definition.comment, punctuation.whitespace.comment, string.comment, text.cancelled + settings + + fontStyle + italic + foreground + #928374 + + + + name + Keywords Inside Comments + scope + comment.keyword, comment.keyword.punctuation + settings + + foreground + #d5c4a1 + + + + name + DocBlockr & Other Keywords Inside Comments + scope + comment.parameter, comment.punctuation, comment.string, comment.type, keyword.other.phpdoc.php, punctuation.definition.keyword.javadoc, source.groovy keyword.other.documentation, source.java keyword.other.documentation, storage.type.annotation.coffeescript, storage.type.class.jsdoc + settings + + foreground + #bdae93 + + + + name + Entity + scope + constant.language.name, entity.name.type, entity.other.inherited-class + settings + + foreground + #fabd2f + + + + name + Template String Punctuation + scope + constant.other.placeholder, entity.name.tag.mustache, entity.tag.tagbraces, punctuation.definition.string.template, punctuation.definition.template-expression, punctuation.quasi, punctuation.section.embedded, string.interpolated, variable.other.interpolation.scss + settings + + foreground + #8ec07c + + + + name + Keywords + scope + js.embedded.control.flow keyword.operator.js, keyword, keyword.control, keyword.operator.logical.python, meta.at-rule.media support.function.misc, meta.prolog.haml, meta.tag.sgml.doctype.html, storage.type.function.jade, storage.type.function.pug, storage.type.import.haxe, storage.type.import.include.jade, storage.type.import.include.pug, support.keyword.timing-direction, variable.documentroot + settings + + foreground + #fb4934 + + + + name + CSS At-Rule Punctuation (@) & At-Rule Vendor Prefixes + scope + keyword.control.at-rule support.type.property-vendor, punctuation.definition.keyword + settings + + foreground + #cc241d + + + + name + Operators + scope + keyword.control.new, keyword.control.operator, keyword.operator, keyword.other.arrow, keyword.other.double-colon, punctuation.operator + settings + + foreground + #8ec07c + + + + name + Constants Punctuation + scope + constant.other.color punctuation.definition.constant, constant.other.symbol punctuation.definition.constant, constant.other.unit, keyword.other.unit, punctuation.section.flowtype, support.constant.unicode-range.prefix + settings + + foreground + #b16286 + + + + name + Storage + scope + storage, storage.type.annotation, storage.type.primitive + settings + + foreground + #fb4934 + + + + scope + storage.modifier.import, storage.modifier.package, storage.type.import, variable.import, variable.package + settings + + foreground + #fbf1c7 + + + + name + Function Keyword + scope + entity.quasi.tag.name, meta.function storage.type.matlab, storage.type.function + settings + + foreground + #8ec07c + + + + name + Variables + scope + entity.name.val.declaration, entity.name.variable, meta.definition.variable, storage.type.variable, support.type.custom-property, support.type.variable-name, variable, variable.interpolation variable, variable.other.interpolation variable, variable.parameter.dosbatch, variable.parameter.output.function.matlab, variable.parameter.sass + settings + + foreground + #83a598 + + + + name + Variable - Punctuation + scope + keyword.other.custom-property.prefix, punctuation.definition.custom-property, punctuation.definition.variable, support.constant.custom-property-name.prefix, variable.interpolation, variable.other.dollar punctuation.dollar, variable.other.object.dollar punctuation.dollar + settings + + foreground + #458588 + + + + name + Function Declaration - Punctuation + scope + entity.name.function punctuation.dollar + settings + + foreground + #98971a + + + + name + Object Properties + scope + meta.property.object + settings + + foreground + #fbf1c7 + + + + name + Object Literal Properties + scope + constant.other.object.key string, meta.object-literal.key + settings + + foreground + #fbf1c7 + + + + name + Parameters + scope + meta.parameters, variable.parameter + settings + + foreground + #fbf1c7 + + + + name + SASS Import URL + scope + variable.parameter.url + settings + + foreground + #b8bb26 + + + + name + Language Constants + scope + constant, constant.numeric, constant.other, constant.other.color, constant.other.symbol, support.constant, support.constant.color, support.constant.font-name, support.constant.media, support.constant.prototype, variable.language + settings + + foreground + #d3869b + + + + name + Language Constants Punctuation + scope + variable.language punctuation.definition.variable + settings + + foreground + #b16286 + + + + name + User-Defined Constants + scope + entity.name.constant, variable.other.constant + settings + + foreground + #fabd2f + + + + name + Escaped Characters + scope + constant.character.escape, constant.character.escaped, constant.character.quoted, constant.other.character-class.escape + settings + + foreground + #fb4934 + + + + name + Invalids and Illegals + scope + invalid + settings + + foreground + #fbf1c7 + background + #fb4934 + + + + name + Inner Scopes of Invalids and Illegals + scope + invalid keyword.other.custom-property.prefix, invalid support.type.custom-property.name + settings + + foreground + #fbf1c7 + + + + name + Errors + scope + message.error + settings + + foreground + #fb4934 + + + + name + Strings + scope + meta.object-literal.key string, string + settings + + foreground + #b8bb26 + + + + name + JSON Keys + scope + meta.structure.dictionary.key.json string + settings + + foreground + #83a598 + + + + name + Regular Expressions Text + scope + source.regexp, string.regexp + settings + + foreground + #b8bb26 + + + + name + Regular Expressions Start & End Punctuation + scope + string.regexp punctuation.definition.string + settings + + foreground + #bdae93 + + + + name + Regular Expressions Character Class Punctuation ([]) + scope + keyword.control.set.regexp, punctuation.definition.character-class, string.regexp.character-class.ruby + settings + + foreground + #d3869b + + + + name + Regular Expressions Capturing Group + scope + string.regexp.group + settings + + foreground + #fbf1c7 + + + + name + Regular Expressions Assertions + scope + constant.other.assertion.regexp, punctuation.definition.group.assertion.regexp, punctuation.definition.group.capture.regexp + settings + + foreground + #83a598 + + + + name + Regular Expressions Character Class + scope + constant.other.character-class.escape.backslash.regexp, keyword.control.character-class.regexp, string.regexp.character-class constant.character.escape + settings + + foreground + #fabd2f + + + + name + Regular Expressions Quantifiers & Operators + scope + string.regexp.arbitrary-repetition, string.regexp.arbitrary-repetition punctuation + settings + + foreground + #8ec07c + + + + name + Hyperlinks + scope + constant.other.reference.link, string.other.link + settings + + foreground + #8ec07c + + + + name + Hyperlink Punctuation + scope + meta.image.inline punctuation.definition.string, meta.link.inline punctuation.definition.string, meta.link.reference punctuation.definition.constant, meta.link.reference.literal punctuation.definition.constant, meta.link.reference.literal punctuation.definition.string + settings + + foreground + #689d6a + + + + name + Markup Tag Punctuation + scope + punctuation.definition.tag + settings + + foreground + #83a598 + + + + name + Markdown Heading + scope + markup.heading + settings + + foreground + #b8bb26 + + + + name + Markdown Heading Punctuation + scope + punctuation.definition.heading, punctuation.definition.identity + settings + + foreground + #98971a + + + + name + Markdown Bold Text + scope + markup.bold + settings + + foreground + #fe8019 + fontStyle + bold + + + + name + Markdown Bold Text Punctuation + scope + punctuation.definition.bold + settings + + foreground + #d65d0e + fontStyle + bold + + + + name + Markdown Italic Text + scope + markup.italic + settings + + foreground + #fb4934 + fontStyle + italic + + + + name + Markdown Italic Text Punctuation + scope + punctuation.definition.italic + settings + + foreground + #cc241d + fontStyle + italic + + + + name + Markdown Inline Code + scope + markup.raw.inline + settings + + foreground + #fabd2f + + + + name + Markdown Inline Code Punctuation + scope + markup.raw.inline punctuation.definition.raw + settings + + foreground + #d79921 + + + + name + Markdown Quoted + scope + markup.quote + settings + + foreground + #d3869b + + + + name + Markdown Quoted Punctuation + scope + markup.quote punctuation.definition.blockquote + settings + + foreground + #b16286 + + + + name + Markdown List + scope + markup.list + settings + + foreground + #83a598 + + + + name + Markdown List Punctuation + scope + markup.list punctuation.definition.list_item + settings + + foreground + #458588 + + + + name + Markdown Separators + scope + meta.separator.markdown + settings + + foreground + #928374 + + + + name + Support + scope + meta.function-call.constructor variable.type, support.class, support.type, variable.other.class + settings + + foreground + #fabd2f + + + + name + Supporting Type - Dollar Punctuation + scope + support.class punctuation.dollar + settings + + foreground + #d79921 + + + + name + Function Calls + scope + entity.name.function.jade, entity.name.function.pug, keyword.other.special-method, meta.function-call variable.function, meta.function-call variable.other.dollar.only punctuation.dollar, support.function + settings + + foreground + #8ec07c + + + + name + Method Calls + scope + meta.function-call.method support.function, meta.function-call.method variable.function, meta.function-call.static variable.function, meta.method-call, meta.method-call support.function, meta.method-call variable.function, support.function.mutator + settings + + foreground + #689d6a + + + + name + Special Variables + scope + support.module + settings + + foreground + #d3869b + + + + name + Entities + scope + entity.name.accessor, entity.name.function, entity.name.label, entity.name.section + settings + + foreground + #b8bb26 + + + + name + Modules + scope + entity.name.module + settings + + foreground + #fe8019 + + + + name + HTML & CSS ID + scope + constant.id.tag, entity.name.tag.id, entity.other.attribute-name.id + settings + + foreground + #fe8019 + + + + name + HTML & CSS ID Punctuation (#) + scope + entity.other.attribute-name.id punctuation.definition.entity + settings + + foreground + #d65d0e + + + + name + HTML & CSS Class + scope + entity.name.tag.class, entity.other.attribute-name.class + settings + + foreground + #fabd2f + + + + name + HTML & CSS Class Punctuation (.) + scope + entity.other.attribute-name.class punctuation.definition.entity + settings + + foreground + #d79921 + + + + name + CSS Attribute Selector Attribute Name + scope + meta.attribute-selector entity.other.attribute-name + settings + + foreground + #d79921 + + + + name + HTML & XML Entity Punctuation + scope + constant.character.entity punctuation.definition.constant, constant.character.entity punctuation.definition.entity + settings + + foreground + #b16286 + + + + scope + entity.name.class, entity.name.type.class + settings + + foreground + #fabd2f + + + + name + Markup Tag + scope + entity.name.function.neon, entity.name.namespace.wildcard, entity.name.tag, entity.tag, keyword.control.untitled, keyword.doctype.xml, keyword.operator support.other.neon, punctuation.definition.prolog.haml, source.less keyword.control.html.elements + settings + + foreground + #83a598 + + + + name + HTML Attribute Names + scope + entity.name.attribute-name, entity.other.attribute-name, meta.section.attributes.haml constant.other.symbol.ruby + settings + + foreground + #fabd2f + + + + name + CSS Pseudo Elements/Classes & Vendor Prefixes + scope + entity.other.attribute-name.placeholder punctuation.definition.entity, entity.other.attribute-name.pseudo-class, entity.other.attribute-name.pseudo-element, entity.other.attribute-name.tag.pseudo-class, entity.other.attribute-name.tag.pseudo-element, entity.other.pseudo-class, entity.other.pseudo-element, support.type.vendor-prefix + settings + + foreground + #d79921 + + + + name + CSS Animations / Keyframes + scope + entity.function-name.stylus, entity.other.animation-keyframe.stylus, entity.other.animation-name, keyword.language.function.misc.stylus, meta.at-rule.keyframes entity.name.function, variable.other.animation-name + settings + + foreground + #8ec07c + + + + name + Author-Defined Names + scope + entity.other.namespace-prefix + settings + + foreground + #8ec07c + + + + scope + meta.class.body, meta.tag + settings + + foreground + #fbf1c7 + + + + name + Markdown Image & Hyperlink + scope + meta.image, meta.link + settings + + foreground + #d3869b + + + + name + Markdown Image & Hyperlink Punctuation + scope + meta.image punctuation.definition.metadata, meta.link punctuation.definition.metadata + settings + + foreground + #b16286 + + + + scope + meta.require + settings + + foreground + #b8bb26 + + + + name + Function Call Braces + scope + constant.name.attribute.tag.jade, constant.name.attribute.tag.pug, meta.brace.round, meta.function-call meta.group punctuation.definition.group, punctuation.definition.method-parameters, punctuation.definition.parameters + settings + + foreground + #bdae93 + + + + name + CSS Property Names + scope + meta.property-name, support.type.property-name, support.type.shape.definition support.constant.property-value + settings + + foreground + #b8bb26 + + + + name + CSS Property Name Vendor Prefixes + scope + meta.property-name support.type.vendor-prefix, support.type.property-name.media support.type.vendor-prefix + settings + + foreground + #98971a + + + + name + CSS Property Values + scope + constant.string.sass, meta.property-value, support.constant.property-value + settings + + foreground + #fbf1c7 + + + + name + CSS Property Value Vendor Prefixes + scope + meta.property-value support.type.vendor-prefix + settings + + foreground + #bdae93 + + + + name + Diff Foreground Text + scope + source.diff + settings + + foreground + #a89984 + + + + name + Diff Header Text From + scope + meta.diff.header.from-file + settings + + foreground + #83a598 + + + + name + Diff Header Text From Punctuation + scope + punctuation.definition.from-file + settings + + foreground + #458588 + + + + name + Diff Header Text To + scope + meta.diff.header.to-file + settings + + foreground + #d3869b + + + + name + Diff Header Text To Punctuation + scope + punctuation.definition.to-file + settings + + foreground + #b16286 + + + + name + Diff Additions & Deletions Stats + scope + meta.diff.range, meta.toc-list.line-number + settings + + foreground + #fabd2f + + + + name + Diff Additions & Deletions Stats Punctuation + scope + punctuation.definition.range.diff + settings + + foreground + #d79921 + + + + name + FiF Line Number + scope + constant.numeric.line-number + settings + + foreground + #928374 + + + + name + FiF Line Number Matched + scope + constant.numeric.line-number.match + settings + + foreground + #8ec07c + + + + name + FiF Filename + scope + entity.name.filename + settings + + foreground + #b8bb26 + + + + name + GitGutter & Diff Deleted + scope + markup.deleted, punctuation.definition.deleted + settings + + foreground + #fb4934 + + + + name + GitGutter & Diff Inserted + scope + markup.inserted, punctuation.definition.inserted + settings + + foreground + #b8bb26 + + + + name + GitGutter & Diff Changed + scope + markup.changed, punctuation.definition.changed + settings + + foreground + #fabd2f + + + + name + GitGutter ignored + scope + markup.ignored + settings + + foreground + #928374 + + + + name + GitGutter untracked + scope + markup.untracked + settings + + foreground + #928374 + + + + name + Bracket Tag + scope + brackethighlighter.tag + settings + + foreground + #bdae93 + + + + name + Bracket Curly + scope + brackethighlighter.curly + settings + + foreground + #bdae93 + + + + name + Bracket Round + scope + brackethighlighter.round + settings + + foreground + #bdae93 + + + + name + Bracket Square + scope + brackethighlighter.square + settings + + foreground + #bdae93 + + + + name + Bracket Angle + scope + brackethighlighter.angle + settings + + foreground + #bdae93 + + + + name + Bracket Quote + scope + brackethighlighter.quote + settings + + foreground + #bdae93 + + + + name + Bracket Unmatched + scope + brackethighlighter.unmatched + settings + + foreground + #fb4934 + + + + name + SublimeLinter Error + scope + sublimelinter.mark.error + settings + + foreground + #fb4934 + + + + name + SublimeLinter Gutter Mark + scope + sublimelinter.gutter-mark + settings + + foreground + #928374 + + + + name + SublimeLinter Warning + scope + sublimelinter.mark.warning + settings + + foreground + #fabd2f + + + + name + HexViewer Upper Byte Nibble + scope + raw.nibble.upper + settings + + foreground + #fbf1c7 + + + + name + HexViewer Lower Byte Nibble + scope + raw.nibble.lower + settings + + foreground + #fbf1c7 + + + + name + HexViewer Highlight + scope + hexviewer.highlight + settings + + foreground + #222222 + background + #fabd2f + + + + name + HexViewer Edited Highlight + scope + hexviewer.highlight.edited + settings + + foreground + #222222 + background + #fe8019 + + + + name + Raw New Line: Carriage Return + scope + glyph.carriage-return + settings + + foreground + #ebdbb226 + + + + name + Raw New Line: New Line Glyph + scope + glyph.new-line + settings + + foreground + #ebdbb226 + + + + name + PlainTasks: Header + scope + keyword.control.header.todo + settings + + foreground + #b8bb26 + background + #3a3a3a + + + + name + PlainTasks: Notes + scope + notes.todo + settings + + foreground + #bdae93 + + + + name + PlainTasks: Punctuation + scope + text.todo punctuation.definition.bold, text.todo punctuation.definition.italic + settings + + foreground + #7c6f64 + + + + name + PlainTasks: Task Pending + scope + meta.item.todo.pending + settings + + foreground + #fbf1c7 + + + + name + PlainTasks: Task Pending Punctuation + scope + punctuation.definition.bullet.pending.todo + settings + + foreground + #928374 + + + + name + PlainTasks: Task Completed Punctuation + scope + punctuation.definition.bullet.completed.todo + settings + + foreground + #8ec07c + + + + name + PlainTasks: Task Cancelled Punctuation + scope + punctuation.definition.bullet.cancelled.todo + settings + + foreground + #fb4934 + + + + name + PlainTasks: Tag Critical + scope + string.other.tag.todo.critical + settings + + foreground + #fb4934 + fontStyle + bold + + + + name + PlainTasks: Tag High + scope + string.other.tag.todo.high + settings + + foreground + #fe8019 + fontStyle + bold + + + + name + PlainTasks: Tag Low + scope + string.other.tag.todo.low + settings + + foreground + #83a598 + fontStyle + bold + + + + name + PlainTasks: Tag Today + scope + string.other.tag.todo.today + settings + + foreground + #fabd2f + fontStyle + bold + + + + name + PlainTasks: Tag + scope + meta.tag.todo + settings + + foreground + #d3869b + + + + name + PlainTasks: URL + scope + punctuation.definition.url, todo.url + settings + + foreground + #83a598 + + + + name + PlainTasks: Separator + scope + meta.punctuation.archive.todo, meta.punctuation.separator.todo + settings + + fontStyle + italic + foreground + #928374 + + + + + diff --git a/yazi/.config/yazi/flavors/gruvbox-material.yazi/LICENSE b/yazi_1/.config/yazi/flavors/gruvbox-material.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/flavors/gruvbox-material.yazi/LICENSE rename to yazi_1/.config/yazi/flavors/gruvbox-material.yazi/LICENSE diff --git a/yazi/.config/yazi/flavors/gruvbox-material.yazi/LICENSE-tmtheme b/yazi_1/.config/yazi/flavors/gruvbox-material.yazi/LICENSE-tmtheme similarity index 100% rename from yazi/.config/yazi/flavors/gruvbox-material.yazi/LICENSE-tmtheme rename to yazi_1/.config/yazi/flavors/gruvbox-material.yazi/LICENSE-tmtheme diff --git a/yazi/.config/yazi/flavors/gruvbox-material.yazi/README.md b/yazi_1/.config/yazi/flavors/gruvbox-material.yazi/README.md similarity index 100% rename from yazi/.config/yazi/flavors/gruvbox-material.yazi/README.md rename to yazi_1/.config/yazi/flavors/gruvbox-material.yazi/README.md diff --git a/yazi/.config/yazi/flavors/gruvbox-material.yazi/flavor.toml b/yazi_1/.config/yazi/flavors/gruvbox-material.yazi/flavor.toml similarity index 100% rename from yazi/.config/yazi/flavors/gruvbox-material.yazi/flavor.toml rename to yazi_1/.config/yazi/flavors/gruvbox-material.yazi/flavor.toml diff --git a/yazi/.config/yazi/flavors/gruvbox-material.yazi/preview.png b/yazi_1/.config/yazi/flavors/gruvbox-material.yazi/preview.png similarity index 100% rename from yazi/.config/yazi/flavors/gruvbox-material.yazi/preview.png rename to yazi_1/.config/yazi/flavors/gruvbox-material.yazi/preview.png diff --git a/yazi/.config/yazi/flavors/gruvbox-material.yazi/tmtheme.xml b/yazi_1/.config/yazi/flavors/gruvbox-material.yazi/tmtheme.xml similarity index 100% rename from yazi/.config/yazi/flavors/gruvbox-material.yazi/tmtheme.xml rename to yazi_1/.config/yazi/flavors/gruvbox-material.yazi/tmtheme.xml diff --git a/yazi/.config/yazi/flavors/kanagawa.yazi/LICENSE b/yazi_1/.config/yazi/flavors/kanagawa.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/flavors/kanagawa.yazi/LICENSE rename to yazi_1/.config/yazi/flavors/kanagawa.yazi/LICENSE diff --git a/yazi/.config/yazi/flavors/kanagawa.yazi/LICENSE-tmtheme b/yazi_1/.config/yazi/flavors/kanagawa.yazi/LICENSE-tmtheme similarity index 100% rename from yazi/.config/yazi/flavors/kanagawa.yazi/LICENSE-tmtheme rename to yazi_1/.config/yazi/flavors/kanagawa.yazi/LICENSE-tmtheme diff --git a/yazi/.config/yazi/flavors/kanagawa.yazi/README.md b/yazi_1/.config/yazi/flavors/kanagawa.yazi/README.md similarity index 100% rename from yazi/.config/yazi/flavors/kanagawa.yazi/README.md rename to yazi_1/.config/yazi/flavors/kanagawa.yazi/README.md diff --git a/yazi/.config/yazi/flavors/kanagawa.yazi/flavor.toml b/yazi_1/.config/yazi/flavors/kanagawa.yazi/flavor.toml similarity index 100% rename from yazi/.config/yazi/flavors/kanagawa.yazi/flavor.toml rename to yazi_1/.config/yazi/flavors/kanagawa.yazi/flavor.toml diff --git a/yazi/.config/yazi/flavors/kanagawa.yazi/preview.png b/yazi_1/.config/yazi/flavors/kanagawa.yazi/preview.png similarity index 100% rename from yazi/.config/yazi/flavors/kanagawa.yazi/preview.png rename to yazi_1/.config/yazi/flavors/kanagawa.yazi/preview.png diff --git a/yazi/.config/yazi/flavors/kanagawa.yazi/tmtheme.xml b/yazi_1/.config/yazi/flavors/kanagawa.yazi/tmtheme.xml similarity index 100% rename from yazi/.config/yazi/flavors/kanagawa.yazi/tmtheme.xml rename to yazi_1/.config/yazi/flavors/kanagawa.yazi/tmtheme.xml diff --git a/yazi/.config/yazi/flavors/monokai.yazi/LICENSE b/yazi_1/.config/yazi/flavors/monokai.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/flavors/monokai.yazi/LICENSE rename to yazi_1/.config/yazi/flavors/monokai.yazi/LICENSE diff --git a/yazi/.config/yazi/flavors/monokai.yazi/LICENSE-tmtheme b/yazi_1/.config/yazi/flavors/monokai.yazi/LICENSE-tmtheme similarity index 100% rename from yazi/.config/yazi/flavors/monokai.yazi/LICENSE-tmtheme rename to yazi_1/.config/yazi/flavors/monokai.yazi/LICENSE-tmtheme diff --git a/yazi/.config/yazi/flavors/monokai.yazi/README.md b/yazi_1/.config/yazi/flavors/monokai.yazi/README.md similarity index 100% rename from yazi/.config/yazi/flavors/monokai.yazi/README.md rename to yazi_1/.config/yazi/flavors/monokai.yazi/README.md diff --git a/yazi/.config/yazi/flavors/monokai.yazi/flavor.toml b/yazi_1/.config/yazi/flavors/monokai.yazi/flavor.toml similarity index 100% rename from yazi/.config/yazi/flavors/monokai.yazi/flavor.toml rename to yazi_1/.config/yazi/flavors/monokai.yazi/flavor.toml diff --git a/yazi/.config/yazi/flavors/monokai.yazi/preview.png b/yazi_1/.config/yazi/flavors/monokai.yazi/preview.png similarity index 100% rename from yazi/.config/yazi/flavors/monokai.yazi/preview.png rename to yazi_1/.config/yazi/flavors/monokai.yazi/preview.png diff --git a/yazi/.config/yazi/flavors/monokai.yazi/tmtheme.xml b/yazi_1/.config/yazi/flavors/monokai.yazi/tmtheme.xml similarity index 100% rename from yazi/.config/yazi/flavors/monokai.yazi/tmtheme.xml rename to yazi_1/.config/yazi/flavors/monokai.yazi/tmtheme.xml diff --git a/yazi/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE b/yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE rename to yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE diff --git a/yazi/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE-tmtheme b/yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE-tmtheme similarity index 100% rename from yazi/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE-tmtheme rename to yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE-tmtheme diff --git a/yazi/.config/yazi/flavors/rose-pine-moon.yazi/README.md b/yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/README.md similarity index 100% rename from yazi/.config/yazi/flavors/rose-pine-moon.yazi/README.md rename to yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/README.md diff --git a/yazi/.config/yazi/flavors/rose-pine-moon.yazi/flavor.toml b/yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/flavor.toml similarity index 100% rename from yazi/.config/yazi/flavors/rose-pine-moon.yazi/flavor.toml rename to yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/flavor.toml diff --git a/yazi/.config/yazi/flavors/rose-pine-moon.yazi/preview.png b/yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/preview.png similarity index 100% rename from yazi/.config/yazi/flavors/rose-pine-moon.yazi/preview.png rename to yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/preview.png diff --git a/yazi/.config/yazi/flavors/rose-pine-moon.yazi/tmtheme.xml b/yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/tmtheme.xml similarity index 100% rename from yazi/.config/yazi/flavors/rose-pine-moon.yazi/tmtheme.xml rename to yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/tmtheme.xml diff --git a/yazi/.config/yazi/flavors/tokyo-night.yazi/LICENSE b/yazi_1/.config/yazi/flavors/tokyo-night.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/flavors/tokyo-night.yazi/LICENSE rename to yazi_1/.config/yazi/flavors/tokyo-night.yazi/LICENSE diff --git a/yazi/.config/yazi/flavors/tokyo-night.yazi/LICENSE-tmtheme b/yazi_1/.config/yazi/flavors/tokyo-night.yazi/LICENSE-tmtheme similarity index 100% rename from yazi/.config/yazi/flavors/tokyo-night.yazi/LICENSE-tmtheme rename to yazi_1/.config/yazi/flavors/tokyo-night.yazi/LICENSE-tmtheme diff --git a/yazi/.config/yazi/flavors/tokyo-night.yazi/README.md b/yazi_1/.config/yazi/flavors/tokyo-night.yazi/README.md similarity index 100% rename from yazi/.config/yazi/flavors/tokyo-night.yazi/README.md rename to yazi_1/.config/yazi/flavors/tokyo-night.yazi/README.md diff --git a/yazi/.config/yazi/flavors/tokyo-night.yazi/flavor.toml b/yazi_1/.config/yazi/flavors/tokyo-night.yazi/flavor.toml similarity index 100% rename from yazi/.config/yazi/flavors/tokyo-night.yazi/flavor.toml rename to yazi_1/.config/yazi/flavors/tokyo-night.yazi/flavor.toml diff --git a/yazi/.config/yazi/flavors/tokyo-night.yazi/preview.png b/yazi_1/.config/yazi/flavors/tokyo-night.yazi/preview.png similarity index 100% rename from yazi/.config/yazi/flavors/tokyo-night.yazi/preview.png rename to yazi_1/.config/yazi/flavors/tokyo-night.yazi/preview.png diff --git a/yazi/.config/yazi/flavors/tokyo-night.yazi/tmtheme.xml b/yazi_1/.config/yazi/flavors/tokyo-night.yazi/tmtheme.xml similarity index 100% rename from yazi/.config/yazi/flavors/tokyo-night.yazi/tmtheme.xml rename to yazi_1/.config/yazi/flavors/tokyo-night.yazi/tmtheme.xml diff --git a/yazi_1/.config/yazi/init.lua b/yazi_1/.config/yazi/init.lua new file mode 100644 index 0000000..24b6f61 --- /dev/null +++ b/yazi_1/.config/yazi/init.lua @@ -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 = "", -- Create a temporary bookmark from the menu + fuzzy_search = "", -- Launch fuzzy search (fzf) + history = "", -- Open directory history + previous_dir = "", -- 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 + -- }, + }, +}) diff --git a/yazi_1/.config/yazi/keymap.toml b/yazi_1/.config/yazi/keymap.toml new file mode 100644 index 0000000..0448b07 --- /dev/null +++ b/yazi_1/.config/yazi/keymap.toml @@ -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 = [ + "", + "i", + ], run = "plugin dupes interactive", desc = "Run dupes interactive" }, + { on = [ + "", + "o", + ], run = "plugin dupes override", desc = "Run dupes override" }, + { on = [ + "", + "d", + ], run = "plugin dupes dry", desc = "Run dupes dry" }, + { on = [ + "", + "a", + ], run = "plugin dupes apply", desc = "Run dupes apply" }, + { on = [ + "", + "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 = "", 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 = [ + "", + ], run = "escape", desc = "Exit visual mode, clear selected, or cancel search" }, + # { on = [ + # "q", + # "q", + # ], run = "quit", desc = "Exit the process" }, + { on = [ + "", + ], 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 = [ + "", + ], 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 = [ + "", + ], 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 = [ + "", + ], run = "close", desc = "Cancel selection" }, + { on = [ + "", + ], 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 = [ + "", + ], run = "close", desc = "Cancel input" }, + { on = [ + "", + ], run = "close --submit", desc = "Submit input" }, +] + +[completion] +prepend_keymap = [ + { on = [ + "", + ], 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 = [ + "", + ], 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" }, +] diff --git a/yazi/.config/yazi/omp/omp.json b/yazi_1/.config/yazi/omp/omp.json similarity index 100% rename from yazi/.config/yazi/omp/omp.json rename to yazi_1/.config/yazi/omp/omp.json diff --git a/yazi/.config/yazi/omp/omp.toml b/yazi_1/.config/yazi/omp/omp.toml similarity index 100% rename from yazi/.config/yazi/omp/omp.toml rename to yazi_1/.config/yazi/omp/omp.toml diff --git a/yazi/.config/yazi/omp/omp2.toml b/yazi_1/.config/yazi/omp/omp2.toml similarity index 100% rename from yazi/.config/yazi/omp/omp2.toml rename to yazi_1/.config/yazi/omp/omp2.toml diff --git a/yazi/.config/yazi/omp/omp_rose-pine.toml b/yazi_1/.config/yazi/omp/omp_rose-pine.toml similarity index 100% rename from yazi/.config/yazi/omp/omp_rose-pine.toml rename to yazi_1/.config/yazi/omp/omp_rose-pine.toml diff --git a/yazi_1/.config/yazi/package.toml b/yazi_1/.config/yazi/package.toml new file mode 100644 index 0000000..9026e65 --- /dev/null +++ b/yazi_1/.config/yazi/package.toml @@ -0,0 +1,174 @@ +[[plugin.deps]] +use = "yazi-rs/plugins:smart-enter" +rev = "4e55902" +hash = "56fdabc96fc1f4d53c96eb884b02a5be" + +[[plugin.deps]] +use = "yazi-rs/plugins:git" +rev = "4e55902" +hash = "36a484acf6a0a0219c543ccb4cee218f" + +[[plugin.deps]] +use = "yazi-rs/plugins:mount" +rev = "4e55902" +hash = "e2d4bfccf31e5ab05f46fee4797c5215" + +[[plugin.deps]] +use = "yazi-rs/plugins:mime-ext" +rev = "4e55902" +hash = "48a77ab785d1ee73bc2d7bbe2a4c2090" + +[[plugin.deps]] +use = "ndtoan96/ouch" +rev = "cfb9140" +hash = "b5067143415bd2d46c0dfa57319ddcef" + +[[plugin.deps]] +use = "yazi-rs/plugins:piper" +rev = "4e55902" +hash = "2dd68af59418b631b9703a03f606b31f" + +[[plugin.deps]] +use = "boydaihungst/mediainfo" +rev = "90a1652" +hash = "7a83bfab51d1d2ba6558cc56bd52539a" + +[[plugin.deps]] +use = "AnirudhG07/rich-preview" +rev = "573b275" +hash = "c3e2871c9ef244fd181f203791f9b0d2" + +[[plugin.deps]] +use = "stelcodes/bunny" +rev = "7137a44" +hash = "58bfa473bcac8574400e573787954512" + +[[plugin.deps]] +use = "Lil-Dank/lazygit" +rev = "0e56060" +hash = "ab2e66065a94cca4886b303518e44bd8" + +[[plugin.deps]] +use = "MasouShizuka/projects" +rev = "eed0657" +hash = "ca79de2b3bb2247906e01b2261e5d2bb" + +[[plugin.deps]] +use = "XYenon/yafg" +rev = "6034c67" +hash = "11543279fd83ad4cacdfb07d67016928" + +[[plugin.deps]] +use = "Shallow-Seek/fazif" +rev = "6e10488" +hash = "c549ff30fb1d1d97c734010fe82f6295" + +[[plugin.deps]] +use = "GianniBYoung/rsync" +rev = "14d2828" +hash = "7394430ba473dce1023721622438bf3d" + +[[plugin.deps]] +use = "llanosrocas/yaziline" +rev = "6266926" +hash = "9917ab5cb9bdbab7ca7f2501f84f0f11" + +[[plugin.deps]] +use = "boydaihungst/file-extra-metadata" +rev = "4fbb106" +hash = "8765a20c837f4be5718f89ab1cf33b4" + +[[plugin.deps]] +use = "yazi-rs/plugins:full-border" +rev = "68f7d48" +hash = "3996fc74044bc44144b323686f887e1" + +[[plugin.deps]] +use = "pirafrank/what-size" +rev = "179ebf6" +hash = "57056b9728006881d580ccabe8154a9c" + +[[plugin.deps]] +use = "yazi-rs/plugins:toggle-pane" +rev = "57f1863" +hash = "8a7c58225816a163a6e8730c0adafbc8" + +[[plugin.deps]] +use = "AminurAlam/yazi-plugins:fchar" +rev = "658a099" +hash = "b484ff674a30268991b67f819f4625da" + +[[plugin.deps]] +use = "mshnwq/dupes" +rev = "18a9395" +hash = "730ddf5e1179f34ccb81f8d5ed564823" + +[[plugin.deps]] +use = "uhs-robert/recycle-bin" +rev = "69d7d4c" +hash = "38dac2fc8f4b50bbdaa46e699720a54a" + +[[plugin.deps]] +use = "Rolv-Apneseth/bypass" +rev = "c1e5fcf" +hash = "208e77480824f62558a5b037c8569d61" + +[[plugin.deps]] +use = "Ape/open-with-cmd" +rev = "e3d430f" +hash = "d29c1ffa99099181faf8c01d1f59aa4c" + +[[plugin.deps]] +use = "yazi-rs/plugins:smart-paste" +rev = "56971d0" +hash = "75fd60b2df7115e0a6ca31a6a804d320" + +[[plugin.deps]] +use = "larry-oates/open-git-remote" +rev = "95b3043" +hash = "8d17dcc063217b558a08ed8bb5e9e8ad" + +[[plugin.deps]] +use = "yazi-rs/plugins:vcs-files" +rev = "56971d0" +hash = "5cfd9b2bd3f8ec4e3245e3426a967f82" + +[[flavor.deps]] +use = "yazi-rs/flavors:catppuccin-mocha" +rev = "4a1802a" +hash = "b951d27afe197d154f6da9683b46c5f8" + +[[flavor.deps]] +use = "Mintass/rose-pine-moon" +rev = "94385fe" +hash = "edbe0dfb5db8ff37281dba62adc7e750" + +[[flavor.deps]] +use = "Chromium-3-Oxide/everforest-medium" +rev = "0158f0f" +hash = "a7c36abf0b13289aceb1c20a17c6a63e" + +[[flavor.deps]] +use = "bennyyip/gruvbox-dark" +rev = "619fdc5" +hash = "d4c7003ec6dc4efd5d3a050d5c2f2575" + +[[flavor.deps]] +use = "BennyOe/tokyo-night" +rev = "8e6296f" +hash = "d9da921c3bd37ba32ee27a8a7a40461f" + +[[flavor.deps]] +use = "dangooddd/kanagawa" +rev = "04985d1" +hash = "c69a63981e254a3a06a11c2bf0e6af1d" + +[[flavor.deps]] +use = "matt-dong-123/gruvbox-material" +rev = "6c36491" +hash = "564f2335540ee2c440c3b671bc28ac60" + +[[flavor.deps]] +use = "malick-tammal/monokai" +rev = "e59f6cb" +hash = "dcb449d46e30dc020e4b7b59202d2448" diff --git a/yazi/.config/yazi/plugins/bunny.yazi/LICENSE b/yazi_1/.config/yazi/plugins/bunny.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/bunny.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/bunny.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/bunny.yazi/README.md b/yazi_1/.config/yazi/plugins/bunny.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/bunny.yazi/README.md rename to yazi_1/.config/yazi/plugins/bunny.yazi/README.md diff --git a/yazi/.config/yazi/plugins/bunny.yazi/main.lua b/yazi_1/.config/yazi/plugins/bunny.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/bunny.yazi/main.lua rename to yazi_1/.config/yazi/plugins/bunny.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/bypass.yazi/LICENSE b/yazi_1/.config/yazi/plugins/bypass.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/bypass.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/bypass.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/bypass.yazi/README.md b/yazi_1/.config/yazi/plugins/bypass.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/bypass.yazi/README.md rename to yazi_1/.config/yazi/plugins/bypass.yazi/README.md diff --git a/yazi/.config/yazi/plugins/bypass.yazi/main.lua b/yazi_1/.config/yazi/plugins/bypass.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/bypass.yazi/main.lua rename to yazi_1/.config/yazi/plugins/bypass.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/dupes.yazi/LICENSE b/yazi_1/.config/yazi/plugins/dupes.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/dupes.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/dupes.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/dupes.yazi/README.md b/yazi_1/.config/yazi/plugins/dupes.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/dupes.yazi/README.md rename to yazi_1/.config/yazi/plugins/dupes.yazi/README.md diff --git a/yazi/.config/yazi/plugins/dupes.yazi/main.lua b/yazi_1/.config/yazi/plugins/dupes.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/dupes.yazi/main.lua rename to yazi_1/.config/yazi/plugins/dupes.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/fazif.yazi/LICENSE b/yazi_1/.config/yazi/plugins/fazif.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/fazif.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/fazif.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/fazif.yazi/README.md b/yazi_1/.config/yazi/plugins/fazif.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/fazif.yazi/README.md rename to yazi_1/.config/yazi/plugins/fazif.yazi/README.md diff --git a/yazi/.config/yazi/plugins/fazif.yazi/faziffd b/yazi_1/.config/yazi/plugins/fazif.yazi/faziffd similarity index 100% rename from yazi/.config/yazi/plugins/fazif.yazi/faziffd rename to yazi_1/.config/yazi/plugins/fazif.yazi/faziffd diff --git a/yazi/.config/yazi/plugins/fazif.yazi/faziffdr b/yazi_1/.config/yazi/plugins/fazif.yazi/faziffdr similarity index 100% rename from yazi/.config/yazi/plugins/fazif.yazi/faziffdr rename to yazi_1/.config/yazi/plugins/fazif.yazi/faziffdr diff --git a/yazi/.config/yazi/plugins/fazif.yazi/fazifrg b/yazi_1/.config/yazi/plugins/fazif.yazi/fazifrg similarity index 100% rename from yazi/.config/yazi/plugins/fazif.yazi/fazifrg rename to yazi_1/.config/yazi/plugins/fazif.yazi/fazifrg diff --git a/yazi/.config/yazi/plugins/fazif.yazi/fazifrga b/yazi_1/.config/yazi/plugins/fazif.yazi/fazifrga similarity index 100% rename from yazi/.config/yazi/plugins/fazif.yazi/fazifrga rename to yazi_1/.config/yazi/plugins/fazif.yazi/fazifrga diff --git a/yazi/.config/yazi/plugins/fazif.yazi/main.lua b/yazi_1/.config/yazi/plugins/fazif.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/fazif.yazi/main.lua rename to yazi_1/.config/yazi/plugins/fazif.yazi/main.lua diff --git a/yazi_1/.config/yazi/plugins/fchar.yazi/LICENSE b/yazi_1/.config/yazi/plugins/fchar.yazi/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/fchar.yazi/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/yazi_1/.config/yazi/plugins/fchar.yazi/README.md b/yazi_1/.config/yazi/plugins/fchar.yazi/README.md new file mode 100644 index 0000000..b687a92 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/fchar.yazi/README.md @@ -0,0 +1,97 @@ +list of files with highlighted matching characters + +# Features + +- case insensitive matching +- supports aliases for different languages +- ignores leading non-alphabet +- can match characters anywhere in filename + +# Installation + +```sh +ya pkg add AminurAlam/yazi-plugins:fchar +``` + +# Usage + +in `~/.config/yazi/keymap.toml` + +```toml +[mgr] +prepend_keymap = [ + { on = "f", run = "plugin fchar", desc = "Jump to char" }, +] +``` + +in `~/.config/yazi/init.lua` + +```lua +-- 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 = 'ß' + }, +} + +-- aliases for Japanese +require('fchar'):setup { + aliases = { + a = 'あア', + b = 'ばびぶべぼバビブベボ', + c = 'ちチ', + d = 'だぢづでどダヂヅデド', + e = 'えエ', + g = 'がぎぐげごガギグゲゴ', + h = 'はひふへほハヒフヘホ', + i = 'いイ', + j = 'じジ', + k = 'かきくけこカキクケコ', + m = 'まみむめもマミムメモ', + n = 'なにぬねのんナニヌネノン', + o = 'おオ', + p = 'ぱぴぷぺぽパピプペポ', + r = 'らりるれろラリルレロ', + s = 'さしすせそサシスセソ', + t = 'たつてとタツテト', + u = 'うウ', + w = 'わをワヲ', + y = 'やゆよヤユヨ', + z = 'ざずぜぞザズゼゾ', + }, +} + +-- 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 +``` diff --git a/yazi_1/.config/yazi/plugins/fchar.yazi/main.lua b/yazi_1/.config/yazi/plugins/fchar.yazi/main.lua new file mode 100644 index 0000000..d7c6cea --- /dev/null +++ b/yazi_1/.config/yazi/plugins/fchar.yazi/main.lua @@ -0,0 +1,159 @@ +--- @since 25.5.31 + +local M = {} + +local changed = ya.sync(function(st, new) + local b = st.last ~= new + st.last = new + return b or not cx.active.finder +end) + +---@type fun(opts: FCharConf): nil +local set_config = ya.sync(function(st, opts) + st.opts = opts +end) + +---@type fun(): FCharConf +local get_config = ya.sync(function(st) + return st.opts + or { + -- 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, twofile, elf + search_location = 'start', + aliases = {}, + } +end) + +local function tbl_deep_extend(default, config) + if type(config) ~= 'table' then + return config + end + + default = (type(default) == 'table') and default or {} + for key, _ in pairs(config) do + default[key] = tbl_deep_extend(default[key], config[key]) + end + + return default +end + +---@type fun(self, config: FCharConf): nil +function M:setup(config) + set_config(tbl_deep_extend(get_config(), config)) +end + +-- TODO: process `--flags` +function M:entry() + local cands = { + { on = '0' }, + { on = '1' }, + { on = '2' }, + { on = '3' }, + { on = '4' }, + { on = '5' }, + { on = '6' }, + { on = '7' }, + { on = '8' }, + { on = '9' }, + { on = '_' }, + { on = 'a' }, + { on = 'b' }, + { on = 'c' }, + { on = 'd' }, + { on = 'e' }, + { on = 'f' }, + { on = 'g' }, + { on = 'h' }, + { on = 'i' }, + { on = 'j' }, + { on = 'k' }, + { on = 'l' }, + { on = 'm' }, + { on = 'n' }, + { on = 'o' }, + { on = 'p' }, + { on = 'q' }, + { on = 'r' }, + { on = 's' }, + { on = 't' }, + { on = 'u' }, + { on = 'v' }, + { on = 'w' }, + { on = 'x' }, + { on = 'y' }, + { on = 'z' }, + { on = 'A' }, + { on = 'B' }, + { on = 'C' }, + { on = 'D' }, + { on = 'E' }, + { on = 'F' }, + { on = 'G' }, + { on = 'H' }, + { on = 'I' }, + { on = 'J' }, + { on = 'K' }, + { on = 'L' }, + { on = 'M' }, + { on = 'N' }, + { on = 'O' }, + { on = 'P' }, + { on = 'Q' }, + { on = 'R' }, + { on = 'S' }, + { on = 'T' }, + { on = 'U' }, + { on = 'V' }, + { on = 'W' }, + { on = 'X' }, + { on = 'Y' }, + { on = 'Z' }, + } + local opts = get_config() + + if opts.skip_symbols then + local additional_keys = '~.!@#-' + for i = 1, #additional_keys do + cands[#cands + 1] = { on = additional_keys:sub(i, i) } + end + end + + local idx = ya.which { cands = cands, silent = true } + if not idx then + return + end + + local loc = { + start = '^', + word = '(^|\\W)', + all = '', + } + + -- TODO: autodetect common prefix + local prefixes = '' + for _, prefix in ipairs(opts.skip_prefix) do + prefixes = prefixes .. '(' .. prefix .. ')?' + end + + local re = loc[opts.search_location] + .. ((opts.search_location == 'start' and opts.skip_symbols) and [[\W?]] or '') + .. prefixes + .. '[' + .. cands[idx].on + .. (opts.aliases[cands[idx].on] or '') + .. ']' + if changed(re) then + ya.emit('find_do', { re, insensitive = opts.insensitive }) + else + ya.emit('find_arrow', {}) + end +end + +return M diff --git a/yazi/.config/yazi/plugins/file-extra-metadata.yazi/LICENSE b/yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/file-extra-metadata.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/file-extra-metadata.yazi/README.md b/yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/file-extra-metadata.yazi/README.md rename to yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/README.md diff --git a/yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/main.lua b/yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/main.lua new file mode 100644 index 0000000..9d11fde --- /dev/null +++ b/yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/main.lua @@ -0,0 +1,367 @@ +--- @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.spec 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 diff --git a/yazi/.config/yazi/plugins/git.yazi/LICENSE b/yazi_1/.config/yazi/plugins/full-border.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/git.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/full-border.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/full-border.yazi/README.md b/yazi_1/.config/yazi/plugins/full-border.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/full-border.yazi/README.md rename to yazi_1/.config/yazi/plugins/full-border.yazi/README.md diff --git a/yazi/.config/yazi/plugins/full-border.yazi/main.lua b/yazi_1/.config/yazi/plugins/full-border.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/full-border.yazi/main.lua rename to yazi_1/.config/yazi/plugins/full-border.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/mount.yazi/LICENSE b/yazi_1/.config/yazi/plugins/git.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/mount.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/git.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/git.yazi/README.md b/yazi_1/.config/yazi/plugins/git.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/git.yazi/README.md rename to yazi_1/.config/yazi/plugins/git.yazi/README.md diff --git a/yazi/.config/yazi/plugins/git.yazi/main.lua b/yazi_1/.config/yazi/plugins/git.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/git.yazi/main.lua rename to yazi_1/.config/yazi/plugins/git.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/git.yazi/types.lua b/yazi_1/.config/yazi/plugins/git.yazi/types.lua similarity index 100% rename from yazi/.config/yazi/plugins/git.yazi/types.lua rename to yazi_1/.config/yazi/plugins/git.yazi/types.lua diff --git a/yazi/.config/yazi/plugins/lazygit.yazi/LICENSE b/yazi_1/.config/yazi/plugins/lazygit.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/lazygit.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/lazygit.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/lazygit.yazi/README.md b/yazi_1/.config/yazi/plugins/lazygit.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/lazygit.yazi/README.md rename to yazi_1/.config/yazi/plugins/lazygit.yazi/README.md diff --git a/yazi/.config/yazi/plugins/lazygit.yazi/main.lua b/yazi_1/.config/yazi/plugins/lazygit.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/lazygit.yazi/main.lua rename to yazi_1/.config/yazi/plugins/lazygit.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/open-with-cmd.yazi/LICENSE b/yazi_1/.config/yazi/plugins/mediainfo.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/open-with-cmd.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/mediainfo.yazi/LICENSE diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/README.md b/yazi_1/.config/yazi/plugins/mediainfo.yazi/README.md new file mode 100644 index 0000000..7ad6dc9 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/README.md @@ -0,0 +1,135 @@ +# mediainfo.yazi (fork) + + + +- [mediainfo.yazi (fork)](#mediainfoyazi-fork) + - [Preview](#preview) + - [Installation](#installation) + - [Configuration:](#configuration) + - [Custom theme](#custom-theme) + + +This is a Yazi plugin for previewing media files. The preview shows thumbnail +using `ffmpeg` if available and media metadata using `mediainfo`. + +> [!IMPORTANT] +> Minimum version: yazi v25.5.31. + +## Preview + +- Video + + ![video](assets/2025-02-15-09-15-39.png) + +- Audio file with cover + + ![audio_with_cover_picture](assets/2025-02-15-09-14-23.png) + +- Images + + ![image](assets/2025-02-15-16-52-39.png) + +- Subtitle + + ![subrip](assets/2025-02-15-16-51-11.png) + +- SVG+XML file doesn't have useful information, so it only show the image preview. +- There are more file extensions which are supported by mediainfo. Just add file's MIME type to `prepend_previewers`, `prepend_preloaders`. + Use `spotter` to determine File's MIME type. [Default is `` key](https://github.com/sxyazi/yazi/blob/1a6abae974370702c8865459344bf256de58359e/yazi-config/preset/keymap-default.toml#L59) + +## Installation + +- Install mediainfo CLI: + - [https://mediaarea.net/en/MediaInfo/Download](https://mediaarea.net/en/MediaInfo/Download) + - Run this command in terminal to check if it's installed correctly: + + ```bash + mediainfo --version + ``` + + If it output `Not found` then add it to your PATH environment variable. It's better to ask ChatGPT to help you (Prompt: `Add MediaInfo CLI to PATH environment variable in Windows`). + +- Install ImageMagick (for linux, you can use your distro package manager to install): + https://imagemagick.org/script/download.php +- Install this plugin: + + ```bash + ya pkg add boydaihungst/mediainfo + ``` + +## Configuration: + +> [!IMPORTANT] +> +> `mediainfo` use built-in video, image, svg, magick plugins behind the scene to render preview image, song cover. +> So you can remove those 4 plugins from `prepend_preloaders` and `prepend_previewers` sections in `yazi.toml`. + +If you have cache problem, run this cmd, and follow the tips: `yazi --clear-cache` + +Config folder for each OS: https://yazi-rs.github.io/docs/configuration/overview. + +Create `.../yazi/yazi.toml` and add: + +> [!IMPORTANT] +> +> For yazi (>=v25.12.29) replace `name` with `url` + +```toml +[plugin] + prepend_preloaders = [ + # Replace magick, image, video with mediainfo + { mime = "{audio,video,image}/*", run = "mediainfo" }, + { mime = "application/subrip", run = "mediainfo" }, + # Adobe Illustrator, Adobe Photoshop is image/adobe.photoshop, already handled above + { mime = "application/postscript", run = "mediainfo" }, + ] + prepend_previewers = [ + # Replace magick, image, video with mediainfo + { mime = "{audio,video,image}/*", run = "mediainfo"}, + { mime = "application/subrip", run = "mediainfo" }, + # Adobe Illustrator, Adobe Photoshop is image/adobe.photoshop, already handled above + { mime = "application/postscript", run = "mediainfo" }, + ] + # There are more extensions which are supported by mediainfo. + # Just add file's MIME type to `previewers`, `preloaders` above. + # https://mediaarea.net/en/MediaInfo/Support/Formats + +# For a large file like Adobe Illustrator, Adobe Photoshop, etc +# you may need to increase the memory limit if no image is rendered. +# https://yazi-rs.github.io/docs/configuration/yazi#tasks +[tasks] + image_alloc = 1073741824 # = 1024*1024*1024 = 1024MB + +``` + +## Custom theme + +Using the same style with spotter. [Read more](https://github.com/sxyazi/yazi/pull/2391) + +Edit or add `yazi/theme.toml`: + +```toml +[spot] +# Section header style. +# Example: Video, Text, Image,... with green color in preview images above +title = { fg = "green" } + +# Value style. +# Example: `Format: FLAC` with blue color in preview images above +tbl_col = { fg = "blue" } +``` + +## (Optional) Keymaps to hide metadata and to preview images in full screen + +> [!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). + +Since Yazi prioritizes the first matching key, `prepend_keymap` takes precedence over defaults. +Or you can use `keymap` to replace all other keys + +```toml +[mgr] + prepend_keymap = [ + { on = "", run = "plugin mediainfo -- toggle-metadata", desc = "Toggle media preview metadata" }, + ] +``` diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/adobe-old.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/adobe-old.lua new file mode 100644 index 0000000..f46ee03 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/adobe-old.lua @@ -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 diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/adobe-stable.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/adobe-stable.lua new file mode 100644 index 0000000..30a23d6 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/adobe-stable.lua @@ -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 diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-09-14-23.png b/yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-09-14-23.png new file mode 100644 index 0000000..2b758d2 Binary files /dev/null and b/yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-09-14-23.png differ diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-09-15-39.png b/yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-09-15-39.png new file mode 100644 index 0000000..b24ade5 Binary files /dev/null and b/yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-09-15-39.png differ diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-16-51-11.png b/yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-16-51-11.png new file mode 100644 index 0000000..bb3279a Binary files /dev/null and b/yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-16-51-11.png differ diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-16-52-39.png b/yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-16-52-39.png new file mode 100644 index 0000000..9e23c41 Binary files /dev/null and b/yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-16-52-39.png differ diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/audio-old.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/audio-old.lua new file mode 100644 index 0000000..8352c59 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/audio-old.lua @@ -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 diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/audio-stable.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/audio-stable.lua new file mode 100644 index 0000000..a6bf677 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/audio-stable.lua @@ -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 diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/const.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/const.lua new file mode 100644 index 0000000..cef5c01 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/const.lua @@ -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 diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/image-old.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/image-old.lua new file mode 100644 index 0000000..bd644d1 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/image-old.lua @@ -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 diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/image-stable.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/image-stable.lua new file mode 100644 index 0000000..13f9227 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/image-stable.lua @@ -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 diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/main.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/main.lua new file mode 100644 index 0000000..6082d51 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/main.lua @@ -0,0 +1,498 @@ +--- @since 25.5.31 + +local skip_labels = { + ["Complete name"] = true, + ["CompleteName_Last"] = true, + ["Unique ID"] = true, + ["File size"] = true, + ["Format/Info"] = true, + ["Codec ID/Info"] = true, + ["MD5 of the unencoded content"] = true, +} + +local ENTRY_ACTION = { + toggle_metadata = "toggle-metadata", +} + +local STATE_KEY = { + units = "units", + hide_metadata = "hide_metadata", + prev_metadata_area = "prev_metadata_area", + prev_peek_data = "prev_peek_data", +} + +local magick_image_mimes = { + avif = true, + hei = true, + heic = true, + heif = true, + ["heif-sequence"] = true, + ["heic-sequence"] = true, + jxl = true, + xml = true, + ["svg+xml"] = true, + ["canon-cr2"] = true, +} + +local seekable_mimes = { + ["application/postscript"] = true, + ["image/adobe.photoshop"] = true, +} + +local M = {} +local suffix = "_mediainfo" +local SHELL = os.getenv("SHELL") or "" + +local function is_valid_utf8(str) + return utf8.len(str) ~= nil +end + +local function path_quote(path) + if not path or tostring(path) == "" then + return path + end + local result = "'" .. string.gsub(tostring(path), "'", "'\\''") .. "'" + return result +end + +local function read_mediainfo_cached_file(file_path) + -- Open the file in read mode + local file = io.open(file_path, "r") + + if file then + -- Read the entire file content + local content = file:read("*all") + file:close() + return content + end +end + +local set_state = ya.sync(function(state, key, value) + state[key] = value +end) + +local get_state = ya.sync(function(state, key) + return state[key] +end) + +local force_render = ya.sync(function(_, _) + (ui.render or ya.render)() +end) + +local function image_layer_count(job) + local cache = ya.file_cache({ file = job.file, skip = 0 }) + if not cache then + return 0 + end + local layer_count = get_state("f" .. tostring(cache)) + if layer_count then + return layer_count + end + local output, err = Command("identify") + :arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) }) + :output() + if err then + return 0 + end + layer_count = 0 + for line in output.stdout:gmatch("[^\r\n]+") do + if line:match("%S") then + layer_count = layer_count + 1 + end + end + set_state("f" .. tostring(cache), layer_count) + return layer_count +end + +function M:peek(job) + local start = os.clock() + local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 }) + if not job.mime then + return + end + set_state(STATE_KEY.prev_peek_data, { + file = tostring(job.file.path or job.file.cache or job.file.url), + mime = job.mime, + area = { + x = job.area.x, + y = job.area.y, + w = job.area.w, + h = job.area.h, + }, + args = job.args, + skip = job.skip, + }) + local is_video = string.find(job.mime, "^video/") + local is_audio = string.find(job.mime, "^audio/") + local is_image = string.find(job.mime, "^image/") + local is_seekable = seekable_mimes[job.mime] or is_video + local cache_img_url = (is_audio or is_image) and cache_img_url_no_skip + + if is_seekable then + cache_img_url = ya.file_cache(job) + end + local preload_status, preload_err = self:preload(job) + if not preload_status then + return + end + ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock())) + local hide_metadata = get_state(STATE_KEY.hide_metadata) + local mediainfo_height = 0 + local lines = {} + local limit = job.area.h + local last_line = 0 + local is_wrap = rt.preview.wrap == "yes" + if not hide_metadata then + local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. suffix + local output = read_mediainfo_cached_file(cache_mediainfo_path) + if output then + local max_width = math.max(1, job.area.w) + if output:match("^Error:") then + job.args.force_reload_mediainfo = true + preload_status, preload_err = self:preload(job) + if not preload_status or preload_err then + return + end + output = read_mediainfo_cached_file(cache_mediainfo_path) + end + + for str in output:gsub("\n+$", ""):gmatch("[^\n]*") do + local label, value = str:match("(.*[^ ]) +: (.*)") + local line + if label then + if not skip_labels[label] then + line = ui.Line({ + ui.Span(label .. ": "):style(ui.Style():fg("reset"):bold()), + ui.Span(value):style(th.spot.tbl_col or ui.Style():fg("blue")), + }) + end + elseif str ~= "General" then + line = ui.Line({ ui.Span(str):style(th.spot.title or ui.Style():fg("green")) }) + end + + if line then + local line_height = math.max(1, is_wrap and math.ceil(ui.width(line) / max_width) or 1) + if (last_line + line_height) > job.skip then + table.insert(lines, line) + end + if (last_line + line_height) >= job.skip + limit then + last_line = job.skip + limit + break + end + last_line = last_line + line_height + end + end + end + mediainfo_height = math.min(limit, last_line) + end + + if + (job.skip > 0 and #lines == 0 and not hide_metadata) + and ( + not is_seekable + or (is_video and job.skip >= 90) + or ( + (job.mime == "image/adobe.photoshop" or job.mime == "application/postscript") + and image_layer_count(job) + < (1 + math.floor( + math.max(0, get_state(STATE_KEY.units) and (job.skip / get_state(STATE_KEY.units)) or 0) + )) + ) + ) + then + ya.emit("peek", { + math.max(0, job.skip - (get_state(STATE_KEY.units) or limit)), + only_if = job.file.url, + upper_bound = true, + }) + return + end + force_render() + -- NOTE: Hacky way to prevent image overlap with old metadata area + if hide_metadata and get_state(STATE_KEY.prev_metadata_area) then + ya.preview_widget(job, { + ui.Clear(ui.Rect(get_state(STATE_KEY.prev_metadata_area))), + }) + end + local rendered_img_rect = cache_img_url + and fs.cha(cache_img_url) + and ya.image_show( + cache_img_url, + ui.Rect({ + x = job.area.x, + y = job.area.y, + w = job.area.w, + h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h, + }) + ) + or nil + local image_height = rendered_img_rect and rendered_img_rect.h or 0 + + -- NOTE: Workaround case audio has no cover image. Prevent regenerate preview image + if is_audio and image_height == 1 then + local info = ya.image_info(cache_img_url) + if not info or (info.w == 1 and info.h == 1) then + image_height = 0 + end + end + + -- NOTE: Workaround case video.lua doesn't doesn't generate preview image because of `skip` overflow video duration + if is_video and not rendered_img_rect then + image_height = math.max(job.area.h - mediainfo_height, 0) + end + + -- Handle image preload error + if preload_err then + table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red"))) + end + + ya.preview_widget(job, { + ui.Text(lines) + :area(ui.Rect({ + x = job.area.x, + y = job.area.y + image_height, + w = job.area.w, + h = job.area.h - image_height, + })) + :wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO), + }) + -- NOTE: Hacky way to prevent image overlap with old metadata area + if not hide_metadata then + set_state(STATE_KEY.prev_metadata_area, { + x = job.area.x, + y = job.area.y + image_height, + w = job.area.w, + h = job.area.h - image_height, + }) + end +end + +function M:seek(job) + local h = cx.active.current.hovered + if h and h.url == job.file.url then + set_state(STATE_KEY.units, job.units) + ya.emit("peek", { + math.max(0, cx.active.preview.skip + job.units), + only_if = job.file.url, + }) + end +end + +function M:re_peek() + local prev_peek_data = get_state(STATE_KEY.prev_peek_data) + if prev_peek_data then + prev_peek_data.file = File({ + url = Url(prev_peek_data.file), + cha = fs.cha(Url(prev_peek_data.file)), + }) + prev_peek_data.area = ui.area and ui.area("preview") or ui.Rect(prev_peek_data.area) + + self:peek(prev_peek_data) + end +end + +function M:preload(job) + local cache_img_url = ya.file_cache({ file = job.file, skip = 0 }) + if not cache_img_url then + return true + end + local cache_mediainfo_url = Url(tostring(cache_img_url) .. suffix) + cache_img_url = seekable_mimes[job.mime] and ya.file_cache(job) or cache_img_url + local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url) + local err_msg = "" + local is_valid_utf8_path = is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url)) + -- video mimetype + if job.mime then + if string.find(job.mime, "^video/") then + local cache_img_status, video_preload_err = require("video"):preload(job) + if not cache_img_status and video_preload_err then + err_msg = err_msg + .. string.format("Failed to start `%s`, Do you have `%s` installed?\n", "ffmpeg", "ffmpeg") + end + -- audo and image mimetype + elseif cache_img_url and (not cache_img_url_cha or cache_img_url_cha.len <= 0) then + -- audio + if string.find(job.mime, "^audio/") then + local qv = 31 - math.floor(rt.preview.image_quality * 0.3) + local audio_preload_output, audio_preload_err = Command("ffmpeg"):arg({ + "-v", + "error", + "-threads", + 1, + "-skip_frame", + "nokey", + "-an", + "-sn", + "-dn", + "-i", + tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url), + "-vframes", + 1, + "-q:v", + qv, + "-vf", + string.format("scale=-1:'min(%d,ih)':flags=fast_bilinear", rt.preview.max_height / 2), + "-f", + "image2", + "-y", + tostring(cache_img_url), + }):output() + -- NOTE: Some audio types doesn't have cover image -> error "" + if + (audio_preload_output and audio_preload_output.stderr ~= nil and audio_preload_output.stderr ~= "") + or audio_preload_err + then + err_msg = err_msg + .. string.format("Failed to start `%s`, Do you have `%s` installed?\n", "ffmpeg", "ffmpeg") + else + cache_img_url_cha, _ = fs.cha(cache_img_url) + if not cache_img_url_cha then + -- NOTE: Workaround case audio has no cover image. Prevent regenerate preview image + audio_preload_output, audio_preload_err = require("magick") + .with_limit() + :arg({ + "-size", + "1x1", + "canvas:none", + string.format("PNG32:%s", cache_img_url), + }) + :output() + if + (audio_preload_output.stderr ~= nil and audio_preload_output.stderr ~= "") + or audio_preload_err + then + err_msg = err_msg + .. string.format( + "Failed to start `%s`, Do you have `%s` installed?\n", + "magick", + "magick" + ) + end + end + end + -- image + elseif string.find(job.mime, "^image/") or job.mime == "application/postscript" then + local svg_plugin_ok, svg_plugin = pcall(require, "svg") + local _, magick_plugin = pcall(require, "magick") + local mime = job.mime:match(".*/(.*)$") + + local image_plugin = magick_image_mimes[mime] + and ((mime == "svg+xml" and svg_plugin_ok) and svg_plugin or magick_plugin) + or require("image") + + local cache_img_status, image_preload_err + -- psd, ai, eps + if mime == "adobe.photoshop" or job.mime == "application/postscript" then + local layer_index = 0 + local units = get_state(STATE_KEY.units) + if units ~= nil then + local max_layer = image_layer_count(job) + layer_index = math.floor(math.max(0, job.skip / units)) + if layer_index + 1 > max_layer then + layer_index = max_layer - 1 + end + end + local cache_img_url_tmp = Url(cache_img_url .. ".tmp") + if fs.cha(cache_img_url_tmp) then + fs.remove("file", cache_img_url_tmp) + end + local tmp_file_path, _ = fs.unique_name(cache_img_url_tmp) + cache_img_status, image_preload_err = magick_plugin + .with_limit() + :arg({ + "-background", + "none", + tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) + .. "[" + .. tostring(layer_index) + .. "]", + "-auto-orient", + "-strip", + "-resize", + string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height), + "-quality", + rt.preview.image_quality, + string.format("PNG32:%s", tostring(tmp_file_path)), + }) + :status() + if cache_img_status then + os.rename(tostring(tmp_file_path), tostring(cache_img_url)) + end + elseif mime == "svg+xml" and not is_valid_utf8_path then + local cache_img_url_tmp = Url(cache_img_url .. ".tmp") + if fs.cha(cache_img_url_tmp) then + fs.remove("file", cache_img_url_tmp) + end + local tmp_file_path, _ = fs.unique_name(cache_img_url_tmp) + -- svg under invalid utf8 path + cache_img_status, image_preload_err = magick_plugin + .with_limit() + :arg({ + "-background", + "none", + tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url), + "-auto-orient", + "-strip", + "-flatten", + "-resize", + string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height), + "-quality", + rt.preview.image_quality, + string.format("PNG32:%s", tostring(tmp_file_path)), + }) + :status() + if cache_img_status then + os.rename(tostring(tmp_file_path), tostring(cache_img_url)) + end + else + -- other image + local no_skip_job = { skip = 0, file = job.file, args = {} } + cache_img_status, image_preload_err = image_plugin:preload(no_skip_job) + end + if not cache_img_status then + err_msg = err_msg .. (image_preload_err and (tostring(image_preload_err)) or "") + end + end + end + end + local cache_mediainfo_cha = fs.cha(cache_mediainfo_url) + if cache_mediainfo_cha and not job.args.force_reload_mediainfo then + return true, err_msg ~= "" and ("Error: " .. err_msg) or nil + end + local cmd = "mediainfo" + local output, err + if is_valid_utf8_path then + output, err = Command(cmd) + :arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) }) + :output() + else + cmd = "cd " + .. path_quote(job.file.path or job.file.cache or (job.file.url.path or job.file.url).parent) + .. " && " + .. cmd + .. " " + .. path_quote(tostring(job.file.path or job.file.cache or job.file.url.name)) + output, err = Command(SHELL) + :arg({ "-c", cmd }) + :arg({ tostring(job.file.path or job.file.cache or (job.file.url.path or job.file.url)) }) + :output() + end + if err then + err_msg = err_msg .. string.format("Failed to start `%s`, 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 + +function M:entry(job) + local action = job.args[1] + + if action == ENTRY_ACTION.toggle_metadata then + set_state(STATE_KEY.hide_metadata, not get_state(STATE_KEY.hide_metadata)) + M:re_peek() + end +end + +return M diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/none-media-preview-old.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/none-media-preview-old.lua new file mode 100644 index 0000000..b88a9ec --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/none-media-preview-old.lua @@ -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 diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/none-media-preview-stable.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/none-media-preview-stable.lua new file mode 100644 index 0000000..11e09ca --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/none-media-preview-stable.lua @@ -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 diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/utils.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/utils.lua new file mode 100644 index 0000000..ea969ca --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/utils.lua @@ -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 diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/video-old.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/video-old.lua new file mode 100644 index 0000000..6f02de9 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/video-old.lua @@ -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 diff --git a/yazi_1/.config/yazi/plugins/mediainfo.yazi/video-stable.lua b/yazi_1/.config/yazi/plugins/mediainfo.yazi/video-stable.lua new file mode 100644 index 0000000..4e174eb --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mediainfo.yazi/video-stable.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/piper.yazi/LICENSE b/yazi_1/.config/yazi/plugins/mime-ext.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/piper.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/mime-ext.yazi/LICENSE diff --git a/yazi_1/.config/yazi/plugins/mime-ext.yazi/README.md b/yazi_1/.config/yazi/plugins/mime-ext.yazi/README.md new file mode 100644 index 0000000..65c6d20 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mime-ext.yazi/README.md @@ -0,0 +1,56 @@ +# 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. + +See https://yazi-rs.github.io/docs/tips#make-yazi-even-faster for more information. + +## Installation + +```sh +ya pkg add yazi-rs/plugins:mime-ext +``` + +## Usage + +Add this to your `~/.config/yazi/yazi.toml`: + +```toml +[[plugin.prepend_fetchers]] +id = "mime" +url = "*" +run = "mime-ext" +prio = "high" +``` + +## Advanced + +You can also customize it in your `~/.config/yazi/init.lua` with: + +```lua +require("mime-ext"):setup { + -- Expand the existing filename database (lowercase), for example: + with_files = { + makefile = "text/makefile", + -- ... + }, + + -- Expand the existing extension database (lowercase), for example: + with_exts = { + mk = "text/makefile", + -- ... + }, + + -- If the mime-type is not in both filename and extension databases, + -- then fallback to Yazi's preset `mime` plugin, which uses `file(1)` + fallback_file1 = false, +} +``` + +## TODO + +- Add more file types (PRs welcome!). +- Compress mime-type tables. + +## License + +This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file. diff --git a/yazi_1/.config/yazi/plugins/mime-ext.yazi/local.lua b/yazi_1/.config/yazi/plugins/mime-ext.yazi/local.lua new file mode 100644 index 0000000..ae0fd1e --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mime-ext.yazi/local.lua @@ -0,0 +1,1146 @@ +--- @since 26.8.15 +--- See https://www.iana.org/assignments/media-types/media-types.xhtml + +local FILES = { + [".envrc"] = "text/plain", + [".gitconfig"] = "text/plain", + [".gitignore"] = "text/plain", + [".luacheckrc"] = "text/lua", + [".npmrc"] = "text/plain", + [".styluaignore"] = "text/plain", + [".zshenv"] = "text/plain", + [".zshrc"] = "text/plain", + ["cargo.lock"] = "application/json", + ["flake.lock"] = "application/json", + license = "text/plain", +} + +local EXTS = { + ["123"] = "application/lotus-1-2-3", + ["3dml"] = "text/in3d.3dml", + ["3ds"] = "image/3ds", + ["3g2"] = "video/3gpp2", + ["3gp"] = "video/3gpp", + ["3mf"] = "model/3mf", + ["7z"] = "application/7z-compressed", + ["for"] = "text/fortran", + ["in"] = "text/plain", + ["n-gage"] = "application/nokia.n-gage.symbian.install", + ["sfd-hdstx"] = "application/hydrostatix.sof-data", + aab = "application/authorware-bin", + aac = "audio/aac", + aam = "application/authorware-map", + aas = "application/authorware-seg", + abw = "application/abiword", + ac = "application/pkix-attr-cert", + acc = "application/americandynamics.acc", + ace = "application/ace-compressed", + acu = "application/acucobol", + acutc = "application/acucorp", + adp = "audio/adpcm", + aep = "application/audiograph", + afm = "application/font-type1", + afp = "application/ibm.modcap", + ahead = "application/ahead.space", + ahk = "text/plain", + ai = "application/postscript", + aif = "audio/aiff", + aifc = "audio/aiff", + aiff = "audio/aiff", + air = "application/adobe.air-application-installer-package+zip", + ait = "application/dvb.ait", + ami = "application/amiga.ami", + apk = "application/android.package-archive", + appcache = "text/cache-manifest", + application = "application/ms-application", + apr = "application/lotus-approach", + arc = "application/freearc", + asc = "application/pgp-signature", + asf = "video/ms-asf", + asm = "text/asm", + aso = "application/accpac.simply.aso", + ass = "text/ass", + asx = "video/ms-asf", + atc = "application/acucorp", + atom = "application/atom+xml", + atomcat = "application/atomcat+xml", + atomsvc = "application/atomsvc+xml", + atx = "application/antix.game-component", + au = "audio/basic", + avi = "video/msvideo", + avif = "image/avif", + aw = "application/applixware", + azf = "application/airzip.filesecure.azf", + azs = "application/airzip.filesecure.azs", + azw = "application/amazon.ebook", + bash = "text/shellscript", + bat = "application/msdownload", + bcpio = "application/bcpio", + bdf = "application/font-bdf", + bdm = "application/syncml.dm+wbxml", + bean = "text/plain", + beancount = "text/plain", + bed = "application/realvnc.bed", + bh2 = "application/fujitsu.oasysprs", + bin = "application/octet-stream", + blb = "application/blorb", + blend = "application/blender", + blend1 = "application/blender", + blorb = "application/blorb", + bmi = "application/bmi", + bmp = "image/bmp", + book = "application/framemaker", + box = "application/previewsystems.box", + boz = "application/bzip2", + bpk = "application/octet-stream", + btif = "image/prs.btif", + bz = "application/bzip", + bz2 = "application/bzip2", + c = "text/c", + c11amc = "application/cluetrust.cartomobile-config", + c11amz = "application/cluetrust.cartomobile-config-pkg", + c4d = "application/clonk.c4group", + c4f = "application/clonk.c4group", + c4g = "application/clonk.c4group", + c4p = "application/clonk.c4group", + c4u = "application/clonk.c4group", + cab = "application/ms-cab-compressed", + caf = "audio/caf", + cap = "application/tcpdump.pcap", + car = "application/curl.car", + cat = "application/ms-pki.seccat", + cb7 = "application/cbr", + cba = "application/cbr", + cbr = "application/cbr", + cbt = "application/cbr", + cbz = "application/cbr", + cc = "text/c", + cct = "application/director", + ccxml = "application/ccxml+xml", + cdbcmsg = "application/contact.cmsg", + cdf = "application/netcdf", + cdkey = "application/mediastation.cdkey", + cdmia = "application/cdmi-capability", + cdmic = "application/cdmi-container", + cdmid = "application/cdmi-domain", + cdmio = "application/cdmi-object", + cdmiq = "application/cdmi-queue", + cdx = "chemical/cdx", + cdxml = "application/chemdraw+xml", + cdy = "application/cinderella", + cer = "application/pkix-cert", + cfg = "text/plain", + cfs = "application/cfs-compressed", + cgm = "image/cgm", + chat = "application/chat", + chm = "application/ms-htmlhelp", + chrt = "application/kde.kchart", + cif = "chemical/cif", + cii = "application/anser-web-certificate-issue-initiation", + cil = "application/ms-artgalry", + cla = "application/claymore", + class = "application/java-vm", + clkk = "application/crick.clicker.keyboard", + clkp = "application/crick.clicker.palette", + clkt = "application/crick.clicker.template", + clkw = "application/crick.clicker.wordbank", + clkx = "application/crick.clicker", + clp = "application/msclip", + cmc = "application/cosmocaller", + cmdf = "chemical/cmdf", + cml = "chemical/cml", + cmp = "application/yellowriver-custom-menu", + cmx = "image/cmx", + cod = "application/rim.cod", + com = "application/msdownload", + conf = "text/plain", + cpio = "application/cpio", + cpp = "text/c", + cpt = "application/mac-compactpro", + crd = "application/mscardfile", + crl = "application/pkix-crl", + crt = "application/x509-ca-cert", + cryptonote = "application/rig.cryptonote", + csh = "application/csh", + csml = "chemical/csml", + csp = "application/commonspace", + css = "text/css", + cst = "application/director", + csv = "text/csv", + cu = "application/cu-seeme", + curl = "text/curl", + cww = "application/prs.cww", + cxt = "application/director", + cxx = "text/c", + dae = "model/collada+xml", + daf = "application/mobius.daf", + dart = "application/dart", + dataless = "application/fdsn.seed", + davmount = "application/davmount+xml", + dbk = "application/docbook+xml", + dcr = "application/director", + dcurl = "text/curl.dcurl", + dd2 = "application/oma.dd2+xml", + ddd = "application/fujixerox.ddd", + deb = "application/debian-package", + def = "text/plain", + deploy = "application/octet-stream", + der = "application/x509-ca-cert", + dfac = "application/dreamfactory", + dgc = "application/dgc-compressed", + dic = "text/c", + dir = "application/director", + dis = "application/mobius.dis", + dist = "application/octet-stream", + distz = "application/octet-stream", + djv = "image/djvu", + djvu = "image/djvu", + dll = "application/msdownload", + dmg = "application/apple-diskimage", + dmp = "application/tcpdump.pcap", + dms = "application/octet-stream", + dna = "application/dna", + doc = "application/msword", + docm = "application/ms-word.document.macroenabled.12", + docx = "application/openxmlformats-officedocument.wordprocessingml.document", + dot = "application/msword", + dotm = "application/ms-word.template.macroenabled.12", + dotx = "application/openxmlformats-officedocument.wordprocessingml.template", + dp = "application/osgi.dp", + dpg = "application/dpgraph", + dra = "audio/dra", + dsc = "text/prs.lines.tag", + dssc = "application/dssc+der", + dtb = "application/dtbook+xml", + dtd = "application/xml-dtd", + dts = "audio/dts", + dtshd = "audio/dts.hd", + dump = "application/octet-stream", + dvb = "video/dvb.file", + dvi = "application/dvi", + dwf = "model/dwf", + dwg = "image/dwg", + dxf = "image/dxf", + dxp = "application/spotfire.dxp", + dxr = "application/director", + ebuild = "application/gentoo.ebuild", + ecelp4800 = "audio/nuera.ecelp4800", + ecelp7470 = "audio/nuera.ecelp7470", + ecelp9600 = "audio/nuera.ecelp9600", + eclass = "application/gentoo.eclass", + ecma = "application/ecmascript", + edm = "application/novadigm.edm", + edx = "application/novadigm.edx", + efif = "application/picsel", + ei6 = "application/pg.osasli", + el = "text/plain", + elc = "application/octet-stream", + emf = "application/msmetafile", + eml = "message/rfc822", + emma = "application/emma+xml", + emz = "application/msmetafile", + env = "text/plain", + eol = "audio/digital-winds", + eot = "application/ms-fontobject", + eps = "application/postscript", + epub = "application/epub+zip", + es3 = "application/eszigno3+xml", + esa = "application/osgi.subsystem", + esf = "application/epson.esf", + et3 = "application/eszigno3+xml", + etx = "text/setext", + eva = "application/eva", + evy = "application/envoy", + exe = "application/msdownload", + exi = "application/exi", + ext = "application/novadigm.ext", + ez = "application/andrew-inset", + ez2 = "application/ezpix-album", + ez3 = "application/ezpix-package", + f = "text/fortran", + f4v = "video/f4v", + f77 = "text/fortran", + f90 = "text/fortran", + fbs = "image/fastbidsheet", + fcdt = "application/adobe.formscentral.fcdt", + fcs = "application/isac.fcs", + fdf = "application/fdf", + fe_launch = "application/denovo.fcselayout-link", + fg5 = "application/fujitsu.oasysgp", + fgd = "application/director", + fh = "image/freehand", + fh4 = "image/freehand", + fh5 = "image/freehand", + fh7 = "image/freehand", + fhc = "image/freehand", + fig = "application/xfig", + fish = "text/shellscript", + flac = "audio/flac", + fli = "video/fli", + flo = "application/micrografx.flo", + flv = "video/flv", + flw = "application/kde.kivio", + flx = "text/fmi.flexstor", + fly = "text/fly", + fm = "application/framemaker", + fnc = "application/frogans.fnc", + fpx = "image/fpx", + frame = "application/framemaker", + fsc = "application/fsc.weblaunch", + fst = "image/fst", + ftc = "application/fluxtime.clip", + fti = "application/anser-web-funds-transfer-initiation", + fvt = "video/fvt", + fxp = "application/adobe.fxp", + fxpl = "application/adobe.fxp", + fzs = "application/fuzzysheet", + g2w = "application/geoplan", + g3 = "image/g3fax", + g3w = "application/geospace", + gac = "application/groove-account", + gam = "application/tads", + gbr = "application/rpki-ghostbusters", + gca = "application/gca-compressed", + gdl = "model/gdl", + geo = "application/dynageo", + gex = "application/geometry-explorer", + ggb = "application/geogebra.file", + ggs = "application/geogebra.slides", + ggt = "application/geogebra.tool", + ghf = "application/groove-help", + gif = "image/gif", + gim = "application/groove-identity-message", + gml = "application/gml+xml", + gmx = "application/gmx", + gnumeric = "application/gnumeric", + go = "text/go", + gph = "application/flographit", + gpx = "application/gpx+xml", + gqf = "application/grafeq", + gqs = "application/grafeq", + gram = "application/srgs", + gramps = "application/gramps-xml", + gre = "application/geometry-explorer", + grv = "application/groove-injector", + grxml = "application/srgs+xml", + gsf = "application/font-ghostscript", + gtar = "application/gtar", + gtm = "application/groove-tool-message", + gtw = "model/gtw", + gv = "text/graphviz", + gxf = "application/gxf", + gxt = "application/geonext", + gz = "application/gzip", + h = "text/c", + h261 = "video/h261", + h263 = "video/h263", + h264 = "video/h264", + hal = "application/hal+xml", + hbci = "application/hbci", + hcl = "text/hcl", + hdf = "application/hdf", + hh = "text/c", + hlp = "application/winhlp", + hpgl = "application/hp-hpgl", + hpid = "application/hp-hpid", + hpp = "text/c", + hps = "application/hp-hps", + hqx = "application/mac-binhex40", + htke = "application/kenameaapp", + htm = "text/html", + html = "text/html", + hvd = "application/yamaha.hv-dic", + hvp = "application/yamaha.hv-voice", + hvs = "application/yamaha.hv-script", + i2g = "application/intergeo", + icc = "application/iccprofile", + ice = "conference/cooltalk", + icm = "application/iccprofile", + ico = "image/icon", + ics = "text/calendar", + ief = "image/ief", + ifb = "text/calendar", + ifm = "application/shana.informed.formdata", + iges = "model/iges", + igl = "application/igloader", + igm = "application/insors.igm", + igs = "model/iges", + igx = "application/micrografx.igx", + iif = "application/shana.informed.interchange", + imp = "application/accpac.simply.imp", + ims = "application/ms-ims", + ini = "text/plain", + ink = "application/inkml+xml", + inkml = "application/inkml+xml", + install = "application/install-instructions", + iota = "application/astraea-software.iota", + ipfix = "application/ipfix", + ipk = "application/shana.informed.package", + irm = "application/ibm.rights-management", + irp = "application/irepository.package+xml", + iso = "application/iso9660-image", + itp = "application/shana.informed.formtemplate", + ivp = "application/immervision-ivp", + ivu = "application/immervision-ivu", + jad = "text/sun.j2me.app-descriptor", + jam = "application/jam", + jar = "application/java-archive", + java = "text/java-source", + jisp = "application/jisp", + jlt = "application/hp-jlyt", + jnlp = "application/java-jnlp-file", + joda = "application/joost.joda-archive", + jpe = "image/jpeg", + jpeg = "image/jpeg", + jpg = "image/jpeg", + jpgm = "video/jpm", + jpgv = "video/jpeg", + jpm = "video/jpm", + js = "text/javascript", + json = "application/json", + jsonc = "application/json", + jsonml = "application/jsonml+json", + jsx = "text/jsx", + jxl = "image/jxl", + kar = "audio/midi", + karbon = "application/kde.karbon", + kfo = "application/kde.kformula", + kia = "application/kidspiration", + kml = "application/google-earth.kml+xml", + kmz = "application/google-earth.kmz", + kne = "application/kinar", + knp = "application/kinar", + kon = "application/kde.kontour", + kpr = "application/kde.kpresenter", + kpt = "application/kde.kpresenter", + kpxx = "application/ds-keypoint", + kra = "application/krita", + ["kra~"] = "application/krita", + ksp = "application/kde.kspread", + ktr = "application/kahootz", + ktx = "image/ktx", + ktz = "application/kahootz", + kwd = "application/kde.kword", + kwt = "application/kde.kword", + lasxml = "application/las.las+xml", + latex = "application/latex", + lbd = "application/llamagraphics.life-balance.desktop", + lbe = "application/llamagraphics.life-balance.exchange+xml", + les = "application/hhe.lesson-player", + lha = "application/lzh-compressed", + link66 = "application/route66.link66+xml", + list = "text/plain", + list3820 = "application/ibm.modcap", + listafp = "application/ibm.modcap", + lnk = "application/ms-shortcut", + log = "text/plain", + lostxml = "application/lost+xml", + lrf = "application/octet-stream", + lrm = "application/ms-lrm", + lrz = "application/lrzip", + ltf = "application/frogans.ltf", + lua = "text/lua", + lvp = "audio/lucent.voice", + lwp = "application/lotus-wordpro", + lz = "application/lzip", + lz4 = "application/lz4", + lzh = "application/lzh-compressed", + lzma = "application/lzma", + lzo = "application/lzop", + m13 = "application/msmediaview", + m14 = "application/msmediaview", + m1v = "video/mpeg", + m21 = "application/mp21", + m2a = "audio/mpeg", + m2t = "video/mp2t", + m2ts = "video/mp2t", + m2v = "video/mpeg", + m3a = "audio/mpeg", + m3u = "audio/mpegurl", + m3u8 = "application/apple.mpegurl", + m4a = "audio/mp4", + m4u = "video/mpegurl", + m4v = "video/m4v", + ma = "application/mathematica", + mads = "application/mads+xml", + mag = "application/ecowin.chart", + maker = "application/framemaker", + man = "text/troff", + mar = "application/octet-stream", + mathml = "application/mathml+xml", + mb = "application/mathematica", + mbk = "application/mobius.mbk", + mbox = "application/mbox", + mc1 = "application/medcalcdata", + mcd = "application/mcd", + mcurl = "text/curl.mcurl", + md = "text/markdown", + mdb = "application/msaccess", + mdi = "image/ms-modi", + me = "text/troff", + mesh = "model/mesh", + meta4 = "application/metalink4+xml", + metalink = "application/metalink+xml", + mets = "application/mets+xml", + mfm = "application/mfmp", + mft = "application/rpki-manifest", + mgp = "application/osgeo.mapguide.package", + mgz = "application/proteus.magazine", + mid = "audio/midi", + midi = "audio/midi", + mie = "application/mie", + mif = "application/mif", + mime = "message/rfc822", + mj2 = "video/mj2", + mjp2 = "video/mj2", + mjs = "text/javascript", + mk3d = "video/matroska", + mka = "audio/matroska", + mks = "video/matroska", + mkv = "video/matroska", + mlp = "application/dolby.mlp", + mmd = "application/chipnuts.karaoke-mmd", + mmf = "application/smaf", + mmr = "image/fujixerox.edmics-mmr", + mng = "video/mng", + mny = "application/msmoney", + mobi = "application/mobipocket-ebook", + mods = "application/mods+xml", + mov = "video/quicktime", + movie = "video/sgi-movie", + mp2 = "audio/mpeg", + mp21 = "application/mp21", + mp2a = "audio/mpeg", + mp3 = "audio/mpeg", + mp4 = "video/mp4", + mp4a = "audio/mp4", + mp4s = "application/mp4", + mp4v = "video/mp4", + mpc = "application/mophun.certificate", + mpe = "video/mpeg", + mpeg = "video/mpeg", + mpg = "video/mpeg", + mpg4 = "video/mp4", + mpga = "audio/mpeg", + mpkg = "application/apple.installer+xml", + mpm = "application/blueice.multipass", + mpn = "application/mophun.application", + mpp = "application/ms-project", + mpt = "application/ms-project", + mpy = "application/ibm.minipay", + mqy = "application/mobius.mqy", + mrc = "application/marc", + mrcx = "application/marcxml+xml", + ms = "text/troff", + mscml = "application/mediaservercontrol+xml", + mseed = "application/fdsn.mseed", + mseq = "application/mseq", + msf = "application/epson.msf", + msh = "model/mesh", + msi = "application/msdownload", + msl = "application/mobius.msl", + msty = "application/muvee.style", + mtl = "model/mtl", + mts = "video/mp2t", + mus = "application/musician", + musicxml = "application/recordare.musicxml+xml", + mvb = "application/msmediaview", + mwf = "application/mfer", + mxf = "application/mxf", + mxl = "application/recordare.musicxml", + mxml = "application/xv+xml", + mxs = "application/triscape.mxs", + mxu = "video/mpegurl", + n3 = "text/n3", + nb = "application/mathematica", + nbp = "application/wolfram.player", + nc = "application/netcdf", + ncx = "application/dtbncx+xml", + nfo = "text/nfo", + ngdat = "application/nokia.n-gage.data", + nitf = "application/nitf", + nix = "text/nix", + nlu = "application/neurolanguage.nlu", + nml = "application/enliven", + nnd = "application/noblenet-directory", + nns = "application/noblenet-sealer", + nnw = "application/noblenet-web", + npx = "image/net-fpx", + nsc = "application/conference", + nsf = "application/lotus-notes", + ntf = "application/nitf", + nzb = "application/nzb", + oa2 = "application/fujitsu.oasys2", + oa3 = "application/fujitsu.oasys3", + oas = "application/fujitsu.oasys", + obd = "application/msbinder", + obj = "model/obj", + oda = "application/oda", + odb = "application/oasis.opendocument.database", + odc = "application/oasis.opendocument.chart", + odf = "application/oasis.opendocument.formula", + odft = "application/oasis.opendocument.formula-template", + odg = "application/oasis.opendocument.graphics", + odi = "application/oasis.opendocument.image", + odm = "application/oasis.opendocument.text-master", + odp = "application/oasis.opendocument.presentation", + ods = "application/oasis.opendocument.spreadsheet", + odt = "application/oasis.opendocument.text", + oga = "audio/ogg", + ogg = "audio/ogg", + ogv = "video/ogg", + ogx = "application/ogg", + omdoc = "application/omdoc+xml", + onepkg = "application/onenote", + onetmp = "application/onenote", + onetoc = "application/onenote", + onetoc2 = "application/onenote", + opf = "application/oebps-package+xml", + opml = "text/opml", + oprc = "application/palm", + opus = "audio/ogg", + org = "application/lotus-organizer", + osf = "application/yamaha.openscoreformat", + osfpvg = "application/yamaha.openscoreformat.osfpvg+xml", + otc = "application/oasis.opendocument.chart-template", + otf = "font/otf", + otg = "application/oasis.opendocument.graphics-template", + oth = "application/oasis.opendocument.text-web", + oti = "application/oasis.opendocument.image-template", + otp = "application/oasis.opendocument.presentation-template", + ots = "application/oasis.opendocument.spreadsheet-template", + ott = "application/oasis.opendocument.text-template", + oxps = "application/oxps", + oxt = "application/openofficeorg.extension", + p = "text/pascal", + p10 = "application/pkcs10", + p12 = "application/pkcs12", + p7b = "application/pkcs7-certificates", + p7c = "application/pkcs7-mime", + p7m = "application/pkcs7-mime", + p7r = "application/pkcs7-certreqresp", + p7s = "application/pkcs7-signature", + p8 = "application/pkcs8", + pas = "text/pascal", + patch = "text/diff", + paw = "application/pawaafile", + pbd = "application/powerbuilder6", + pbm = "image/portable-bitmap", + pcap = "application/tcpdump.pcap", + pcf = "application/font-pcf", + pcl = "application/hp-pcl", + pclxl = "application/hp-pclxl", + pct = "image/pict", + pcurl = "application/curl.pcurl", + pcx = "image/pcx", + pdb = "application/palm", + pdf = "application/pdf", + pfa = "application/font-type1", + pfb = "application/font-type1", + pfm = "application/font-type1", + pfr = "application/font-tdpfr", + pfx = "application/pkcs12", + pgm = "image/portable-graymap", + pgn = "application/chess-pgn", + pgp = "application/pgp-encrypted", + php = "text/php", + pic = "image/pict", + pkg = "application/octet-stream", + pki = "application/pkixcmp", + pkipath = "application/pkix-pkipath", + plb = "application/3gpp.pic-bw-large", + plc = "application/mobius.plc", + plf = "application/pocketlearn", + pls = "application/pls+xml", + pml = "application/ctc-posml", + png = "image/png", + pnm = "image/portable-anymap", + portpkg = "application/macports.portpkg", + pot = "application/ms-powerpoint", + potm = "application/ms-powerpoint.template.macroenabled.12", + potx = "application/openxmlformats-officedocument.presentationml.template", + ppam = "application/ms-powerpoint.addin.macroenabled.12", + ppd = "application/cups-ppd", + ppm = "image/portable-pixmap", + pps = "application/ms-powerpoint", + ppsm = "application/ms-powerpoint.slideshow.macroenabled.12", + ppsx = "application/openxmlformats-officedocument.presentationml.slideshow", + ppt = "application/ms-powerpoint", + pptm = "application/ms-powerpoint.presentation.macroenabled.12", + pptx = "application/openxmlformats-officedocument.presentationml.presentation", + pqa = "application/palm", + prc = "application/mobipocket-ebook", + pre = "application/lotus-freelance", + prf = "application/pics-rules", + ps = "application/postscript", + psb = "application/3gpp.pic-bw-small", + psd = "image/adobe.photoshop", + psf = "application/font-linux-psf", + pskcxml = "application/pskc+xml", + ptid = "application/pvi.ptid1", + pub = "application/mspublisher", + pvb = "application/3gpp.pic-bw-var", + pwn = "application/3m.post-it-notes", + py = "text/python", + pya = "audio/ms-playready.media.pya", + pyv = "video/ms-playready.media.pyv", + qam = "application/epson.quickanime", + qbo = "application/intu.qbo", + qfx = "application/intu.qfx", + qml = "text/qml", + qps = "application/publishare-delta-tree", + qt = "video/quicktime", + qwd = "application/quark.quarkxpress", + qwt = "application/quark.quarkxpress", + qxb = "application/quark.quarkxpress", + qxd = "application/quark.quarkxpress", + qxl = "application/quark.quarkxpress", + qxt = "application/quark.quarkxpress", + r = "text/r", + ra = "audio/pn-realaudio", + ram = "audio/pn-realaudio", + rar = "application/rar", + ras = "image/cmu-raster", + rb = "text/ruby", + rcprofile = "application/ipunplugged.rcprofile", + rdf = "application/rdf+xml", + rdz = "application/data-vision.rdz", + rep = "application/businessobjects", + res = "application/dtbresource+xml", + rgb = "image/rgb", + rif = "application/reginfo+xml", + rip = "audio/rip", + ris = "application/research-info-systems", + rl = "application/resource-lists+xml", + rlc = "image/fujixerox.edmics-rlc", + rld = "application/resource-lists-diff+xml", + rm = "application/rn-realmedia", + rmi = "audio/midi", + rmp = "audio/pn-realaudio-plugin", + rms = "application/jcp.javame.midlet-rms", + rmvb = "application/rn-realmedia-vbr", + rnc = "application/relax-ng-compact-syntax", + roa = "application/rpki-roa", + roff = "text/troff", + rp9 = "application/cloanto.rp9", + rpm = "application/rpm", + rpss = "application/nokia.radio-presets", + rpst = "application/nokia.radio-preset", + rq = "application/sparql-query", + rs = "text/rust", + rsd = "application/rsd+xml", + rss = "application/rss+xml", + rtf = "application/rtf", + rtx = "text/richtext", + s = "text/asm", + s3m = "audio/s3m", + saf = "application/yamaha.smaf-audio", + sbml = "application/sbml+xml", + sc = "application/ibm.secure-container", + scd = "application/msschedule", + scm = "application/lotus-screencam", + scq = "application/scvp-cv-request", + scs = "application/scvp-cv-response", + scss = "text/scss", + scurl = "text/curl.scurl", + sda = "application/stardivision.draw", + sdc = "application/stardivision.calc", + sdd = "application/stardivision.impress", + sdkd = "application/solent.sdkm+xml", + sdkm = "application/solent.sdkm+xml", + sdp = "application/sdp", + sdw = "application/stardivision.writer", + see = "application/seemail", + seed = "application/fdsn.seed", + sema = "application/sema", + semd = "application/semd", + semf = "application/semf", + ser = "application/java-serialized-object", + setpay = "application/set-payment-initiation", + setreg = "application/set-registration-initiation", + sfs = "application/spotfire.sfs", + sfv = "text/sfv", + sgi = "image/sgi", + sgl = "application/stardivision.writer-global", + sgm = "text/sgml", + sgml = "text/sgml", + sh = "text/shellscript", + shar = "application/shar", + shf = "application/shf+xml", + sid = "image/mrsid-image", + sig = "application/pgp-signature", + sil = "audio/silk", + silo = "model/mesh", + sis = "application/symbian.install", + sisx = "application/symbian.install", + sit = "application/stuffit", + sitx = "application/stuffitx", + skd = "application/koan", + skm = "application/koan", + skp = "application/koan", + skt = "application/koan", + sldm = "application/ms-powerpoint.slide.macroenabled.12", + sldx = "application/openxmlformats-officedocument.presentationml.slide", + slt = "application/epson.salt", + sm = "application/stepmania.stepchart", + smf = "application/stardivision.math", + smi = "application/smil+xml", + smil = "application/smil+xml", + smv = "video/smv", + smzip = "application/stepmania.package", + snd = "audio/basic", + snf = "application/font-snf", + so = "application/octet-stream", + spc = "application/pkcs7-certificates", + spf = "application/yamaha.smaf-phrase", + spl = "application/futuresplash", + spot = "text/in3d.spot", + spp = "application/scvp-vp-response", + spq = "application/scvp-vp-request", + spx = "audio/ogg", + sql = "application/sql", + src = "application/wais-source", + srt = "application/subrip", + sru = "application/sru+xml", + srx = "application/sparql-results+xml", + ssdl = "application/ssdl+xml", + sse = "application/kodak-descriptor", + ssf = "application/epson.ssf", + ssml = "application/ssml+xml", + st = "application/sailingtracker.track", + stc = "application/sun.xml.calc.template", + std = "application/sun.xml.draw.template", + step = "model/step", + stf = "application/wt.stf", + sti = "application/sun.xml.impress.template", + stk = "application/hyperstudio", + stl = "model/stl", + stp = "model/step", + str = "application/pg.format", + stw = "application/sun.xml.writer.template", + sub = "text/dvb.subtitle", + sus = "application/sus-calendar", + susp = "application/sus-calendar", + sv4cpio = "application/sv4cpio", + sv4crc = "application/sv4crc", + svc = "application/dvb.service", + svd = "application/svd", + svg = "image/svg+xml", + svgz = "image/svg+xml", + swa = "application/director", + swf = "application/shockwave-flash", + swi = "application/aristanetworks.swi", + sxc = "application/sun.xml.calc", + sxd = "application/sun.xml.draw", + sxg = "application/sun.xml.writer.global", + sxi = "application/sun.xml.impress", + sxm = "application/sun.xml.math", + sxw = "application/sun.xml.writer", + t = "text/troff", + t3 = "application/t3vm-image", + taglet = "application/mynfc", + tao = "application/tao.intent-module-archive", + tar = "application/tar", + tcap = "application/3gpp2.tcap", + tcl = "application/tcl", + teacher = "application/smart.teacher", + tei = "application/tei+xml", + teicorpus = "application/tei+xml", + tex = "application/tex", + texi = "application/texinfo", + texinfo = "application/texinfo", + text = "text/plain", + tf = "text/hcl", + tfi = "application/thraud+xml", + tfm = "application/tex-tfm", + tfrc = "text/hcl", + tfstate = "application/json", + tfvars = "text/hcl", + tga = "image/tga", + thmx = "application/ms-officetheme", + tif = "image/tiff", + tiff = "image/tiff", + tmo = "application/tmobile-livetv", + tofu = "text/hcl", + toml = "text/toml", + torrent = "application/bittorrent", + tpl = "application/groove-tool-template", + tpt = "application/trid.tpt", + tr = "text/troff", + tra = "application/trueapp", + trm = "application/msterminal", + ts = "text/typescript", + tsd = "application/timestamped-data", + tsv = "text/tab-separated-values", + tsx = "text/tsx", + ttc = "font/collection", + ttf = "font/ttf", + ttl = "text/turtle", + twd = "application/simtech-mindmapper", + twds = "application/simtech-mindmapper", + txd = "application/genomatix.tuxedo", + txf = "application/mobius.txf", + txt = "text/plain", + u32 = "application/authorware-bin", + udeb = "application/debian-package", + ufd = "application/ufdl", + ufdl = "application/ufdl", + ulx = "application/glulx", + umj = "application/umajin", + unityweb = "application/unity", + uoml = "application/uoml+xml", + uri = "text/uri-list", + uris = "text/uri-list", + urls = "text/uri-list", + ustar = "application/ustar", + utz = "application/uiq.theme", + uu = "text/uuencode", + uva = "audio/dece.audio", + uvd = "application/dece.data", + uvf = "application/dece.data", + uvg = "image/dece.graphic", + uvh = "video/dece.hd", + uvi = "image/dece.graphic", + uvm = "video/dece.mobile", + uvp = "video/dece.pd", + uvs = "video/dece.sd", + uvt = "application/dece.ttml+xml", + uvu = "video/uvvu.mp4", + uvv = "video/dece.video", + uvva = "audio/dece.audio", + uvvd = "application/dece.data", + uvvf = "application/dece.data", + uvvg = "image/dece.graphic", + uvvh = "video/dece.hd", + uvvi = "image/dece.graphic", + uvvm = "video/dece.mobile", + uvvp = "video/dece.pd", + uvvs = "video/dece.sd", + uvvt = "application/dece.ttml+xml", + uvvu = "video/uvvu.mp4", + uvvv = "video/dece.video", + uvvx = "application/dece.unspecified", + uvvz = "application/dece.zip", + uvx = "application/dece.unspecified", + uvz = "application/dece.zip", + vcard = "text/vcard", + vcd = "application/cdlink", + vcf = "text/vcard", + vcg = "application/groove-vcard", + vcs = "text/vcalendar", + vcx = "application/vcx", + vim = "text/plain", + vis = "application/visionary", + viv = "video/vivo", + vob = "video/ms-vob", + vor = "application/stardivision.writer", + vox = "application/authorware-bin", + vrml = "model/vrml", + vsd = "application/visio", + vsf = "application/vsf", + vss = "application/visio", + vst = "application/visio", + vsw = "application/visio", + vtu = "model/vtu", + vxml = "application/voicexml+xml", + w3d = "application/director", + wad = "application/doom", + wasm = "application/wasm", + wav = "audio/wav", + wax = "audio/ms-wax", + wbmp = "image/wap.wbmp", + wbs = "application/criticaltools.wbs+xml", + wbxml = "application/wap.wbxml", + wcm = "application/ms-works", + wdb = "application/ms-works", + wdp = "image/ms-photo", + weba = "audio/webm", + webm = "video/webm", + webp = "image/webp", + wg = "application/pmi.widget", + wgt = "application/widget", + wks = "application/ms-works", + wm = "video/ms-wm", + wma = "audio/ms-wma", + wmd = "application/ms-wmd", + wmf = "application/msmetafile", + wml = "text/wap.wml", + wmlc = "application/wap.wmlc", + wmls = "text/wap.wmlscript", + wmlsc = "application/wap.wmlscriptc", + wmv = "video/ms-wmv", + wmx = "video/ms-wmx", + wmz = "application/ms-wmz", + woff = "font/woff", + woff2 = "font/woff2", + wpd = "application/wordperfect", + wpl = "application/ms-wpl", + wps = "application/ms-works", + wqd = "application/wqd", + wri = "application/mswrite", + wrl = "model/vrml", + wsdl = "application/wsdl+xml", + wspolicy = "application/wspolicy+xml", + wtb = "application/webturbo", + wvx = "video/ms-wvx", + x32 = "application/authorware-bin", + x3d = "model/x3d+xml", + x3db = "model/x3d+binary", + x3dbz = "model/x3d+binary", + x3dv = "model/x3d+vrml", + x3dvz = "model/x3d+vrml", + x3dz = "model/x3d+xml", + xaml = "application/xaml+xml", + xap = "application/silverlight-app", + xar = "application/xara", + xbap = "application/ms-xbap", + xbd = "application/fujixerox.docuworks.binder", + xbm = "image/xbitmap", + xdf = "application/xcap-diff+xml", + xdm = "application/syncml.dm+xml", + xdp = "application/adobe.xdp+xml", + xdssc = "application/dssc+xml", + xdw = "application/fujixerox.docuworks", + xenc = "application/xenc+xml", + xer = "application/patch-ops-error+xml", + xfdf = "application/adobe.xfdf", + xfdl = "application/xfdl", + xht = "application/xhtml+xml", + xhtml = "application/xhtml+xml", + xhvml = "application/xv+xml", + xif = "image/xiff", + xla = "application/ms-excel", + xlam = "application/ms-excel.addin.macroenabled.12", + xlc = "application/ms-excel", + xlf = "application/xliff+xml", + xlm = "application/ms-excel", + xls = "application/ms-excel", + xlsb = "application/ms-excel.sheet.binary.macroenabled.12", + xlsm = "application/ms-excel.sheet.macroenabled.12", + xlsx = "application/openxmlformats-officedocument.spreadsheetml.sheet", + xlt = "application/ms-excel", + xltm = "application/ms-excel.template.macroenabled.12", + xltx = "application/openxmlformats-officedocument.spreadsheetml.template", + xlw = "application/ms-excel", + xm = "audio/xm", + xml = "application/xml", + xo = "application/olpc-sugar", + xop = "application/xop+xml", + xpak = "application/gentoo.xpak", + xpi = "application/xpinstall", + xpl = "application/xproc+xml", + xpm = "image/xpixmap", + xpr = "application/is-xpr", + xps = "application/ms-xpsdocument", + xpw = "application/intercon.formnet", + xpx = "application/intercon.formnet", + xsl = "application/xml", + xslt = "application/xslt+xml", + xsm = "application/syncml+xml", + xspf = "application/xspf+xml", + xul = "application/mozilla.xul+xml", + xvm = "application/xv+xml", + xvml = "application/xv+xml", + xwd = "image/xwindowdump", + xyz = "chemical/xyz", + xz = "application/xz", + yaml = "text/yaml", + yang = "application/yang", + yin = "application/yin+xml", + yml = "text/yaml", + z1 = "application/zmachine", + z2 = "application/zmachine", + z3 = "application/zmachine", + z4 = "application/zmachine", + z5 = "application/zmachine", + z6 = "application/zmachine", + z7 = "application/zmachine", + z8 = "application/zmachine", + zaz = "application/zzazz.deck+xml", + zip = "application/zip", + zir = "application/zul", + zirz = "application/zul", + zmm = "application/handheld-entertainment+xml", + zsh = "text/shellscript", + zst = "application/zstd", +} + +local options = ya.sync( + function(st) + return { + with_files = st.with_files, + with_exts = st.with_exts, + custom_only = st.custom_only, + fallback_file1 = st.fallback_file1, + } + end +) + +local M = {} + +function M:setup(opts) + opts = opts or {} + + self.with_files = opts.with_files + self.with_exts = opts.with_exts + self.custom_only = opts.custom_only + self.fallback_file1 = opts.fallback_file1 +end + +function M:fetch(job) + local opts = options() + local merged_files = opts.with_files or {} + local merged_exts = opts.with_exts or {} + + if not opts.custom_only then + merged_files = ya.dict_merge(FILES, merged_files) + merged_exts = ya.dict_merge(EXTS, merged_exts) + end + + return ya.co(function() + local updates, unknown = {}, {} + for _, file in ipairs(job.files) do + if file.cha.is_dummy then + coroutine.yield(file, { retry = true }) + goto continue + end + + local mime + if file.cha.len == 0 then + mime = "inode/empty" + else + mime = merged_files[(file.url.name or ""):lower()] + mime = mime or merged_exts[(file.url.ext or ""):lower()] + end + + if not mime and opts.fallback_file1 then + unknown[#unknown + 1] = file + elseif not mime then + mime = "application/octet-stream" + end + + if mime and coroutine.yield(file, { mime }) then + updates[file.url] = mime + end + ::continue:: + end + + if next(updates) then + ya.emit("update_mimes", { updates = updates }) + end + + if #unknown > 0 then + self.fallback_builtin(job, unknown) + end + end) +end + +function M.fallback_builtin(job, unknown) + local next = require("mime.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 diff --git a/yazi_1/.config/yazi/plugins/mime-ext.yazi/main.lua b/yazi_1/.config/yazi/plugins/mime-ext.yazi/main.lua new file mode 100644 index 0000000..abe183b --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mime-ext.yazi/main.lua @@ -0,0 +1,1130 @@ +--- @since 25.12.29 + +local FILES = { + [".envrc"] = "text/plain", + [".gitconfig"] = "text/plain", + [".gitignore"] = "text/plain", + [".luacheckrc"] = "text/lua", + [".npmrc"] = "text/plain", + [".styluaignore"] = "text/plain", + [".zshenv"] = "text/plain", + [".zshrc"] = "text/plain", + ["cargo.lock"] = "application/json", + ["flake.lock"] = "application/json", + license = "text/plain", + config = "text/plain", +} + +local EXTS = { + ["123"] = "application/lotus-1-2-3", + ["3dml"] = "text/in3d.3dml", + ["3ds"] = "image/3ds", + ["3g2"] = "video/3gpp2", + ["3gp"] = "video/3gpp", + ["7z"] = "application/7z-compressed", + ["for"] = "text/fortran", + ["in"] = "text/plain", + ["n-gage"] = "application/nokia.n-gage.symbian.install", + ["sfd-hdstx"] = "application/hydrostatix.sof-data", + aab = "application/authorware-bin", + aac = "audio/aac", + aam = "application/authorware-map", + aas = "application/authorware-seg", + abw = "application/abiword", + ac = "application/pkix-attr-cert", + acc = "application/americandynamics.acc", + ace = "application/ace-compressed", + acu = "application/acucobol", + acutc = "application/acucorp", + adp = "audio/adpcm", + aep = "application/audiograph", + afm = "application/font-type1", + afp = "application/ibm.modcap", + ahead = "application/ahead.space", + ai = "application/postscript", + aif = "audio/aiff", + aifc = "audio/aiff", + aiff = "audio/aiff", + air = "application/adobe.air-application-installer-package+zip", + ait = "application/dvb.ait", + ami = "application/amiga.ami", + apk = "application/android.package-archive", + appcache = "text/cache-manifest", + application = "application/ms-application", + apr = "application/lotus-approach", + arc = "application/freearc", + asc = "application/pgp-signature", + asf = "video/ms-asf", + asm = "text/asm", + aso = "application/accpac.simply.aso", + ass = "text/ass", + asx = "video/ms-asf", + atc = "application/acucorp", + atom = "application/atom+xml", + atomcat = "application/atomcat+xml", + atomsvc = "application/atomsvc+xml", + atx = "application/antix.game-component", + au = "audio/basic", + avi = "video/msvideo", + avif = "image/avif", + aw = "application/applixware", + azf = "application/airzip.filesecure.azf", + azs = "application/airzip.filesecure.azs", + azw = "application/amazon.ebook", + bash = "text/shellscript", + bat = "application/msdownload", + bcpio = "application/bcpio", + bdf = "application/font-bdf", + bdm = "application/syncml.dm+wbxml", + bean = "text/plain", + beancount = "text/plain", + bed = "application/realvnc.bed", + bh2 = "application/fujitsu.oasysprs", + bin = "application/octet-stream", + blb = "application/blorb", + blorb = "application/blorb", + bmi = "application/bmi", + bmp = "image/bmp", + book = "application/framemaker", + box = "application/previewsystems.box", + boz = "application/bzip2", + bpk = "application/octet-stream", + btif = "image/prs.btif", + bz = "application/bzip", + bz2 = "application/bzip2", + c = "text/c", + c11amc = "application/cluetrust.cartomobile-config", + c11amz = "application/cluetrust.cartomobile-config-pkg", + c4d = "application/clonk.c4group", + c4f = "application/clonk.c4group", + c4g = "application/clonk.c4group", + c4p = "application/clonk.c4group", + c4u = "application/clonk.c4group", + cab = "application/ms-cab-compressed", + caf = "audio/caf", + cap = "application/tcpdump.pcap", + car = "application/curl.car", + cat = "application/ms-pki.seccat", + cb7 = "application/cbr", + cba = "application/cbr", + cbr = "application/cbr", + cbt = "application/cbr", + cbz = "application/cbr", + cc = "text/c", + cct = "application/director", + ccxml = "application/ccxml+xml", + cdbcmsg = "application/contact.cmsg", + cdf = "application/netcdf", + cdkey = "application/mediastation.cdkey", + cdmia = "application/cdmi-capability", + cdmic = "application/cdmi-container", + cdmid = "application/cdmi-domain", + cdmio = "application/cdmi-object", + cdmiq = "application/cdmi-queue", + cdx = "chemical/cdx", + cdxml = "application/chemdraw+xml", + cdy = "application/cinderella", + cer = "application/pkix-cert", + cfg = "text/plain", + cfs = "application/cfs-compressed", + cgm = "image/cgm", + chat = "application/chat", + chm = "application/ms-htmlhelp", + chrt = "application/kde.kchart", + cif = "chemical/cif", + cii = "application/anser-web-certificate-issue-initiation", + cil = "application/ms-artgalry", + cla = "application/claymore", + class = "application/java-vm", + clkk = "application/crick.clicker.keyboard", + clkp = "application/crick.clicker.palette", + clkt = "application/crick.clicker.template", + clkw = "application/crick.clicker.wordbank", + clkx = "application/crick.clicker", + clp = "application/msclip", + cmc = "application/cosmocaller", + cmdf = "chemical/cmdf", + cml = "chemical/cml", + cmp = "application/yellowriver-custom-menu", + cmx = "image/cmx", + cod = "application/rim.cod", + com = "application/msdownload", + conf = "text/plain", + config = "text/plain", + cpio = "application/cpio", + cpp = "text/c", + cpt = "application/mac-compactpro", + crd = "application/mscardfile", + crl = "application/pkix-crl", + crt = "application/x509-ca-cert", + cryptonote = "application/rig.cryptonote", + csh = "application/csh", + csml = "chemical/csml", + csp = "application/commonspace", + css = "text/css", + cst = "application/director", + csv = "text/csv", + cu = "application/cu-seeme", + curl = "text/curl", + cww = "application/prs.cww", + cxt = "application/director", + cxx = "text/c", + dae = "model/collada+xml", + daf = "application/mobius.daf", + dart = "application/dart", + dataless = "application/fdsn.seed", + davmount = "application/davmount+xml", + dbk = "application/docbook+xml", + dcr = "application/director", + dcurl = "text/curl.dcurl", + dd2 = "application/oma.dd2+xml", + ddd = "application/fujixerox.ddd", + deb = "application/debian-package", + def = "text/plain", + deploy = "application/octet-stream", + der = "application/x509-ca-cert", + dfac = "application/dreamfactory", + dgc = "application/dgc-compressed", + dic = "text/c", + dir = "application/director", + dis = "application/mobius.dis", + dist = "application/octet-stream", + distz = "application/octet-stream", + djv = "image/djvu", + djvu = "image/djvu", + dll = "application/msdownload", + dmg = "application/apple-diskimage", + dmp = "application/tcpdump.pcap", + dms = "application/octet-stream", + dna = "application/dna", + doc = "application/msword", + docm = "application/ms-word.document.macroenabled.12", + docx = "application/openxmlformats-officedocument.wordprocessingml.document", + dot = "application/msword", + dotm = "application/ms-word.template.macroenabled.12", + dotx = "application/openxmlformats-officedocument.wordprocessingml.template", + dp = "application/osgi.dp", + dpg = "application/dpgraph", + dra = "audio/dra", + dsc = "text/prs.lines.tag", + dssc = "application/dssc+der", + dtb = "application/dtbook+xml", + dtd = "application/xml-dtd", + dts = "audio/dts", + dtshd = "audio/dts.hd", + dump = "application/octet-stream", + dvb = "video/dvb.file", + dvi = "application/dvi", + dwf = "model/dwf", + dwg = "image/dwg", + dxf = "image/dxf", + dxp = "application/spotfire.dxp", + dxr = "application/director", + ebuild = "application/gentoo.ebuild", + ecelp4800 = "audio/nuera.ecelp4800", + ecelp7470 = "audio/nuera.ecelp7470", + ecelp9600 = "audio/nuera.ecelp9600", + eclass = "application/gentoo.eclass", + ecma = "application/ecmascript", + edm = "application/novadigm.edm", + edx = "application/novadigm.edx", + efif = "application/picsel", + ei6 = "application/pg.osasli", + elc = "application/octet-stream", + emf = "application/msmetafile", + eml = "message/rfc822", + emma = "application/emma+xml", + emz = "application/msmetafile", + env = "text/plain", + eol = "audio/digital-winds", + eot = "application/ms-fontobject", + eps = "application/postscript", + epub = "application/epub+zip", + es3 = "application/eszigno3+xml", + esa = "application/osgi.subsystem", + esf = "application/epson.esf", + et3 = "application/eszigno3+xml", + etx = "text/setext", + eva = "application/eva", + evy = "application/envoy", + exe = "application/msdownload", + exi = "application/exi", + ext = "application/novadigm.ext", + ez = "application/andrew-inset", + ez2 = "application/ezpix-album", + ez3 = "application/ezpix-package", + f = "text/fortran", + f4v = "video/f4v", + f77 = "text/fortran", + f90 = "text/fortran", + fbs = "image/fastbidsheet", + fcdt = "application/adobe.formscentral.fcdt", + fcs = "application/isac.fcs", + fdf = "application/fdf", + fe_launch = "application/denovo.fcselayout-link", + fg5 = "application/fujitsu.oasysgp", + fgd = "application/director", + fh = "image/freehand", + fh4 = "image/freehand", + fh5 = "image/freehand", + fh7 = "image/freehand", + fhc = "image/freehand", + fig = "application/xfig", + fish = "text/shellscript", + flac = "audio/flac", + fli = "video/fli", + flo = "application/micrografx.flo", + flv = "video/flv", + flw = "application/kde.kivio", + flx = "text/fmi.flexstor", + fly = "text/fly", + fm = "application/framemaker", + fnc = "application/frogans.fnc", + fpx = "image/fpx", + frame = "application/framemaker", + fsc = "application/fsc.weblaunch", + fst = "image/fst", + ftc = "application/fluxtime.clip", + fti = "application/anser-web-funds-transfer-initiation", + fvt = "video/fvt", + fxp = "application/adobe.fxp", + fxpl = "application/adobe.fxp", + fzs = "application/fuzzysheet", + g2w = "application/geoplan", + g3 = "image/g3fax", + g3w = "application/geospace", + gac = "application/groove-account", + gam = "application/tads", + gbr = "application/rpki-ghostbusters", + gca = "application/gca-compressed", + gdl = "model/gdl", + geo = "application/dynageo", + gex = "application/geometry-explorer", + ggb = "application/geogebra.file", + ggs = "application/geogebra.slides", + ggt = "application/geogebra.tool", + ghf = "application/groove-help", + gif = "image/gif", + gim = "application/groove-identity-message", + gml = "application/gml+xml", + gmx = "application/gmx", + gnumeric = "application/gnumeric", + go = "text/go", + gph = "application/flographit", + gpx = "application/gpx+xml", + gqf = "application/grafeq", + gqs = "application/grafeq", + gram = "application/srgs", + gramps = "application/gramps-xml", + gre = "application/geometry-explorer", + grv = "application/groove-injector", + grxml = "application/srgs+xml", + gsf = "application/font-ghostscript", + gtar = "application/gtar", + gtm = "application/groove-tool-message", + gtw = "model/gtw", + gv = "text/graphviz", + gxf = "application/gxf", + gxt = "application/geonext", + h = "text/c", + h261 = "video/h261", + h263 = "video/h263", + h264 = "video/h264", + hal = "application/hal+xml", + hbci = "application/hbci", + hcl = "text/hcl", + hdf = "application/hdf", + hh = "text/c", + hlp = "application/winhlp", + hpgl = "application/hp-hpgl", + hpid = "application/hp-hpid", + hpp = "text/c", + hps = "application/hp-hps", + hqx = "application/mac-binhex40", + htke = "application/kenameaapp", + htm = "text/html", + html = "text/html", + hvd = "application/yamaha.hv-dic", + hvp = "application/yamaha.hv-voice", + hvs = "application/yamaha.hv-script", + i2g = "application/intergeo", + icc = "application/iccprofile", + ice = "conference/cooltalk", + icm = "application/iccprofile", + ico = "image/icon", + ics = "text/calendar", + ief = "image/ief", + ifb = "text/calendar", + ifm = "application/shana.informed.formdata", + iges = "model/iges", + igl = "application/igloader", + igm = "application/insors.igm", + igs = "model/iges", + igx = "application/micrografx.igx", + iif = "application/shana.informed.interchange", + imp = "application/accpac.simply.imp", + ims = "application/ms-ims", + ini = "text/plain", + ink = "application/inkml+xml", + inkml = "application/inkml+xml", + install = "application/install-instructions", + iota = "application/astraea-software.iota", + ipfix = "application/ipfix", + ipk = "application/shana.informed.package", + irm = "application/ibm.rights-management", + irp = "application/irepository.package+xml", + iso = "application/iso9660-image", + itp = "application/shana.informed.formtemplate", + ivp = "application/immervision-ivp", + ivu = "application/immervision-ivu", + jad = "text/sun.j2me.app-descriptor", + jam = "application/jam", + jar = "application/java-archive", + java = "text/java-source", + jisp = "application/jisp", + jlt = "application/hp-jlyt", + jnlp = "application/java-jnlp-file", + joda = "application/joost.joda-archive", + jpe = "image/jpeg", + jpeg = "image/jpeg", + jpg = "image/jpeg", + jpgm = "video/jpm", + jpgv = "video/jpeg", + jpm = "video/jpm", + js = "text/javascript", + json = "application/json", + jsonc = "application/json", + jsonml = "application/jsonml+json", + jsx = "text/jsx", + jxl = "image/jxl", + kar = "audio/midi", + karbon = "application/kde.karbon", + kdl = "text/kdl", + kfo = "application/kde.kformula", + kia = "application/kidspiration", + kml = "application/google-earth.kml+xml", + kmz = "application/google-earth.kmz", + kne = "application/kinar", + knp = "application/kinar", + kon = "application/kde.kontour", + kpr = "application/kde.kpresenter", + kpt = "application/kde.kpresenter", + kpxx = "application/ds-keypoint", + ksp = "application/kde.kspread", + ktr = "application/kahootz", + ktx = "image/ktx", + ktz = "application/kahootz", + kwd = "application/kde.kword", + kwt = "application/kde.kword", + lasxml = "application/las.las+xml", + latex = "application/latex", + lbd = "application/llamagraphics.life-balance.desktop", + lbe = "application/llamagraphics.life-balance.exchange+xml", + les = "application/hhe.lesson-player", + lha = "application/lzh-compressed", + link66 = "application/route66.link66+xml", + list = "text/plain", + list3820 = "application/ibm.modcap", + listafp = "application/ibm.modcap", + lnk = "application/ms-shortcut", + log = "text/plain", + lostxml = "application/lost+xml", + lrf = "application/octet-stream", + lrm = "application/ms-lrm", + ltf = "application/frogans.ltf", + lua = "text/lua", + lvp = "audio/lucent.voice", + lwp = "application/lotus-wordpro", + lzh = "application/lzh-compressed", + m13 = "application/msmediaview", + m14 = "application/msmediaview", + m1v = "video/mpeg", + m21 = "application/mp21", + m2a = "audio/mpeg", + m2t = "video/mp2t", + m2ts = "video/mp2t", + m2v = "video/mpeg", + m3a = "audio/mpeg", + m3u = "audio/mpegurl", + m3u8 = "application/apple.mpegurl", + m4a = "audio/mp4", + m4u = "video/mpegurl", + m4v = "video/m4v", + ma = "application/mathematica", + mads = "application/mads+xml", + mag = "application/ecowin.chart", + maker = "application/framemaker", + man = "text/troff", + mar = "application/octet-stream", + mathml = "application/mathml+xml", + mb = "application/mathematica", + mbk = "application/mobius.mbk", + mbox = "application/mbox", + mc1 = "application/medcalcdata", + mcd = "application/mcd", + mcurl = "text/curl.mcurl", + md = "text/markdown", + mdb = "application/msaccess", + mdi = "image/ms-modi", + me = "text/troff", + mesh = "model/mesh", + meta4 = "application/metalink4+xml", + metalink = "application/metalink+xml", + mets = "application/mets+xml", + mfm = "application/mfmp", + mft = "application/rpki-manifest", + mgp = "application/osgeo.mapguide.package", + mgz = "application/proteus.magazine", + mid = "audio/midi", + midi = "audio/midi", + mie = "application/mie", + mif = "application/mif", + mime = "message/rfc822", + mj2 = "video/mj2", + mjp2 = "video/mj2", + mjs = "text/javascript", + mk3d = "video/matroska", + mka = "audio/matroska", + mks = "video/matroska", + mkv = "video/matroska", + mlp = "application/dolby.mlp", + mmd = "application/chipnuts.karaoke-mmd", + mmf = "application/smaf", + mmr = "image/fujixerox.edmics-mmr", + mng = "video/mng", + mny = "application/msmoney", + mobi = "application/mobipocket-ebook", + mods = "application/mods+xml", + mov = "video/quicktime", + movie = "video/sgi-movie", + mp2 = "audio/mpeg", + mp21 = "application/mp21", + mp2a = "audio/mpeg", + mp3 = "audio/mpeg", + mp4 = "video/mp4", + mp4a = "audio/mp4", + mp4s = "application/mp4", + mp4v = "video/mp4", + mpc = "application/mophun.certificate", + mpe = "video/mpeg", + mpeg = "video/mpeg", + mpg = "video/mpeg", + mpg4 = "video/mp4", + mpga = "audio/mpeg", + mpkg = "application/apple.installer+xml", + mpm = "application/blueice.multipass", + mpn = "application/mophun.application", + mpp = "application/ms-project", + mpt = "application/ms-project", + mpy = "application/ibm.minipay", + mqy = "application/mobius.mqy", + mrc = "application/marc", + mrcx = "application/marcxml+xml", + ms = "text/troff", + mscml = "application/mediaservercontrol+xml", + mseed = "application/fdsn.mseed", + mseq = "application/mseq", + msf = "application/epson.msf", + msh = "model/mesh", + msi = "application/msdownload", + msl = "application/mobius.msl", + msty = "application/muvee.style", + mts = "video/mp2t", + mus = "application/musician", + musicxml = "application/recordare.musicxml+xml", + mvb = "application/msmediaview", + mwf = "application/mfer", + mxf = "application/mxf", + mxl = "application/recordare.musicxml", + mxml = "application/xv+xml", + mxs = "application/triscape.mxs", + mxu = "video/mpegurl", + n3 = "text/n3", + nb = "application/mathematica", + nbp = "application/wolfram.player", + nc = "application/netcdf", + ncx = "application/dtbncx+xml", + nfo = "text/nfo", + ngdat = "application/nokia.n-gage.data", + nitf = "application/nitf", + nix = "text/nix", + nlu = "application/neurolanguage.nlu", + nml = "application/enliven", + nnd = "application/noblenet-directory", + nns = "application/noblenet-sealer", + nnw = "application/noblenet-web", + npx = "image/net-fpx", + nsc = "application/conference", + nsf = "application/lotus-notes", + ntf = "application/nitf", + nu = "text/nu", + nzb = "application/nzb", + oa2 = "application/fujitsu.oasys2", + oa3 = "application/fujitsu.oasys3", + oas = "application/fujitsu.oasys", + obd = "application/msbinder", + obj = "application/tgif", + oda = "application/oda", + odb = "application/oasis.opendocument.database", + odc = "application/oasis.opendocument.chart", + odf = "application/oasis.opendocument.formula", + odft = "application/oasis.opendocument.formula-template", + odg = "application/oasis.opendocument.graphics", + odi = "application/oasis.opendocument.image", + odm = "application/oasis.opendocument.text-master", + odp = "application/oasis.opendocument.presentation", + ods = "application/oasis.opendocument.spreadsheet", + odt = "application/oasis.opendocument.text", + oga = "audio/ogg", + ogg = "audio/ogg", + ogv = "video/ogg", + ogx = "application/ogg", + omdoc = "application/omdoc+xml", + onepkg = "application/onenote", + onetmp = "application/onenote", + onetoc = "application/onenote", + onetoc2 = "application/onenote", + opf = "application/oebps-package+xml", + opml = "text/opml", + oprc = "application/palm", + opus = "audio/ogg", + org = "application/lotus-organizer", + osf = "application/yamaha.openscoreformat", + osfpvg = "application/yamaha.openscoreformat.osfpvg+xml", + otc = "application/oasis.opendocument.chart-template", + otf = "font/otf", + otg = "application/oasis.opendocument.graphics-template", + oth = "application/oasis.opendocument.text-web", + oti = "application/oasis.opendocument.image-template", + otp = "application/oasis.opendocument.presentation-template", + ots = "application/oasis.opendocument.spreadsheet-template", + ott = "application/oasis.opendocument.text-template", + oxps = "application/oxps", + oxt = "application/openofficeorg.extension", + p = "text/pascal", + p10 = "application/pkcs10", + p12 = "application/pkcs12", + p7b = "application/pkcs7-certificates", + p7c = "application/pkcs7-mime", + p7m = "application/pkcs7-mime", + p7r = "application/pkcs7-certreqresp", + p7s = "application/pkcs7-signature", + p8 = "application/pkcs8", + pas = "text/pascal", + patch = "text/diff", + paw = "application/pawaafile", + pbd = "application/powerbuilder6", + pbm = "image/portable-bitmap", + pcap = "application/tcpdump.pcap", + pcf = "application/font-pcf", + pcl = "application/hp-pcl", + pclxl = "application/hp-pclxl", + pct = "image/pict", + pcurl = "application/curl.pcurl", + pcx = "image/pcx", + pdb = "application/palm", + pdf = "application/pdf", + pfa = "application/font-type1", + pfb = "application/font-type1", + pfm = "application/font-type1", + pfr = "application/font-tdpfr", + pfx = "application/pkcs12", + pgm = "image/portable-graymap", + pgn = "application/chess-pgn", + pgp = "application/pgp-encrypted", + php = "text/php", + pic = "image/pict", + pkg = "application/octet-stream", + pki = "application/pkixcmp", + pkipath = "application/pkix-pkipath", + plb = "application/3gpp.pic-bw-large", + plc = "application/mobius.plc", + plf = "application/pocketlearn", + pls = "application/pls+xml", + pml = "application/ctc-posml", + png = "image/png", + pnm = "image/portable-anymap", + portpkg = "application/macports.portpkg", + pot = "application/ms-powerpoint", + potm = "application/ms-powerpoint.template.macroenabled.12", + potx = "application/openxmlformats-officedocument.presentationml.template", + ppam = "application/ms-powerpoint.addin.macroenabled.12", + ppd = "application/cups-ppd", + ppm = "image/portable-pixmap", + pps = "application/ms-powerpoint", + ppsm = "application/ms-powerpoint.slideshow.macroenabled.12", + ppsx = "application/openxmlformats-officedocument.presentationml.slideshow", + ppt = "application/ms-powerpoint", + pptm = "application/ms-powerpoint.presentation.macroenabled.12", + pptx = "application/openxmlformats-officedocument.presentationml.presentation", + pqa = "application/palm", + prc = "application/mobipocket-ebook", + pre = "application/lotus-freelance", + prf = "application/pics-rules", + ps = "application/postscript", + psb = "application/3gpp.pic-bw-small", + psd = "image/adobe.photoshop", + psf = "application/font-linux-psf", + pskcxml = "application/pskc+xml", + ptid = "application/pvi.ptid1", + pub = "application/mspublisher", + pvb = "application/3gpp.pic-bw-var", + pwn = "application/3m.post-it-notes", + py = "text/python", + pya = "audio/ms-playready.media.pya", + pyv = "video/ms-playready.media.pyv", + qam = "application/epson.quickanime", + qbo = "application/intu.qbo", + qfx = "application/intu.qfx", + qml = "text/qml", + qps = "application/publishare-delta-tree", + qt = "video/quicktime", + qwd = "application/quark.quarkxpress", + qwt = "application/quark.quarkxpress", + qxb = "application/quark.quarkxpress", + qxd = "application/quark.quarkxpress", + qxl = "application/quark.quarkxpress", + qxt = "application/quark.quarkxpress", + r = "text/r", + ra = "audio/pn-realaudio", + ram = "audio/pn-realaudio", + rar = "application/rar", + ras = "image/cmu-raster", + rb = "text/nu", + rcprofile = "application/ipunplugged.rcprofile", + rdf = "application/rdf+xml", + rdz = "application/data-vision.rdz", + rep = "application/businessobjects", + res = "application/dtbresource+xml", + rgb = "image/rgb", + rif = "application/reginfo+xml", + rip = "audio/rip", + ris = "application/research-info-systems", + rl = "application/resource-lists+xml", + rlc = "image/fujixerox.edmics-rlc", + rld = "application/resource-lists-diff+xml", + rm = "application/rn-realmedia", + rmi = "audio/midi", + rmp = "audio/pn-realaudio-plugin", + rms = "application/jcp.javame.midlet-rms", + rmvb = "application/rn-realmedia-vbr", + rnc = "application/relax-ng-compact-syntax", + roa = "application/rpki-roa", + roff = "text/troff", + rp9 = "application/cloanto.rp9", + rpm = "application/rpm", + rpss = "application/nokia.radio-presets", + rpst = "application/nokia.radio-preset", + rq = "application/sparql-query", + rs = "text/rust", + rsd = "application/rsd+xml", + rss = "application/rss+xml", + rtf = "application/rtf", + rtx = "text/richtext", + s = "text/asm", + s3m = "audio/s3m", + saf = "application/yamaha.smaf-audio", + sbml = "application/sbml+xml", + sc = "application/ibm.secure-container", + scd = "application/msschedule", + scm = "application/lotus-screencam", + scq = "application/scvp-cv-request", + scs = "application/scvp-cv-response", + scss = "text/scss", + scurl = "text/curl.scurl", + sda = "application/stardivision.draw", + sdc = "application/stardivision.calc", + sdd = "application/stardivision.impress", + sdkd = "application/solent.sdkm+xml", + sdkm = "application/solent.sdkm+xml", + sdp = "application/sdp", + sdw = "application/stardivision.writer", + see = "application/seemail", + seed = "application/fdsn.seed", + sema = "application/sema", + semd = "application/semd", + semf = "application/semf", + ser = "application/java-serialized-object", + setpay = "application/set-payment-initiation", + setreg = "application/set-registration-initiation", + sfs = "application/spotfire.sfs", + sfv = "text/sfv", + sgi = "image/sgi", + sgl = "application/stardivision.writer-global", + sgm = "text/sgml", + sgml = "text/sgml", + sh = "text/shellscript", + shar = "application/shar", + shf = "application/shf+xml", + sid = "image/mrsid-image", + sig = "application/pgp-signature", + sil = "audio/silk", + silo = "model/mesh", + sis = "application/symbian.install", + sisx = "application/symbian.install", + sit = "application/stuffit", + sitx = "application/stuffitx", + skd = "application/koan", + skm = "application/koan", + skp = "application/koan", + skt = "application/koan", + sldm = "application/ms-powerpoint.slide.macroenabled.12", + sldx = "application/openxmlformats-officedocument.presentationml.slide", + slt = "application/epson.salt", + sm = "application/stepmania.stepchart", + smf = "application/stardivision.math", + smi = "application/smil+xml", + smil = "application/smil+xml", + smv = "video/smv", + smzip = "application/stepmania.package", + snd = "audio/basic", + snf = "application/font-snf", + so = "application/octet-stream", + spc = "application/pkcs7-certificates", + spf = "application/yamaha.smaf-phrase", + spl = "application/futuresplash", + spot = "text/in3d.spot", + spp = "application/scvp-vp-response", + spq = "application/scvp-vp-request", + spx = "audio/ogg", + sql = "application/sql", + src = "application/wais-source", + srt = "application/subrip", + sru = "application/sru+xml", + srx = "application/sparql-results+xml", + ssdl = "application/ssdl+xml", + sse = "application/kodak-descriptor", + ssf = "application/epson.ssf", + ssml = "application/ssml+xml", + st = "application/sailingtracker.track", + stc = "application/sun.xml.calc.template", + std = "application/sun.xml.draw.template", + stf = "application/wt.stf", + sti = "application/sun.xml.impress.template", + stk = "application/hyperstudio", + stl = "application/ms-pki.stl", + str = "application/pg.format", + stw = "application/sun.xml.writer.template", + sub = "text/dvb.subtitle", + sus = "application/sus-calendar", + susp = "application/sus-calendar", + sv4cpio = "application/sv4cpio", + sv4crc = "application/sv4crc", + svc = "application/dvb.service", + svd = "application/svd", + svg = "image/svg+xml", + svgz = "image/svg+xml", + swa = "application/director", + swf = "application/shockwave-flash", + swi = "application/aristanetworks.swi", + sxc = "application/sun.xml.calc", + sxd = "application/sun.xml.draw", + sxg = "application/sun.xml.writer.global", + sxi = "application/sun.xml.impress", + sxm = "application/sun.xml.math", + sxw = "application/sun.xml.writer", + t = "text/troff", + t3 = "application/t3vm-image", + taglet = "application/mynfc", + tao = "application/tao.intent-module-archive", + tar = "application/tar", + tcap = "application/3gpp2.tcap", + tcl = "application/tcl", + teacher = "application/smart.teacher", + tei = "application/tei+xml", + teicorpus = "application/tei+xml", + tex = "application/tex", + texi = "application/texinfo", + texinfo = "application/texinfo", + text = "text/plain", + tf = "text/hcl", + tfi = "application/thraud+xml", + tfm = "application/tex-tfm", + tfrc = "text/hcl", + tfstate = "application/json", + tfvars = "text/hcl", + tga = "image/tga", + theme = "application/plain", + thmx = "application/ms-officetheme", + tif = "image/tiff", + tiff = "image/tiff", + tmo = "application/tmobile-livetv", + tmTheme = "text/plain", + toml = "text/toml", + torrent = "application/bittorrent", + tpl = "application/groove-tool-template", + tpt = "application/trid.tpt", + tr = "text/troff", + tra = "application/trueapp", + trm = "application/msterminal", + ts = "text/typescript", + tsd = "application/timestamped-data", + tsv = "text/tab-separated-values", + tsx = "text/tsx", + ttc = "font/collection", + ttf = "font/ttf", + ttl = "text/turtle", + twd = "application/simtech-mindmapper", + twds = "application/simtech-mindmapper", + txd = "application/genomatix.tuxedo", + txf = "application/mobius.txf", + txt = "text/plain", + u32 = "application/authorware-bin", + udeb = "application/debian-package", + ufd = "application/ufdl", + ufdl = "application/ufdl", + ulx = "application/glulx", + umj = "application/umajin", + unityweb = "application/unity", + uoml = "application/uoml+xml", + uri = "text/uri-list", + uris = "text/uri-list", + urls = "text/uri-list", + ustar = "application/ustar", + utz = "application/uiq.theme", + uu = "text/uuencode", + uva = "audio/dece.audio", + uvd = "application/dece.data", + uvf = "application/dece.data", + uvg = "image/dece.graphic", + uvh = "video/dece.hd", + uvi = "image/dece.graphic", + uvm = "video/dece.mobile", + uvp = "video/dece.pd", + uvs = "video/dece.sd", + uvt = "application/dece.ttml+xml", + uvu = "video/uvvu.mp4", + uvv = "video/dece.video", + uvva = "audio/dece.audio", + uvvd = "application/dece.data", + uvvf = "application/dece.data", + uvvg = "image/dece.graphic", + uvvh = "video/dece.hd", + uvvi = "image/dece.graphic", + uvvm = "video/dece.mobile", + uvvp = "video/dece.pd", + uvvs = "video/dece.sd", + uvvt = "application/dece.ttml+xml", + uvvu = "video/uvvu.mp4", + uvvv = "video/dece.video", + uvvx = "application/dece.unspecified", + uvvz = "application/dece.zip", + uvx = "application/dece.unspecified", + uvz = "application/dece.zip", + vcard = "text/vcard", + vcd = "application/cdlink", + vcf = "text/vcard", + vcg = "application/groove-vcard", + vcs = "text/vcalendar", + vcx = "application/vcx", + vis = "application/visionary", + viv = "video/vivo", + vob = "video/ms-vob", + vor = "application/stardivision.writer", + vox = "application/authorware-bin", + vrml = "model/vrml", + vsd = "application/visio", + vsf = "application/vsf", + vss = "application/visio", + vst = "application/visio", + vsw = "application/visio", + vtu = "model/vtu", + vxml = "application/voicexml+xml", + w3d = "application/director", + wad = "application/doom", + wasm = "application/wasm", + wav = "audio/wav", + wax = "audio/ms-wax", + wbmp = "image/wap.wbmp", + wbs = "application/criticaltools.wbs+xml", + wbxml = "application/wap.wbxml", + wcm = "application/ms-works", + wdb = "application/ms-works", + wdp = "image/ms-photo", + weba = "audio/webm", + webm = "video/webm", + webp = "image/webp", + wg = "application/pmi.widget", + wgt = "application/widget", + wks = "application/ms-works", + wm = "video/ms-wm", + wma = "audio/ms-wma", + wmd = "application/ms-wmd", + wmf = "application/msmetafile", + wml = "text/wap.wml", + wmlc = "application/wap.wmlc", + wmls = "text/wap.wmlscript", + wmlsc = "application/wap.wmlscriptc", + wmv = "video/ms-wmv", + wmx = "video/ms-wmx", + wmz = "application/ms-wmz", + woff = "font/woff", + woff2 = "font/woff2", + wpd = "application/wordperfect", + wpl = "application/ms-wpl", + wps = "application/ms-works", + wqd = "application/wqd", + wri = "application/mswrite", + wrl = "model/vrml", + wsdl = "application/wsdl+xml", + wspolicy = "application/wspolicy+xml", + wtb = "application/webturbo", + wvx = "video/ms-wvx", + x32 = "application/authorware-bin", + x3d = "model/x3d+xml", + x3db = "model/x3d+binary", + x3dbz = "model/x3d+binary", + x3dv = "model/x3d+vrml", + x3dvz = "model/x3d+vrml", + x3dz = "model/x3d+xml", + xaml = "application/xaml+xml", + xap = "application/silverlight-app", + xar = "application/xara", + xbap = "application/ms-xbap", + xbd = "application/fujixerox.docuworks.binder", + xbm = "image/xbitmap", + xdf = "application/xcap-diff+xml", + xdm = "application/syncml.dm+xml", + xdp = "application/adobe.xdp+xml", + xdssc = "application/dssc+xml", + xdw = "application/fujixerox.docuworks", + xenc = "application/xenc+xml", + xer = "application/patch-ops-error+xml", + xfdf = "application/adobe.xfdf", + xfdl = "application/xfdl", + xht = "application/xhtml+xml", + xhtml = "application/xhtml+xml", + xhvml = "application/xv+xml", + xif = "image/xiff", + xla = "application/ms-excel", + xlam = "application/ms-excel.addin.macroenabled.12", + xlc = "application/ms-excel", + xlf = "application/xliff+xml", + xlm = "application/ms-excel", + xls = "application/ms-excel", + xlsb = "application/ms-excel.sheet.binary.macroenabled.12", + xlsm = "application/ms-excel.sheet.macroenabled.12", + xlsx = "application/openxmlformats-officedocument.spreadsheetml.sheet", + xlt = "application/ms-excel", + xltm = "application/ms-excel.template.macroenabled.12", + xltx = "application/openxmlformats-officedocument.spreadsheetml.template", + xlw = "application/ms-excel", + xm = "audio/xm", + xml = "application/xml", + xo = "application/olpc-sugar", + xop = "application/xop+xml", + xpak = "application/gentoo.xpak", + xpi = "application/xpinstall", + xpl = "application/xproc+xml", + xpm = "image/xpixmap", + xpr = "application/is-xpr", + xps = "application/ms-xpsdocument", + xpw = "application/intercon.formnet", + xpx = "application/intercon.formnet", + xsl = "application/xml", + xslt = "application/xslt+xml", + xsm = "application/syncml+xml", + xspf = "application/xspf+xml", + xul = "application/mozilla.xul+xml", + xvm = "application/xv+xml", + xvml = "application/xv+xml", + xwd = "image/xwindowdump", + xyz = "chemical/xyz", + xz = "application/xz", + yaml = "text/yaml", + yang = "application/yang", + yin = "application/yin+xml", + yml = "text/yaml", + z1 = "application/zmachine", + z2 = "application/zmachine", + z3 = "application/zmachine", + z4 = "application/zmachine", + z5 = "application/zmachine", + z6 = "application/zmachine", + z7 = "application/zmachine", + z8 = "application/zmachine", + zaz = "application/zzazz.deck+xml", + zip = "application/zip", + zir = "application/zul", + zirz = "application/zul", + zmm = "application/handheld-entertainment+xml", + zsh = "text/shellscript", +} + +local options = ya.sync(function(st) + return { + with_files = st.with_files, + with_exts = st.with_exts, + fallback_file1 = st.fallback_file1, + } +end) + +local M = {} + +function M:setup(opts) + opts = opts or {} + + self.with_files = opts.with_files + self.with_exts = opts.with_exts + self.fallback_file1 = opts.fallback_file1 +end + +function M:fetch(job) + local opts = options() + local merged_files = ya.dict_merge(FILES, opts.with_files or {}) + local merged_exts = ya.dict_merge(EXTS, opts.with_exts or {}) + + local updates, unknown, state = {}, {}, {} + for i, file in ipairs(job.files) do + if file.cha.is_dummy then + state[i] = false + goto continue + end + + local mime + if file.cha.len == 0 then + mime = "inode/empty" + else + mime = merged_files[(file.url.name or ""):lower()] + mime = mime or merged_exts[(file.url.ext or ""):lower()] + end + + if mime then + updates[tostring(file.url)], state[i] = mime, true + elseif opts.fallback_file1 then + unknown[#unknown + 1] = file + else + updates[tostring(file.url)], state[i] = "application/octet-stream", true + end + ::continue:: + end + + if next(updates) then + ya.emit("update_mimes", { updates = updates }) + end + + if #unknown > 0 then + return self.fallback_builtin(job, unknown, state) + end + + return state +end + +function M.fallback_builtin(job, unknown, state) + local indices = {} + for i, f in ipairs(job.files) do + indices[f:hash()] = i + end + + local result = require("mime.local"):fetch(ya.dict_merge(job, { files = unknown })) + for i, f in ipairs(unknown) do + if type(result) == "table" then + state[indices[f:hash()]] = result[i] + else + state[indices[f:hash()]] = result + end + end + return state +end + +return M diff --git a/yazi_1/.config/yazi/plugins/mime-ext.yazi/remote.lua b/yazi_1/.config/yazi/plugins/mime-ext.yazi/remote.lua new file mode 100644 index 0000000..7626434 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mime-ext.yazi/remote.lua @@ -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 diff --git a/yazi/.config/yazi/plugins/rsync.yazi/LICENSE b/yazi_1/.config/yazi/plugins/mount.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/rsync.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/mount.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/mount.yazi/README.md b/yazi_1/.config/yazi/plugins/mount.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/mount.yazi/README.md rename to yazi_1/.config/yazi/plugins/mount.yazi/README.md diff --git a/yazi_1/.config/yazi/plugins/mount.yazi/cross.lua b/yazi_1/.config/yazi/plugins/mount.yazi/cross.lua new file mode 100644 index 0000000..7d329eb --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mount.yazi/cross.lua @@ -0,0 +1,148 @@ +local M = {} + +--- @param type "mount"|"unmount"|"eject" +--- @param partition table +function M.operate(type, partition) + if not partition then + return + elseif not partition.sub then + return -- TODO: mount/unmount main disk + end + + local cmd, output, err + if ya.target_os() == "macos" then + cmd, output, err = "diskutil", M.diskutil(type, partition.src) + elseif ya.target_os() == "linux" then + if type == "eject" and partition.src:match("^/dev/sr%d+") then + M.udisksctl("unmount", partition.src) + cmd, output, err = "eject", M.eject(partition.src) + elseif type == "eject" then + M.udisksctl("unmount", partition.src) + cmd, output, err = "udisksctl", M.udisksctl("power-off", partition.src) + else + cmd, output, err = "udisksctl", M.udisksctl(type, partition.src) + end + end + + if not cmd then + M.fail("mount.yazi is not currently supported on your platform") + elseif not output then + M.fail("Failed to spawn `%s`: %s", cmd, err) + elseif not output.status.success then + M.fail("Failed to %s `%s`: %s", type, partition.src, output.stderr) + end +end + +--- @param type "mount"|"unmount"|"eject" +--- @param src string +--- @return Output? output +--- @return Error? err +function M.diskutil(type, src) return Command("diskutil"):arg({ type, src }):output() end + +--- @param type "mount"|"unmount"|"power-off" +--- @param src string +--- @return Output? output +--- @return Error? err +function M.udisksctl(type, src) + local args = { type, "-b", src, "--no-user-interaction" } + local output, err = Command("udisksctl"):arg(args):output() + + if not output or err then + return nil, err + elseif output.stderr:find("org.freedesktop.UDisks2.Error.NotAuthorizedCanObtain", 1, true) then + local tx, rx = table.unpack(require(".main").permit) + tx:send(true) + ya.emit("which:dismiss", {}) + output, err = M.udisks_dbus(type, src) + if not output then + output, err = M.udisksctl_interactive(type, src) + end + rx:recv() + return output, err + else + return output + end +end + +--- @param type "mount"|"unmount"|"power-off" +--- @param src string +--- @return Output? output +--- @return Error? err +function M.udisks_dbus(type, src) + local obj, err = M.gdbus_resolve( + "/org/freedesktop/UDisks2/Manager", + "org.freedesktop.UDisks2.Manager.ResolveDevice", + string.format("{'path': <%q>}", src), + "{}" + ) + if not obj then + return nil, err + end + + local method, options = "", "{}" + if type == "mount" then + local user = ya.user_name() + if not user then + return nil, Err("Cannot determine the current user") + end + method = "org.freedesktop.UDisks2.Filesystem.Mount" + options = string.format("{'as-user': <%q>}", user) + elseif type == "unmount" then + method = "org.freedesktop.UDisks2.Filesystem.Unmount" + elseif type == "power-off" then + obj, err = M.gdbus_resolve(obj, "org.freedesktop.UDisks2.Block.GetDrive") + if not obj then + return nil, err + end + method = "org.freedesktop.UDisks2.Drive.PowerOff" + else + return nil, Err("Unsupported UDisks operation: %s", type) + end + + return require(".sudo").run_with_sudo("gdbus", M.gdbus_args(obj, method, options)) +end + +--- @param type "mount"|"unmount"|"power-off" +--- @param src string +--- @return Output? output +--- @return Error? err +function M.udisksctl_interactive(type, src) + local permit = ui.hide() + local output, err = Command("udisksctl"):arg({ type, "-b", src }):stdin(Command.INHERIT):output() + permit:drop() + return output, err +end + +--- @param src string +--- @return Output? output +--- @return Error? err +function M.eject(src) return Command("eject"):arg({ "--traytoggle", src }):output() end + +function M.fail(...) ya.notify { title = "Mount", content = string.format(...), timeout = 10, level = "error" } end + +--- @param obj string +--- @param method string +--- @param ... string +--- @return string? obj +--- @return Error? err +function M.gdbus_resolve(obj, method, ...) + local output, err = Command("gdbus"):arg(M.gdbus_args(obj, method, ...)):output() + if not output or err then + return nil, err + elseif not output.status.success then + return nil, Err("%s", output.stderr) + else + local result = output.stdout:match("objectpath '([^']+)'") + return result, result and nil or Err("Unexpected `gdbus` response: %s", output.stdout) + end +end + +--- @param obj string +--- @param method string +--- @param ... string +--- @return string[] args +function M.gdbus_args(obj, method, ...) + return { "call", "--system", "--dest", "org.freedesktop.UDisks2", "--object-path", obj, "--method", method, ... } +end + +return M diff --git a/yazi/.config/yazi/plugins/mount.yazi/main.lua b/yazi_1/.config/yazi/plugins/mount.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/mount.yazi/main.lua rename to yazi_1/.config/yazi/plugins/mount.yazi/main.lua diff --git a/yazi_1/.config/yazi/plugins/mount.yazi/sudo.lua b/yazi_1/.config/yazi/plugins/mount.yazi/sudo.lua new file mode 100644 index 0000000..3a62fcc --- /dev/null +++ b/yazi_1/.config/yazi/plugins/mount.yazi/sudo.lua @@ -0,0 +1,65 @@ +local M = {} + +--- Verify if `sudo` is already authenticated +--- @return boolean +--- @return Error? +function M.sudo_already() + local status, err = Command("sudo"):arg({ "--validate", "--non-interactive" }):status() + return status and status.success or false, err +end + +--- Authenticate `sudo` through Yazi's input component +--- @param program string +--- @return boolean authenticated +--- @return Error? err +function M.authenticate(program) + if M.sudo_already() then + return true + end + + local value, event = ya.input { + pos = { "top-center", y = 3, w = 40 }, + title = string.format("Password for `sudo %s`:", program), + obscure = true, + } + if not value or event ~= 1 then + return false, Err("Sudo password input cancelled") + end + + local child, err = Command("sudo") + :arg({ "--stdin", "--validate" }) + :stdin(Command.PIPED) + :stdout(Command.PIPED) + :stderr(Command.PIPED) + :spawn() + if not child or err then + return false, err + end + + child:write_all(value .. "\n") + child:flush() + local output, err = child:wait_with_output() + if not output or err then + return false, err + elseif not output.status.success then + return false, Err("Incorrect sudo password") + else + return true + end +end + +--- Run a program with `sudo` privilege +--- @param program string +--- @param args table +--- @return Output? output +--- @return Error? err +function M.run_with_sudo(program, args) + local authenticated, err = M.authenticate(program) + if authenticated then + return Command("sudo"):arg({ "--non-interactive", "--", program }):arg(args):output() + else + return nil, err + end +end + +return M diff --git a/yazi/.config/yazi/plugins/omp.yazi b/yazi_1/.config/yazi/plugins/omp.yazi similarity index 100% rename from yazi/.config/yazi/plugins/omp.yazi rename to yazi_1/.config/yazi/plugins/omp.yazi diff --git a/yazi/.config/yazi/plugins/open-git-remote.yazi/LICENSE b/yazi_1/.config/yazi/plugins/open-git-remote.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/open-git-remote.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/open-git-remote.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/open-git-remote.yazi/README.md b/yazi_1/.config/yazi/plugins/open-git-remote.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/open-git-remote.yazi/README.md rename to yazi_1/.config/yazi/plugins/open-git-remote.yazi/README.md diff --git a/yazi/.config/yazi/plugins/open-git-remote.yazi/main.lua b/yazi_1/.config/yazi/plugins/open-git-remote.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/open-git-remote.yazi/main.lua rename to yazi_1/.config/yazi/plugins/open-git-remote.yazi/main.lua diff --git a/yazi_1/.config/yazi/plugins/open-with-cmd.yazi/LICENSE b/yazi_1/.config/yazi/plugins/open-with-cmd.yazi/LICENSE new file mode 100644 index 0000000..0399f1c --- /dev/null +++ b/yazi_1/.config/yazi/plugins/open-with-cmd.yazi/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024 Lauri Niskanen + +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. diff --git a/yazi/.config/yazi/plugins/open-with-cmd.yazi/README.md b/yazi_1/.config/yazi/plugins/open-with-cmd.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/open-with-cmd.yazi/README.md rename to yazi_1/.config/yazi/plugins/open-with-cmd.yazi/README.md diff --git a/yazi/.config/yazi/plugins/open-with-cmd.yazi/main.lua b/yazi_1/.config/yazi/plugins/open-with-cmd.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/open-with-cmd.yazi/main.lua rename to yazi_1/.config/yazi/plugins/open-with-cmd.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/ouch.yazi/LICENSE b/yazi_1/.config/yazi/plugins/ouch.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/ouch.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/ouch.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/ouch.yazi/README.md b/yazi_1/.config/yazi/plugins/ouch.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/ouch.yazi/README.md rename to yazi_1/.config/yazi/plugins/ouch.yazi/README.md diff --git a/yazi/.config/yazi/plugins/ouch.yazi/main.lua b/yazi_1/.config/yazi/plugins/ouch.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/ouch.yazi/main.lua rename to yazi_1/.config/yazi/plugins/ouch.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/toggle-pane.yazi/LICENSE b/yazi_1/.config/yazi/plugins/piper.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/toggle-pane.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/piper.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/piper.yazi/README.md b/yazi_1/.config/yazi/plugins/piper.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/piper.yazi/README.md rename to yazi_1/.config/yazi/plugins/piper.yazi/README.md diff --git a/yazi/.config/yazi/plugins/piper.yazi/main.lua b/yazi_1/.config/yazi/plugins/piper.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/piper.yazi/main.lua rename to yazi_1/.config/yazi/plugins/piper.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/projects.yazi/LICENSE b/yazi_1/.config/yazi/plugins/projects.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/projects.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/projects.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/projects.yazi/README.md b/yazi_1/.config/yazi/plugins/projects.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/projects.yazi/README.md rename to yazi_1/.config/yazi/plugins/projects.yazi/README.md diff --git a/yazi/.config/yazi/plugins/projects.yazi/json.lua b/yazi_1/.config/yazi/plugins/projects.yazi/json.lua similarity index 100% rename from yazi/.config/yazi/plugins/projects.yazi/json.lua rename to yazi_1/.config/yazi/plugins/projects.yazi/json.lua diff --git a/yazi/.config/yazi/plugins/projects.yazi/main.lua b/yazi_1/.config/yazi/plugins/projects.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/projects.yazi/main.lua rename to yazi_1/.config/yazi/plugins/projects.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/recycle-bin.yazi/LICENSE b/yazi_1/.config/yazi/plugins/recycle-bin.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/recycle-bin.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/recycle-bin.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/recycle-bin.yazi/README.md b/yazi_1/.config/yazi/plugins/recycle-bin.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/recycle-bin.yazi/README.md rename to yazi_1/.config/yazi/plugins/recycle-bin.yazi/README.md diff --git a/yazi/.config/yazi/plugins/recycle-bin.yazi/main.lua b/yazi_1/.config/yazi/plugins/recycle-bin.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/recycle-bin.yazi/main.lua rename to yazi_1/.config/yazi/plugins/recycle-bin.yazi/main.lua diff --git a/yazi_1/.config/yazi/plugins/rich-preview.yazi/LICENSE b/yazi_1/.config/yazi/plugins/rich-preview.yazi/LICENSE new file mode 100644 index 0000000..ea3f779 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/rich-preview.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Anirudh Gupta + +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. diff --git a/yazi_1/.config/yazi/plugins/rich-preview.yazi/README.md b/yazi_1/.config/yazi/plugins/rich-preview.yazi/README.md new file mode 100644 index 0000000..4557fc1 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/rich-preview.yazi/README.md @@ -0,0 +1,89 @@ +# rich-preview.yazi + +Preview file types using `rich` command in Yazi. This plugin allows preview for various filetypes including - + +- Markdown +- Jupyter notebook +- JSON +- CSV +- RestructuredText + +## Previews/Screenshots + +[rich-preview1.webm](https://github.com/user-attachments/assets/580e36a8-249f-48a8-95fc-8c3d60e6a7d7) + +## Requirements + +- [Yazi](https://github.com/sxyazi/yazi) v25.4.8 or higher. +- [rich-cli](https://github.com/Textualize/rich-cli) v13.7.1 or higher. + +## Installation + +To install this plugin, simply run- + +```bash +ya pkg add AnirudhG07/rich-preview +## For linux and MacOS +git clone https://github.com/AnirudhG07/rich-preview.yazi.git ~/.config/yazi/plugins/rich-preview.yazi + +## For Windows +git clone https://github.com/AnirudhG07/rich-preview.yazi.git %AppData%\yazi\config\plugins\rich-preview.yazi +``` + +## Usages + +The `rich` commands automatically detects if the file is markdown, csv, json, etc. files and accordingly the preview is viewed. + +Add the below to your `yazi.toml` file to allow the respective file to previewed using `rich`. + +```toml +[plugin] + +prepend_previewers = [ + { url = "*.csv", run = "rich-preview"}, # for csv files + { url = "*.md", run = "rich-preview" }, # for markdown (.md) files + { url = "*.rst", run = "rich-preview"}, # for restructured text (.rst) files + { url = "*.ipynb", run = "rich-preview"}, # for jupyter notebooks (.ipynb) + { url = "*.json", run = "rich-preview"}, # for json (.json) files +# { url = "*.lang_type", run = "rich-preview"} # for particular language files eg. .py, .go., .lua, etc. +] +``` + +## Configurations + +If you would like to use `rich` with more configurations, you can go to `init.lua` and edit the arguments in the code with your preferences. You can view the options using `rich --help`. + +```lua +-- init.lua +"-j", +"--left", +"--line-numbers", +"--force-terminal", +"--panel=rounded", +"--guides", +"--max-width" -- to area of preview +``` + +You can add more, remove and choose themes as you wish. You can set styles or Themes(as mentioned in `rich --help`) by `--theme=your_theme` and similarly for style. + +## Notes + +Currently the colors maynot be uniformly present, along with weird lines here and there. This is due to `"--force-terminal"` option. You can disable it if you find it annoying. Work is in progress to possibly fix the issue. + +## Using piper.yazi + +[piper.yazi](https://github.com/yazi-rs/plugins/tree/main/piper.yazi) is a general-purpose previewer - you can pass any shell command to piper and it will use the command's output as the preview content. + +To use `rich` with piper, you can add this in your `yazi.toml` file: + +```toml +[[plugin.prepend_previewers]] +url = "*.md" +run = 'piper -- rich -j --left --panel=rounded --guides --line-numbers --force-terminal "$1"' +``` + +Note you can also add other filetypes as mentioned above in the same format. + +# Explore Yazi + +Yazi is an amazing, blazing fast terminal file manager, with a variety of plugins, flavors and themes. Check them out at [awesome-yazi](https://github.com/AnirudhG07/awesome-yazi) and the official [yazi webpage](https://yazi-rs.github.io/). diff --git a/yazi_1/.config/yazi/plugins/rich-preview.yazi/main.lua b/yazi_1/.config/yazi/plugins/rich-preview.yazi/main.lua new file mode 100644 index 0000000..a75003f --- /dev/null +++ b/yazi_1/.config/yazi/plugins/rich-preview.yazi/main.lua @@ -0,0 +1,56 @@ +local M = {} + +function M:peek(job) + local child = Command("rich") + :arg({ + "-j", + "--left", + "--line-numbers", + "--force-terminal", + "--panel=rounded", + "--guides", + "--max-width", + tostring(job.area.w), + tostring(job.file.url), + }) + :stdout(Command.PIPED) + :stderr(Command.PIPED) + :spawn() + + if not child then + return require("code"):peek(job) + end + + local limit = job.area.h + local i, lines = 0, "" + repeat + local next, event = child:read_line() + if event == 1 then + return require("code"):peek(job) + elseif event ~= 0 then + break + end + + i = i + 1 + if i > job.skip then + lines = lines .. next + end + until i >= job.skip + limit + + child:start_kill() + 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 }) + else + lines = lines:gsub("\t", string.rep(" ", rt.preview.tab_size)) + ya.preview_widget( + job, + ui.Text.parse(lines):area(job.area):wrap(rt.preview.wrap == "yes" and ui.Wrap.YES or ui.Wrap.NO) + ) + end +end + +function M:seek(job) + require("code"):seek(job) +end + +return M diff --git a/yazi/.config/yazi/plugins/vcs-files.yazi/LICENSE b/yazi_1/.config/yazi/plugins/rsync.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/vcs-files.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/rsync.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/rsync.yazi/README.md b/yazi_1/.config/yazi/plugins/rsync.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/rsync.yazi/README.md rename to yazi_1/.config/yazi/plugins/rsync.yazi/README.md diff --git a/yazi/.config/yazi/plugins/rsync.yazi/assets/demo.gif b/yazi_1/.config/yazi/plugins/rsync.yazi/assets/demo.gif similarity index 100% rename from yazi/.config/yazi/plugins/rsync.yazi/assets/demo.gif rename to yazi_1/.config/yazi/plugins/rsync.yazi/assets/demo.gif diff --git a/yazi/.config/yazi/plugins/rsync.yazi/assets/demo.webm b/yazi_1/.config/yazi/plugins/rsync.yazi/assets/demo.webm similarity index 100% rename from yazi/.config/yazi/plugins/rsync.yazi/assets/demo.webm rename to yazi_1/.config/yazi/plugins/rsync.yazi/assets/demo.webm diff --git a/yazi/.config/yazi/plugins/rsync.yazi/main.lua b/yazi_1/.config/yazi/plugins/rsync.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/rsync.yazi/main.lua rename to yazi_1/.config/yazi/plugins/rsync.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/simple-tag.yazi/LICENSE b/yazi_1/.config/yazi/plugins/simple-tag.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/simple-tag.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/simple-tag.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/simple-tag.yazi/README.md b/yazi_1/.config/yazi/plugins/simple-tag.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/simple-tag.yazi/README.md rename to yazi_1/.config/yazi/plugins/simple-tag.yazi/README.md diff --git a/yazi/.config/yazi/plugins/simple-tag.yazi/main.lua b/yazi_1/.config/yazi/plugins/simple-tag.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/simple-tag.yazi/main.lua rename to yazi_1/.config/yazi/plugins/simple-tag.yazi/main.lua diff --git a/yazi_1/.config/yazi/plugins/smart-enter.yazi/LICENSE b/yazi_1/.config/yazi/plugins/smart-enter.yazi/LICENSE new file mode 100644 index 0000000..fb5b1d6 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/smart-enter.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 yazi-rs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/yazi_1/.config/yazi/plugins/smart-enter.yazi/README.md b/yazi_1/.config/yazi/plugins/smart-enter.yazi/README.md new file mode 100644 index 0000000..742f2e1 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/smart-enter.yazi/README.md @@ -0,0 +1,40 @@ +# smart-enter.yazi + +[`Open`][open] files or [`enter`][enter] directories all in one key! + +## Installation + +```sh +ya pkg add yazi-rs/plugins:smart-enter +``` + +## Usage + +Bind your l key to the plugin, in your `~/.config/yazi/keymap.toml`: + +```toml +[[mgr.prepend_keymap]] +on = "l" +run = "plugin smart-enter" +desc = "Enter the child directory, or open the file" +``` + +## Advanced + +By default, `--hovered` is passed to the [`open`][open] command, make the behavior consistent with [`enter`][enter] avoiding accidental triggers, +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`: + +```lua +require("smart-enter"):setup { + open_multi = true, +} +``` + +## License + +This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file. + +[open]: https://yazi-rs.github.io/docs/configuration/keymap/#mgr.open +[enter]: https://yazi-rs.github.io/docs/configuration/keymap/#mgr.enter diff --git a/yazi_1/.config/yazi/plugins/smart-enter.yazi/main.lua b/yazi_1/.config/yazi/plugins/smart-enter.yazi/main.lua new file mode 100644 index 0000000..e9e2ec6 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/smart-enter.yazi/main.lua @@ -0,0 +1,11 @@ +--- @since 25.5.31 +--- @sync entry + +local function setup(self, opts) self.open_multi = opts.open_multi end + +local function entry(self) + local h = cx.active.current.hovered + ya.emit(h and h.cha.is_dir and "enter" or "open", { hovered = not self.open_multi }) +end + +return { entry = entry, setup = setup } diff --git a/yazi/.config/yazi/plugins/smart-paste.yazi/LICENSE b/yazi_1/.config/yazi/plugins/smart-paste.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/smart-paste.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/smart-paste.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/smart-paste.yazi/README.md b/yazi_1/.config/yazi/plugins/smart-paste.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/smart-paste.yazi/README.md rename to yazi_1/.config/yazi/plugins/smart-paste.yazi/README.md diff --git a/yazi/.config/yazi/plugins/smart-paste.yazi/main.lua b/yazi_1/.config/yazi/plugins/smart-paste.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/smart-paste.yazi/main.lua rename to yazi_1/.config/yazi/plugins/smart-paste.yazi/main.lua diff --git a/yazi_1/.config/yazi/plugins/toggle-pane.yazi/LICENSE b/yazi_1/.config/yazi/plugins/toggle-pane.yazi/LICENSE new file mode 100644 index 0000000..fb5b1d6 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/toggle-pane.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 yazi-rs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/yazi/.config/yazi/plugins/toggle-pane.yazi/README.md b/yazi_1/.config/yazi/plugins/toggle-pane.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/toggle-pane.yazi/README.md rename to yazi_1/.config/yazi/plugins/toggle-pane.yazi/README.md diff --git a/yazi/.config/yazi/plugins/toggle-pane.yazi/main.lua b/yazi_1/.config/yazi/plugins/toggle-pane.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/toggle-pane.yazi/main.lua rename to yazi_1/.config/yazi/plugins/toggle-pane.yazi/main.lua diff --git a/yazi_1/.config/yazi/plugins/vcs-files.yazi/LICENSE b/yazi_1/.config/yazi/plugins/vcs-files.yazi/LICENSE new file mode 100644 index 0000000..fb5b1d6 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/vcs-files.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 yazi-rs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/yazi/.config/yazi/plugins/vcs-files.yazi/README.md b/yazi_1/.config/yazi/plugins/vcs-files.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/vcs-files.yazi/README.md rename to yazi_1/.config/yazi/plugins/vcs-files.yazi/README.md diff --git a/yazi/.config/yazi/plugins/vcs-files.yazi/main.lua b/yazi_1/.config/yazi/plugins/vcs-files.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/vcs-files.yazi/main.lua rename to yazi_1/.config/yazi/plugins/vcs-files.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/what-size.yazi/LICENSE b/yazi_1/.config/yazi/plugins/what-size.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/what-size.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/what-size.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/what-size.yazi/README.md b/yazi_1/.config/yazi/plugins/what-size.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/what-size.yazi/README.md rename to yazi_1/.config/yazi/plugins/what-size.yazi/README.md diff --git a/yazi/.config/yazi/plugins/what-size.yazi/main.lua b/yazi_1/.config/yazi/plugins/what-size.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/what-size.yazi/main.lua rename to yazi_1/.config/yazi/plugins/what-size.yazi/main.lua diff --git a/yazi_1/.config/yazi/plugins/whoosh.yazi b/yazi_1/.config/yazi/plugins/whoosh.yazi new file mode 160000 index 0000000..67bebd1 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/whoosh.yazi @@ -0,0 +1 @@ +Subproject commit 67bebd12ad052b527b40ff2d24d6bba086da3fc1 diff --git a/yazi_1/.config/yazi/plugins/wise-enter.yazi/LICENSE b/yazi_1/.config/yazi/plugins/wise-enter.yazi/LICENSE new file mode 100644 index 0000000..d822628 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/wise-enter.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Vasilina Demidova + +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. diff --git a/yazi_1/.config/yazi/plugins/wise-enter.yazi/README.md b/yazi_1/.config/yazi/plugins/wise-enter.yazi/README.md new file mode 100644 index 0000000..8a45526 --- /dev/null +++ b/yazi_1/.config/yazi/plugins/wise-enter.yazi/README.md @@ -0,0 +1,73 @@ +# wise-enter.yazi +Yazi plugin: enter the deepest subfolder, extract archives, open files - all in `` key + +--- + +## Features + +- **Tunnel skipping** — if a directory contains only a single subdirectory, it jumps straight to the deepest non-trivial level (e.g. `a/b/c/` → enters `c` directly) +- **Archive extraction** — extracts archives via `unar` and then navigates into the result, also skipping tunnels +- **Smart file opening** — opens regular files with `open`, with optional multi-select support via `open_multi` + +## Requirements + +- [Yazi](https://github.com/sxyazi/yazi) +- [`unar`](https://theunarchiver.com/command-line) (provided by the `unarchiver` package) + +```bash +# Arch Linux +sudo pacman -S unarchiver + +# macOS +brew install unar +``` + +## Installation + +```bash +ya pkg add jaam8/wise-enter +``` + +Or manually clone into your Yazi plugins directory: + +```bash +git clone https://github.com/jaam8/wise-enter.yazi.git ~/.config/yazi/plugins/wise-enter.yazi +``` + +## Usage + +Add to `~/.config/yazi/keymap.toml`: + +```toml +[[manager.keymap]] +on = "" +run = "plugin wise-enter" +desc = "Wise enter" +``` + +Optionally configure in `~/.config/yazi/init.lua`: + +```lua +require("wise-enter"):setup { + open_multi = false, -- set to true to open all selected files instead of hovered only +} +``` + +## Behavior + +| Hovered item | Action | +|---|---| +| Directory (no tunnel) | `enter` — navigate one level in | +| Directory (with tunnel) | `cd` — jump straight to the deepest non-empty directory | +| Archive | Extract via `unar`, then apply directory logic to the result | +| Regular file | `open` — respects `open_multi` option | + +## Credits + +Inspired by and built on top of: +- [smart-enter.yazi](https://github.com/yazi-rs/plugins/tree/main/smart-enter.yazi) — the original archive extraction + tunnel skipping idea +- [fast-enter.yazi](https://github.com/ourongxing/fast-enter.yazi) — the `open_multi` and `enter`/`open` toggle approach + +## License + +This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file. diff --git a/yazi_1/.config/yazi/plugins/wise-enter.yazi/main.lua b/yazi_1/.config/yazi/plugins/wise-enter.yazi/main.lua new file mode 100644 index 0000000..a82950c --- /dev/null +++ b/yazi_1/.config/yazi/plugins/wise-enter.yazi/main.lua @@ -0,0 +1,76 @@ +--- @sync entry + +local function setup(self, opts) + self.open_multi = opts and opts.open_multi or false +end + +local function is_folder_only(path) + local p = io.popen("ls -l " .. ya.quote(path), "r") + if p == nil then return false end + local num = -1 + local flag = false + for line in p:lines() do + num = num + 1 + if num == 1 and line:sub(1, 1) == "d" then flag = true end + end + p:close() + return num == 1 and flag or false +end + +local function get_innermost_directory(path) + if not is_folder_only(path) then return path end + local dirs = io.popen("ls " .. ya.quote(path), "r") + if dirs == nil then return path end + local next = path + for dir in dirs:lines() do + next = path .. "/" .. dir + end + dirs:close() + return get_innermost_directory(next) +end + +local function is_archive(mime) + local patterns = { + "application/zip", + "application/gzip", + "application/x-tar", + "application/x-bzip", + "application/x-bzip2", + "application/x-7z-compressed", + "application/x-rar", + "application/x-xz", + } + for _, pattern in ipairs(patterns) do + if mime:match(pattern) then return true end + end + return false +end + +local function extract(archive) + local filename = archive:match("(.*)%.([^%.]+)$") + os.execute("unar -f -d " .. ya.quote(archive) .. " >/dev/null 2>&1") + return filename +end + +local function entry(self) + local h = cx.active.current.hovered + if h == nil then return end + + if h.cha.is_dir then + local path = tostring(h.url) + if is_folder_only(path) then + ya.emit("cd", { get_innermost_directory(path) }) + else + ya.emit("enter", {}) + end + + elseif is_archive(h:mime()) then + local extracted = extract(tostring(h.url)) + ya.emit("cd", { get_innermost_directory(extracted) }) + + else + ya.emit("open", { hovered = not self.open_multi }) + end +end + +return { entry = entry, setup = setup } diff --git a/yazi/.config/yazi/plugins/yafg.yazi/LICENSE b/yazi_1/.config/yazi/plugins/yafg.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/yafg.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/yafg.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/yafg.yazi/README.md b/yazi_1/.config/yazi/plugins/yafg.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/yafg.yazi/README.md rename to yazi_1/.config/yazi/plugins/yafg.yazi/README.md diff --git a/yazi/.config/yazi/plugins/yafg.yazi/main.lua b/yazi_1/.config/yazi/plugins/yafg.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/yafg.yazi/main.lua rename to yazi_1/.config/yazi/plugins/yafg.yazi/main.lua diff --git a/yazi/.config/yazi/plugins/yaziline.yazi/LICENSE b/yazi_1/.config/yazi/plugins/yaziline.yazi/LICENSE similarity index 100% rename from yazi/.config/yazi/plugins/yaziline.yazi/LICENSE rename to yazi_1/.config/yazi/plugins/yaziline.yazi/LICENSE diff --git a/yazi/.config/yazi/plugins/yaziline.yazi/README.md b/yazi_1/.config/yazi/plugins/yaziline.yazi/README.md similarity index 100% rename from yazi/.config/yazi/plugins/yaziline.yazi/README.md rename to yazi_1/.config/yazi/plugins/yaziline.yazi/README.md diff --git a/yazi/.config/yazi/plugins/yaziline.yazi/main.lua b/yazi_1/.config/yazi/plugins/yaziline.yazi/main.lua similarity index 100% rename from yazi/.config/yazi/plugins/yaziline.yazi/main.lua rename to yazi_1/.config/yazi/plugins/yaziline.yazi/main.lua diff --git a/yazi/.config/yazi/tags/home/liph/.local/share/Trash/files/tags.json b/yazi_1/.config/yazi/tags/home/liph/.local/share/Trash/files/tags.json similarity index 100% rename from yazi/.config/yazi/tags/home/liph/.local/share/Trash/files/tags.json rename to yazi_1/.config/yazi/tags/home/liph/.local/share/Trash/files/tags.json diff --git a/yazi/.config/yazi/tags/home/liph/Downloads/tags.json b/yazi_1/.config/yazi/tags/home/liph/Downloads/tags.json similarity index 100% rename from yazi/.config/yazi/tags/home/liph/Downloads/tags.json rename to yazi_1/.config/yazi/tags/home/liph/Downloads/tags.json diff --git a/yazi/.config/yazi/tags/home/liph/programming/webdev/html/static/tags.json b/yazi_1/.config/yazi/tags/home/liph/programming/webdev/html/static/tags.json similarity index 100% rename from yazi/.config/yazi/tags/home/liph/programming/webdev/html/static/tags.json rename to yazi_1/.config/yazi/tags/home/liph/programming/webdev/html/static/tags.json diff --git a/yazi_1/.config/yazi/tags/home/liph/scripts/tags.json b/yazi_1/.config/yazi/tags/home/liph/scripts/tags.json new file mode 100644 index 0000000..354f208 --- /dev/null +++ b/yazi_1/.config/yazi/tags/home/liph/scripts/tags.json @@ -0,0 +1 @@ +{"\\x61\\x72\\x69\\x61\\x2E\\x73\\x68":["\\x31"]} \ No newline at end of file diff --git a/yazi/.config/yazi/tags/home/liph/tags.json b/yazi_1/.config/yazi/tags/home/liph/tags.json similarity index 100% rename from yazi/.config/yazi/tags/home/liph/tags.json rename to yazi_1/.config/yazi/tags/home/liph/tags.json diff --git a/yazi_1/.config/yazi/yazi.toml b/yazi_1/.config/yazi/yazi.toml new file mode 100644 index 0000000..4407362 --- /dev/null +++ b/yazi_1/.config/yazi/yazi.toml @@ -0,0 +1,144 @@ +[mgr] +# it also support --mode=or when using with --input or --keys=!1q or --key=!1q or --tag=!1q or --tags=!1q +ratio = [2, 3, 5] +sort_by = "natural" +sort_sensitive = false +sort_reverse = false +sort_dir_first = true +linemode = "none" +show_hidden = true +show_symlink = true +scrolloff = 5 + +[preview] +tab_size = 2 +max_width = 600 +max_height = 900 +cache_dir = "" +image_delay = 30 +image_filter = "triangle" +image_quality = 75 + +[opener] +# edit = [ +# { run = '$EDITOR "$@"', block = true, for = "unix" }, +# ] +open = [{ run = 'xdg-open "$1"', desc = "Open", orphan = true }] +edit = [{ run = 'nvim "$@"', block = true, desc = "nvim", for = "unix" }] +reveal = [ + { run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" }, +] +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] +rules = [ + # Empty files + { mime = "inode/empty", use = "edit" }, + # { mime = "inode/x-empty", use = "edit" }, + # Text files - use edit (which uses $EDITOR/nvim) + { mime = "text/*", use = "edit" }, # Changed from "text" to "edit" + # { mime = "text/x-tex", use = "edit" }, # Changed from "text" to "edit" + { mime = "application/json", use = "edit" }, + { mime = "application/x-sh", use = "edit" }, + { mime = "application/x-shellscript", use = "edit" }, + { mime = "application/javascript", use = "edit" }, + # Fallback by extension + { url = "*.sh", use = "edit" }, + { url = "*.json", use = "edit" }, + { url = "*.md", use = "edit" }, + { url = "*.txt", use = "edit" }, + { url = "*.css", use = "edit" }, + { url = "*.js", use = "edit" }, + { url = "*.toml", use = "edit" }, + { url = "*.yaml", use = "edit" }, + { url = "*.yml", use = "edit" }, + { url = "*.tex", use = "edit" }, # Uncommented! + { url = "*.text", use = "edit" }, + { url = "*.epub", use = "edit" }, + { url = "*,kdl", use = "edit" }, + # Media files + { mime = "image/*", use = "open" }, + { mime = "video/*", use = "play" }, + { mime = "audio/*", use = "play" }, + { mime = "application/pdf", use = "pdf" }, + # Fallback + { mime = "*", use = "open" }, +] + +[tasks] +micro_workers = 10 +macro_workers = 25 +bizarre_retry = 5 +suppress_preload = false + +[plugin] +prepend_fetchers = [ + { group = "mime", id = "mime", url = "*", run = "mime-ext", prio = "high" }, + # git plugin + { group = "mime", id = "git", url = "*", run = "git" }, + { group = "mime", id = "git", url = "*/", run = "git" }, + # mime plugin + { group = "mime", id = "simple-tag", url = "*", run = "simple-tag" }, + { group = "mime", id = "simple-tag", url = "*/", run = "simple-tag" }, +] +preloaders = [ + # Image + { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, + { mime = "image/*", run = "image" }, + # Video + # { mime = "video/*", run = "video" }, + # PDF + { mime = "application/pdf", run = "pdf" }, + # Font + { mime = "font/*", run = "font" }, + { mime = "application/ms-opentype", run = "font" }, +] +prepend_preloaders = [ + # Replace magick, image, video with mediainfo + { mime = "{audio,video,image}/*", run = "mediainfo" }, + { mime = "application/subrip", run = "mediainfo" }, + # Adobe Illustrator, Adobe Photoshop is image/adobe.photoshop, already handled above + { mime = "application/postscript", run = "mediainfo" }, +] +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 = [ + # mediainfo MUST come before the catch-all piper rule + { 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 = "*.md", run = "rich-preview" }, # for markdown (.md) files + { url = "*.rst", run = "rich-preview" }, # for restructured text (.rst) files + { url = "*.ipynb", run = "rich-preview" }, # for jupyter notebooks (.ipynb) + { url = "*.json", run = "rich-preview" }, # for json (.json) files + # { url = "*.lang_type", run = "rich-preview"} # for particular language files eg. .py, .go., .lua, etc. +]