diff --git a/yazi/.config/yazi/init.lua b/yazi/.config/yazi/init.lua
index 33826fd..9cc817c 100644
--- a/yazi/.config/yazi/init.lua
+++ b/yazi/.config/yazi/init.lua
@@ -135,6 +135,7 @@ require("git"):setup()
-- { tag = "Downloads", path = "~/Downloads", key = "o" },
-- { tag = "Downloads", path = "~/Downloads", key = "o" },
-- }
+
require("whoosh"):setup({
-- Configuration bookmarks (cannot be deleted through plugin)
bookmarks = bookmarks,
diff --git a/yazi/.config/yazi/plugins/whoosh.yazi/LICENSE b/yazi/.config/yazi/plugins/whoosh.yazi/LICENSE
index 634b10e..c4eb9b8 100644
--- a/yazi/.config/yazi/plugins/whoosh.yazi/LICENSE
+++ b/yazi/.config/yazi/plugins/whoosh.yazi/LICENSE
@@ -1,21 +1,21 @@
-MIT License
-
-Copyright (c) 2025 Hunter Hwang
-
-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.
+ MIT License
+
+ Copyright (c) 2025 Hunter Hwang
+
+ 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/whoosh.yazi/README-RU.md b/yazi/.config/yazi/plugins/whoosh.yazi/README-RU.md
new file mode 100644
index 0000000..3726455
--- /dev/null
+++ b/yazi/.config/yazi/plugins/whoosh.yazi/README-RU.md
@@ -0,0 +1,458 @@
+
π 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 0077cd9..407ee22 100644
--- a/yazi/.config/yazi/plugins/whoosh.yazi/README.md
+++ b/yazi/.config/yazi/plugins/whoosh.yazi/README.md
@@ -31,7 +31,7 @@
## Installation
> [!IMPORTANT]
-> Requires Yazi v25.5.28+
+> Requires Yazi v26.1.4+
```sh
# Manual installation
diff --git a/yazi/.config/yazi/plugins/whoosh.yazi/bookmarks b/yazi/.config/yazi/plugins/whoosh.yazi/bookmarks
index 9bed818..905530c 100644
--- a/yazi/.config/yazi/plugins/whoosh.yazi/bookmarks
+++ b/yazi/.config/yazi/plugins/whoosh.yazi/bookmarks
@@ -1,26 +1,6 @@
-zshrc /home/liph/dotfiles/zshrc z
-yazi /home/liph/dotfiles/yazi/.config/yazi y
-wezterm /home/liph/dotfiles/wezterm/.config/wezterm d,w
-tmux /home/liph/dotfiles/tmux d,t
-tex /home/liph/Documents/tex D,t
-tank /mnt/tank t,t
-scripts /home/liph/scripts d,s
-r /home/liph/Documents/r D,r
-programming /mnt/tank/programming p
-podman /mnt/flash1/podman t,p
-ohmyposh /home/liph/dotfiles/ohmyposh/.config/ohmyposh d,o
-obsidian /home/liph/Documents/obsidian/vault D,o
-nvim /home/liph/dotfiles/nvim/.config/nvim/ n,n,n
-niri /home/liph/dotfiles/niri/.config/niri d,n
-neovim_plug /home/liph/dotfiles/nvim/.config/nvim/lua/plugins/ n,p
-mnt /mnt m
-mcp /home/liph/mcp M
-kitty /home/liph/dotfiles/kitty/.config/kitty k
-hypr /home/liph/.config/hypr h
-home /home/liph f
-Downloads /home/liph/Downloads D,d
+waybar-niri /home/liph/dotfiles/niri/.config/niri/waybar-niri d,n
+plugins /home/liph/dotfiles/nvim/.config/nvim/lua/plugins n,p
+nvim /home/liph/dotfiles/nvim/.config/nvim n,n
dotfiles /home/liph/dotfiles d,d
-Documents /home/liph/Documents D,D
.config /home/liph/.config c
-cloud /home/liph/cloud C
aerc /home/liph/dotfiles/aerc/.config/aerc d,a
diff --git a/yazi/.config/yazi/plugins/whoosh.yazi/main.lua b/yazi/.config/yazi/plugins/whoosh.yazi/main.lua
index 855ee3d..a53d242 100644
--- a/yazi/.config/yazi/plugins/whoosh.yazi/main.lua
+++ b/yazi/.config/yazi/plugins/whoosh.yazi/main.lua
@@ -1,1742 +1,1392 @@
--- @since 25.5.28 this is a test
-local path_sep = package.config:sub(1, 1)
-
-local DEFAULT_SPECIAL_KEYS = {
- create_temp = "",
- fuzzy_search = "",
- history = "",
- previous_dir = "",
-}
-
-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
- local path = tostring(h.url)
- if h.cha.is_dir then
- if ya.target_family() == "windows" and path:match("^[A-Za-z]:$") then
- return path .. "\\"
- end
- return path
- end
- return path
- else
- return ""
- end
-end)
-
-local is_hovered_directory = ya.sync(function(state)
- local h = cx.active.current.hovered
- if h then
- return h.cha.is_dir
- end
- return false
-end)
-
-local get_current_dir_path = ya.sync(function()
- local path = tostring(cx.active.current.cwd)
- if ya.target_family() == "windows" and path:match("^[A-Za-z]:$") then
- return path .. "\\"
- end
- return path
-end)
-
-local get_state_attr = ya.sync(function(state, attr)
- return state[attr]
-end)
-
-local set_state_attr = ya.sync(function(state, attr, value)
- state[attr] = value
-end)
-
-local set_bookmarks = ya.sync(function(state, path, value)
- state.bookmarks[path] = value
-end)
-
-local set_temp_bookmarks = ya.sync(function(state, path, value)
- state.temp_bookmarks[path] = value
-end)
-
-local get_temp_bookmarks = ya.sync(function(state)
- return state.temp_bookmarks
-end)
-
-local get_current_tab_idx = ya.sync(function(state)
- return cx.tabs.idx
-end)
-
-local get_directory_history = ya.sync(function(state)
- return state.directory_history
-end)
-
-local add_to_history = ya.sync(function(state, tab_idx, path)
- if not state.directory_history[tab_idx] then
- state.directory_history[tab_idx] = {}
- end
-
- local history = state.directory_history[tab_idx]
- local history_size = state.history_size or 10
-
- for i = #history, 1, -1 do
- if history[i] == path then
- table.remove(history, i)
- end
- end
-
- table.insert(history, 1, path)
-
- while #history > history_size do
- table.remove(history, #history)
- end
-end)
-
-local get_tab_history = ya.sync(function(state, tab_idx)
- return state.directory_history[tab_idx] or {}
-end)
-
-local function ensure_directory(path)
- local dir_path = path:match("(.+)[\\/][^\\/]*$")
- if not dir_path then
- return
- end
- if ya.target_family() == "windows" then
- os.execute('mkdir "' .. dir_path:gsub("/", "\\") .. '" 2>nul')
- else
- os.execute('mkdir -p "' .. dir_path .. '"')
- end
-end
-
-local function normalize_path(path)
- local normalized_path = tostring(path):gsub("[\\/]+", path_sep)
-
- if ya.target_family() == "windows" then
- if normalized_path:match("^[A-Za-z]:[\\/]*$") then
- normalized_path = normalized_path:gsub("^([A-Za-z]:)[\\/]*", "%1\\")
- else
- normalized_path = normalized_path:gsub("^([A-Za-z]:)[\\/]+", "%1\\")
- normalized_path = normalized_path:gsub("[\\/]+$", "")
- end
- else
- if normalized_path ~= "/" then
- normalized_path = normalized_path:gsub("[\\/]+$", "")
- end
- end
-
- return normalized_path
-end
-
-local function apply_home_alias(path)
- if not path or path == "" then
- return path
- end
-
- local home_alias_enabled = get_state_attr("home_alias_enabled")
- if home_alias_enabled == false then
- return path
- end
-
- if path:sub(1, 1) == "~" then
- return path
- end
-
- local home = os.getenv("HOME")
- if ya.target_family() == "windows" and (not home or home == "") then
- home = os.getenv("USERPROFILE")
- end
- if not home or home == "" then
- return path
- end
-
- local normalized_home = normalize_path(home)
- if not normalized_home or normalized_home == "" then
- return path
- end
-
- local sep = path_sep
-
- if ya.target_family() == "windows" then
- local path_lower = path:lower()
- local home_lower = normalized_home:lower()
- if path_lower == home_lower then
- return "~"
- end
- local prefix_lower = (normalized_home .. sep):lower()
- if path_lower:sub(1, #prefix_lower) == prefix_lower then
- return "~" .. path:sub(#normalized_home + 1)
- end
- else
- if path == normalized_home then
- return "~"
- end
- local prefix = normalized_home .. sep
- if path:sub(1, #prefix) == prefix then
- return "~" .. path:sub(#normalized_home + 1)
- end
- end
-
- return path
-end
-
-local function normalize_special_key(value, default)
- if value == nil then
- return default
- end
- if value == false then
- return nil
- end
- if type(value) == "string" then
- local trimmed = value:gsub("^%s*(.-)%s*$", "%1")
- if trimmed == "" then
- return nil
- end
- return trimmed
- end
- if type(value) == "table" then
- local seq = {}
- for _, item in ipairs(value) do
- if type(item) == "string" then
- local trimmed = item:gsub("^%s*(.-)%s*$", "%1")
- if trimmed ~= "" then
- table.insert(seq, trimmed)
- end
- end
- end
- if #seq == 0 then
- return nil
- end
- return seq
- end
- return default
-end
-
-local function truncate_long_folder_names(path, max_folder_length)
- if not max_folder_length or max_folder_length <= 0 then
- return path
- end
-
- local separator = ya.target_family() == "windows" and "\\" or "/"
- local parts = {}
-
- for part in path:gmatch("[^" .. separator .. "]+") do
- if #part > max_folder_length then
- local keep_length = math.max(3, math.floor(max_folder_length * 0.4))
- local truncated = part:sub(1, keep_length) .. "..."
- table.insert(parts, truncated)
- else
- table.insert(parts, part)
- end
- end
-
- local result = table.concat(parts, separator)
-
- if path:sub(1, 1) == separator then
- result = separator .. result
- end
-
- return result
-end
-
-local function truncate_path(path, max_parts)
- max_parts = max_parts or 3
- local normalized_path = normalize_path(path)
- normalized_path = apply_home_alias(normalized_path)
-
- local parts = {}
- local separator = ya.target_family() == "windows" and "\\" or "/"
-
- if ya.target_family() == "windows" then
- local drive, rest = normalized_path:match("^([A-Za-z]:\\)(.*)$")
- if drive then
- table.insert(parts, drive)
- if rest and rest ~= "" then
- for part in rest:gmatch("[^\\]+") do
- table.insert(parts, part)
- end
- end
- else
- for part in normalized_path:gmatch("[^\\]+") do
- table.insert(parts, part)
- end
- end
- else
- if normalized_path:sub(1, 1) == "/" then
- table.insert(parts, "/")
- local rest = normalized_path:sub(2)
- if rest ~= "" then
- for part in rest:gmatch("[^/]+") do
- table.insert(parts, part)
- end
- end
- elseif normalized_path:sub(1, 1) == "~" then
- table.insert(parts, "~")
- local rest = normalized_path:sub(2)
- if rest:sub(1, 1) == "/" then
- rest = rest:sub(2)
- end
- if rest ~= "" then
- for part in rest:gmatch("[^/]+") do
- table.insert(parts, part)
- end
- end
- else
- for part in normalized_path:gmatch("[^/]+") do
- table.insert(parts, part)
- end
- end
- end
-
- if #parts > max_parts then
- local result_parts = {}
- local first_part = parts[1]
-
- if ya.target_family() == "windows" and first_part:match("^[A-Za-z]:\\$") then
- first_part = first_part:sub(1, -2)
- end
-
- if ya.target_family() ~= "windows" and first_part == "/" then
- table.insert(result_parts, "")
- else
- table.insert(result_parts, first_part)
- end
-
- table.insert(result_parts, "β¦")
- for i = #parts - max_parts + 2, #parts do
- table.insert(result_parts, parts[i])
- end
-
- local out = table.concat(result_parts, separator)
- if ya.target_family() ~= "windows" then
- out = out:gsub("^//+", "/")
- end
- return out
- else
- return normalized_path
- end
-end
-
-local function path_to_desc(path)
- local path_truncate_enabled = get_state_attr("path_truncate_enabled")
- local result_path = apply_home_alias(normalize_path(path))
-
- local path_truncate_long_names_enabled = get_state_attr("path_truncate_long_names_enabled")
- if path_truncate_long_names_enabled == true then
- local max_folder_length = get_state_attr("path_max_folder_name_length") or 20
- result_path = truncate_long_folder_names(result_path, max_folder_length)
- end
-
- if path_truncate_enabled == true then
- local max_depth = get_state_attr("path_max_depth") or 3
- result_path = truncate_path(result_path, max_depth)
- end
-
- return result_path
-end
-
-local function get_display_width(str)
- return ui.Line(str):width()
-end
-
-local function truncate_long_folder_names(path, max_folder_length)
- if not max_folder_length or max_folder_length <= 0 then
- return path
- end
-
- local separator = ya.target_family() == "windows" and "\\" or "/"
- local parts = {}
- local is_windows = ya.target_family() == "windows"
-
- if is_windows then
- local drive, rest = path:match("^([A-Za-z]:\\)(.*)$")
- if drive then
- table.insert(parts, drive:sub(1, -2))
- if rest and rest ~= "" then
- for part in rest:gmatch("[^\\]+") do
- if #part > max_folder_length then
- local keep_length = math.max(3, math.floor(max_folder_length * 0.4))
- local truncated = part:sub(1, keep_length) .. "..."
- table.insert(parts, truncated)
- else
- table.insert(parts, part)
- end
- end
- end
- return table.concat(parts, separator)
- end
- end
-
- for part in path:gmatch("[^" .. (separator == "\\" and "\\\\" or separator) .. "]+") do
- if #part > max_folder_length then
- local keep_length = math.max(3, math.floor(max_folder_length * 0.4))
- local truncated = part:sub(1, keep_length) .. "..."
- table.insert(parts, truncated)
- else
- table.insert(parts, part)
- end
- end
-
- local result = table.concat(parts, separator)
-
- if path:sub(1, 1) == separator then
- result = separator .. result
- end
-
- return result
-end
-
-local function path_to_desc_for_fzf(path)
- local fzf_path_truncate_enabled = get_state_attr("fzf_path_truncate_enabled")
- local result_path = apply_home_alias(normalize_path(path))
-
- local fzf_path_truncate_long_names_enabled = get_state_attr("fzf_path_truncate_long_names_enabled")
- if fzf_path_truncate_long_names_enabled == true then
- local max_folder_length = get_state_attr("fzf_path_max_folder_name_length") or 20
- result_path = truncate_long_folder_names(result_path, max_folder_length)
- end
-
- if fzf_path_truncate_enabled == true then
- local max_depth = get_state_attr("fzf_path_max_depth") or 5
- result_path = truncate_path(result_path, max_depth)
- end
-
- return result_path
-end
-
-local function path_to_desc_for_history(path)
- local history_fzf_path_truncate_enabled = get_state_attr("history_fzf_path_truncate_enabled")
- local result_path = apply_home_alias(normalize_path(path))
-
- local history_fzf_path_truncate_long_names_enabled = get_state_attr("history_fzf_path_truncate_long_names_enabled")
- if history_fzf_path_truncate_long_names_enabled == true then
- local max_folder_length = get_state_attr("history_fzf_path_max_folder_name_length") or 30
- result_path = truncate_long_folder_names(result_path, max_folder_length)
- end
-
- if history_fzf_path_truncate_enabled == true then
- local max_depth = get_state_attr("history_fzf_path_max_depth") or 5
- result_path = truncate_path(result_path, max_depth)
- end
-
- return result_path
-end
-
-local function format_bookmark_for_menu(tag, key)
- return tag
-end
-
-local function format_bookmark_for_fzf(tag, path, key, max_tag_width, max_path_width)
- local tag_width = math.max(max_tag_width, 15)
- local path_width = math.max(max_path_width or 30, 30)
-
- local formatted_tag = tag
- local tag_display_width = get_display_width(tag)
- if tag_display_width > tag_width then
- formatted_tag = tag:sub(1, tag_width - 3) .. "..."
- else
- formatted_tag = tag .. string.rep(" ", tag_width - tag_display_width)
- end
-
- local display_path = path_to_desc_for_fzf(path)
- local formatted_path = display_path
- local path_display_width = get_display_width(display_path)
- if path_display_width > path_width then
- formatted_path = display_path:sub(1, path_width - 3) .. "..."
- else
- formatted_path = display_path .. string.rep(" ", path_width - path_display_width)
- end
-
- local key_display = ""
- if key then
- if type(key) == "table" then
- key_display = table.concat(key, ",")
- elseif type(key) == "string" and #key > 0 then
- key_display = key
- else
- key_display = tostring(key)
- end
- end
-
- return formatted_tag .. " " .. formatted_path .. " " .. key_display
-end
-
-local function sort_bookmarks(bookmarks, key1, key2, reverse)
- reverse = reverse or false
- table.sort(bookmarks, function(x, y)
- if not x or not y then
- return false
- end
- local x_key1, y_key1 = x[key1], y[key1]
- local x_key2, y_key2 = x[key2], y[key2]
- if x_key1 == nil and y_key1 == nil then
- if x_key2 == nil and y_key2 == nil then
- return false
- elseif x_key2 == nil then
- return false
- elseif y_key2 == nil then
- return true
- else
- return tostring(x_key2) < tostring(y_key2)
- end
- elseif x_key1 == nil then
- return false
- elseif y_key1 == nil then
- return true
- else
- return tostring(x_key1) < tostring(y_key1)
- end
- end)
- if reverse then
- local n = #bookmarks
- for i = 1, math.floor(n / 2) do
- bookmarks[i], bookmarks[n - i + 1] = bookmarks[n - i + 1], bookmarks[i]
- end
- end
- return bookmarks
-end
-
-local action_save, action_jump, action_delete, which_find, fzf_find, fzf_find_for_rename, fzf_history
-
-local function get_all_bookmarks()
- local all_b = {}
- local config_b = get_state_attr("config_bookmarks")
- local user_b = get_state_attr("bookmarks")
-
- for path, item in pairs(config_b) do
- all_b[path] = item
- end
- for path, item in pairs(user_b) do
- all_b[path] = item
- end
- return all_b
-end
-
-local function serialize_key_for_file(key)
- if type(key) == "table" then
- return table.concat(key, ",")
- elseif type(key) == "string" then
- return key
- else
- return tostring(key)
- end
-end
-
-local function deserialize_key_from_file(key_str)
- if not key_str or key_str == "" then
- return ""
- end
-
- key_str = key_str:gsub("^%s*(.-)%s*$", "%1")
- if key_str == "" then
- return ""
- end
-
- if key_str:find(",") then
- local seq = {}
- for token in key_str:gmatch("[^,%s]+") do
- token = token:gsub("^%s*(.-)%s*$", "%1")
- if token ~= "" then
- if token:match("^<.->$") then
- table.insert(seq, token)
- else
- for _, cp in utf8.codes(token) do
- table.insert(seq, utf8.char(cp))
- end
- end
- end
- end
- return seq
- end
-
- if key_str:match("^<.->$") then
- return key_str
- end
-
- if utf8.len(key_str) > 1 then
- local seq = {}
- for _, cp in utf8.codes(key_str) do
- table.insert(seq, utf8.char(cp))
- end
- return seq
- else
- return key_str
- end
-end
-
-local save_to_file = function(mb_path, bookmarks)
- ensure_directory(mb_path)
- local file = io.open(mb_path, "w")
- if file == nil then
- ya.notify({
- title = "Bookmarks Error",
- content = "Cannot create bookmark file: " .. mb_path,
- timeout = 2,
- level = "error",
- })
- return
- end
- local array = {}
- for _, item in pairs(bookmarks) do
- table.insert(array, item)
- end
- 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))
- end
- file:close()
-end
-
-fzf_find = function()
- local mb_path = get_state_attr("path")
- local temp_bookmarks = get_temp_bookmarks()
-
- local permit = ui.hide()
- local temp_file_path = nil
- local cmd
-
- local all_perm_bookmarks = get_all_bookmarks()
-
- temp_file_path = os.tmpname()
- local temp_file = io.open(temp_file_path, "w")
- if temp_file then
- local all_fzf_items = {}
- local max_tag_width = 0
- local max_path_width = 0
-
- if temp_bookmarks and next(temp_bookmarks) then
- local temp_array = {}
- for _, item in pairs(temp_bookmarks) do
- if item and item.tag and item.path and item.key then
- table.insert(temp_array, item)
- end
- end
- sort_bookmarks(temp_array, "tag", "key", true)
- for _, item in ipairs(temp_array) do
- local tag_with_prefix = "[TEMP] " .. item.tag
- local display_path = path_to_desc_for_fzf(item.path)
- table.insert(all_fzf_items, { tag = tag_with_prefix, path = item.path, key = item.key or "" })
- max_tag_width = math.max(max_tag_width, get_display_width(tag_with_prefix))
- max_path_width = math.max(max_path_width, get_display_width(display_path))
- end
- end
-
- if all_perm_bookmarks and next(all_perm_bookmarks) then
- local perm_array = {}
- for _, item in pairs(all_perm_bookmarks) do
- table.insert(perm_array, item)
- end
- sort_bookmarks(perm_array, "tag", "key", true)
- for _, item in ipairs(perm_array) do
- local display_path = path_to_desc_for_fzf(item.path)
- table.insert(all_fzf_items, { tag = item.tag, path = item.path, key = item.key or "" })
- max_tag_width = math.max(max_tag_width, get_display_width(item.tag))
- max_path_width = math.max(max_path_width, get_display_width(display_path))
- end
- end
-
- if #all_fzf_items > 0 then
- for _, item in ipairs(all_fzf_items) do
- local formatted_line = format_bookmark_for_fzf(item.tag, item.path, item.key, max_tag_width, max_path_width)
- temp_file:write(formatted_line .. "\t" .. item.path .. "\n")
- end
- temp_file:close()
- cmd = string.format('fzf %s --with-nth=1 --prompt="Search > " < "%s"', get_fzf_delimiter(), temp_file_path)
- else
- temp_file:close()
- cmd = 'echo No bookmarks found | fzf --prompt="Search > "'
- end
- else
- cmd = 'echo No bookmarks found | fzf --prompt="Search > "'
- end
-
- local handle = io.popen(cmd, "r")
- local result = ""
- if handle then
- result = string.gsub(handle:read("*all") or "", "^%s*(.-)%s*$", "%1")
- handle:close()
- end
-
- if temp_file_path then
- os.remove(temp_file_path)
- end
- permit:drop()
-
- if result and result ~= "" and result ~= "No bookmarks found" then
- local tab_pos = result:find("\t")
- if tab_pos then
- return result:sub(tab_pos + 1)
- end
- end
- return nil
-end
-
-fzf_find_for_rename = function()
- local mb_path = get_state_attr("path")
- local temp_bookmarks = get_temp_bookmarks()
-
- local permit = ui.hide()
- local temp_file_path = nil
- local cmd
-
- local all_perm_bookmarks = get_all_bookmarks()
-
- temp_file_path = os.tmpname()
- local temp_file = io.open(temp_file_path, "w")
- if temp_file then
- local all_fzf_items = {}
- local max_tag_width = 0
- local max_path_width = 0
-
- if temp_bookmarks and next(temp_bookmarks) then
- local temp_array = {}
- for _, item in pairs(temp_bookmarks) do
- if item and item.tag and item.path and item.key then
- table.insert(temp_array, item)
- end
- end
- sort_bookmarks(temp_array, "tag", "key", true)
- for _, item in ipairs(temp_array) do
- local tag_with_prefix = "[TEMP] " .. item.tag
- local display_path = path_to_desc_for_fzf(item.path)
- table.insert(all_fzf_items, { tag = tag_with_prefix, path = item.path, key = item.key or "" })
- max_tag_width = math.max(max_tag_width, get_display_width(tag_with_prefix))
- max_path_width = math.max(max_path_width, get_display_width(display_path))
- end
- end
-
- if all_perm_bookmarks and next(all_perm_bookmarks) then
- local perm_array = {}
- for _, item in pairs(all_perm_bookmarks) do
- table.insert(perm_array, item)
- end
- sort_bookmarks(perm_array, "tag", "key", true)
- for _, item in ipairs(perm_array) do
- local display_path = path_to_desc_for_fzf(item.path)
- table.insert(all_fzf_items, { tag = item.tag, path = item.path, key = item.key or "" })
- max_tag_width = math.max(max_tag_width, get_display_width(item.tag))
- max_path_width = math.max(max_path_width, get_display_width(display_path))
- end
- end
-
- if #all_fzf_items > 0 then
- for _, item in ipairs(all_fzf_items) do
- local formatted_line = format_bookmark_for_fzf(item.tag, item.path, item.key, max_tag_width, max_path_width)
- temp_file:write(formatted_line .. "\t" .. item.path .. "\n")
- end
- temp_file:close()
- cmd = string.format('fzf %s --with-nth=1 --prompt="Rename > " < "%s"', get_fzf_delimiter(), temp_file_path)
- else
- temp_file:close()
- cmd = 'echo No bookmarks found | fzf --prompt="Rename > "'
- end
- else
- cmd = 'echo No bookmarks found | fzf --prompt="Rename > "'
- end
-
- local handle = io.popen(cmd, "r")
- local result = ""
- if handle then
- result = string.gsub(handle:read("*all") or "", "^%s*(.-)%s*$", "%1")
- handle:close()
- end
-
- if temp_file_path then
- os.remove(temp_file_path)
- end
- permit:drop()
-
- if result and result ~= "" and result ~= "No bookmarks found" then
- local tab_pos = result:find("\t")
- if tab_pos then
- return result:sub(tab_pos + 1)
- end
- end
- return nil
-end
-
-fzf_find_multi = function()
- local temp_bookmarks = get_temp_bookmarks()
- local user_bookmarks = get_state_attr("bookmarks")
-
- local permit = ui.hide()
- local temp_file_path = nil
- local cmd
-
- temp_file_path = os.tmpname()
- local temp_file = io.open(temp_file_path, "w")
- if temp_file then
- local all_fzf_items = {}
- local max_tag_width = 0
- local max_path_width = 0
-
- if temp_bookmarks and next(temp_bookmarks) then
- local temp_array = {}
- for _, item in pairs(temp_bookmarks) do
- if item and item.tag and item.path and item.key then
- table.insert(temp_array, item)
- end
- end
- sort_bookmarks(temp_array, "tag", "key", true)
- for _, item in ipairs(temp_array) do
- local tag_with_prefix = "[TEMP] " .. item.tag
- local display_path = path_to_desc_for_fzf(item.path)
- table.insert(all_fzf_items, { tag = tag_with_prefix, path = item.path, key = item.key or "" })
- max_tag_width = math.max(max_tag_width, get_display_width(tag_with_prefix))
- max_path_width = math.max(max_path_width, get_display_width(display_path))
- end
- end
-
- if user_bookmarks and next(user_bookmarks) then
- local user_array = {}
- for _, item in pairs(user_bookmarks) do
- table.insert(user_array, item)
- end
- sort_bookmarks(user_array, "tag", "key", true)
- for _, item in ipairs(user_array) do
- local display_path = path_to_desc_for_fzf(item.path)
- table.insert(all_fzf_items, { tag = item.tag, path = item.path, key = item.key or "" })
- max_tag_width = math.max(max_tag_width, get_display_width(item.tag))
- max_path_width = math.max(max_path_width, get_display_width(display_path))
- end
- end
-
- if #all_fzf_items > 0 then
- for _, item in ipairs(all_fzf_items) do
- local formatted_line = format_bookmark_for_fzf(item.tag, item.path, item.key, max_tag_width, max_path_width)
- temp_file:write(formatted_line .. "\t" .. item.path .. "\n")
- end
- temp_file:close()
- cmd =
- string.format('fzf --multi %s --with-nth=1 --prompt="Delete > " < "%s"', get_fzf_delimiter(), temp_file_path)
- else
- temp_file:close()
- cmd = 'echo No deletable bookmarks found | fzf --prompt="Delete > "'
- end
- else
- cmd = 'echo No deletable bookmarks found | fzf --prompt="Delete > "'
- end
-
- local handle = io.popen(cmd, "r")
- local result = ""
- if handle then
- result = handle:read("*all") or ""
- handle:close()
- end
-
- if temp_file_path then
- os.remove(temp_file_path)
- end
- permit:drop()
-
- if result and result ~= "" and result ~= "No deletable bookmarks found" then
- local paths = {}
- for line in result:gmatch("[^\r\n]+") do
- line = string.gsub(line, "^%s*(.-)%s*$", "%1")
- if line ~= "" then
- local tab_pos = line:find("\t")
- if tab_pos then
- table.insert(paths, line:sub(tab_pos + 1))
- end
- end
- end
- return paths
- end
- return {}
-end
-
-fzf_history = 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
- for _, path in ipairs(history) do
- if path ~= current_path then
- table.insert(filtered_history, path)
- end
- end
- end
-
- if not filtered_history or #filtered_history == 0 then
- return nil
- 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")
- 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
-end
-
-local create_special_menu_items = function()
- local special_items = {}
- local special_keys = get_state_attr("special_keys") or DEFAULT_SPECIAL_KEYS
- 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__" })
- end
-
- local fuzzy_search_key = special_keys.fuzzy_search
- if fuzzy_search_key then
- table.insert(special_items, { desc = "Fuzzy search", on = fuzzy_search_key, path = "__FUZZY_SEARCH__" })
- end
-
- 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
- for _, path in ipairs(history) do
- if path ~= current_path then
- table.insert(filtered_history, path)
- end
- end
- end
-
- local history_key = special_keys.history
- if history_key and filtered_history and #filtered_history > 0 then
- table.insert(special_items, { desc = "Directory history", on = history_key, path = "__HISTORY__" })
- end
-
- local previous_dir_key = special_keys.previous_dir
- if previous_dir_key and filtered_history and filtered_history[1] then
- local previous_dir = filtered_history[1]
- local display_path = path_to_desc(previous_dir)
- table.insert(special_items, { desc = "<- " .. display_path, on = previous_dir_key, path = previous_dir })
- end
-
- return special_items
-end
-
-which_find = function()
- local bookmarks = get_all_bookmarks()
- local temp_bookmarks = get_temp_bookmarks()
-
- local cands_static = create_special_menu_items()
- local cands_bookmarks = {}
-
- local all_bookmark_items = {}
- local max_tag_width = 0
- local max_path_width = 0
-
- if temp_bookmarks then
- for path, item in pairs(temp_bookmarks) do
- if item and item.tag and #item.tag ~= 0 then
- local tag_with_prefix = "[TEMP] " .. item.tag
- local display_path = path_to_desc(item.path or path)
- table.insert(
- all_bookmark_items,
- { tag = tag_with_prefix, path = item.path or path, key = item.key or "", is_temp = true }
- )
- max_tag_width = math.max(max_tag_width, get_display_width(tag_with_prefix))
- max_path_width = math.max(max_path_width, get_display_width(display_path))
- end
- end
- end
-
- for path, item in pairs(bookmarks) do
- if item and item.tag and #item.tag ~= 0 then
- local display_path = path_to_desc(item.path or path)
- table.insert(
- all_bookmark_items,
- { tag = item.tag, path = item.path or path, key = item.key or "", is_temp = false }
- )
- max_tag_width = math.max(max_tag_width, get_display_width(item.tag))
- max_path_width = math.max(max_path_width, get_display_width(display_path))
- end
- end
-
- for _, item in ipairs(all_bookmark_items) do
- if
- item.key
- and item.key ~= ""
- and (type(item.key) == "string" or (type(item.key) == "table" and #item.key > 0))
- then
- local formatted_desc = format_bookmark_for_menu(item.tag, item.key)
- table.insert(cands_bookmarks, { desc = formatted_desc, on = item.key, path = item.path })
- end
- end
-
- sort_bookmarks(cands_bookmarks, "on", "desc", false)
-
- local cands = {}
- for _, item in ipairs(cands_static) do
- table.insert(cands, item)
- end
- for _, item in ipairs(cands_bookmarks) do
- table.insert(cands, item)
- end
-
- if #cands == #cands_static and #cands_bookmarks == 0 then
- ya.notify({ title = "Bookmarks", content = "No bookmarks found", timeout = 1, level = "info" })
- end
- local idx = ya.which({ cands = cands })
- if idx == nil then
- return nil
- end
- return cands[idx].path
-end
-
-which_find_deletable = function()
- local user_bookmarks = get_state_attr("bookmarks")
- local temp_bookmarks = get_temp_bookmarks()
-
- local cands_bookmarks = {}
-
- local all_bookmark_items = {}
-
- if temp_bookmarks then
- for path, item in pairs(temp_bookmarks) do
- if item and item.tag and #item.tag ~= 0 then
- local tag_with_prefix = "[TEMP] " .. item.tag
- table.insert(
- all_bookmark_items,
- { tag = tag_with_prefix, path = item.path or path, key = item.key or "", is_temp = true }
- )
- end
- end
- end
-
- if user_bookmarks then
- for path, item in pairs(user_bookmarks) do
- if item and item.tag and #item.tag ~= 0 then
- table.insert(
- all_bookmark_items,
- { tag = item.tag, path = item.path or path, key = item.key or "", is_temp = false }
- )
- end
- end
- end
-
- for _, item in ipairs(all_bookmark_items) do
- if
- item.key
- and item.key ~= ""
- and (type(item.key) == "string" or (type(item.key) == "table" and #item.key > 0))
- then
- local formatted_desc = format_bookmark_for_menu(item.tag, item.key)
- table.insert(cands_bookmarks, { desc = formatted_desc, on = item.key, path = item.path })
- end
- end
-
- sort_bookmarks(cands_bookmarks, "on", "desc", false)
-
- if #cands_bookmarks == 0 then
- ya.notify({ title = "Bookmarks", content = "No deletable bookmarks found", timeout = 1, level = "info" })
- return nil
- end
-
- local idx = ya.which({ cands = cands_bookmarks })
- if idx == nil then
- return nil
- end
- return cands_bookmarks[idx].path
-end
-
-action_jump = function(path)
- if path == nil then
- return
- end
-
- local jump_notify = get_state_attr("jump_notify")
- local all_bookmarks = get_all_bookmarks()
- local temp_bookmarks = get_temp_bookmarks()
-
- if path == "__CREATE_TEMP__" then
- action_save(get_current_dir_path(), true)
- return
- elseif path == "__FUZZY_SEARCH__" then
- local selected_path = fzf_find()
- if selected_path then
- action_jump(selected_path)
- end
- return
- elseif path == "__HISTORY__" then
- local selected_path = fzf_history()
- if selected_path then
- action_jump(selected_path)
- end
- return
- end
-
- local bookmark = temp_bookmarks[path] or all_bookmarks[path]
- if not bookmark then
- ya.emit("cd", { path })
- if jump_notify then
- ya.notify({ title = "Bookmarks", content = 'Jump to "' .. path_to_desc(path) .. '"', timeout = 1, level = "info" })
- end
- return
- end
-
- local tag = bookmark.tag
- local is_temp = temp_bookmarks[path] ~= nil
-
- ya.emit("cd", { path })
-
- if jump_notify then
- local prefix = is_temp and "[TEMP] " or ""
- ya.notify({ title = "Bookmarks", content = 'Jump to "' .. prefix .. tag .. '"', timeout = 1, level = "info" })
- end
-end
-
-local function parse_keys_input(input)
- if not input or input == "" then
- return {}
- end
- local seq = {}
- for token in input:gmatch("[^,%s]+") do
- token = token:gsub("^%s*(.-)%s*$", "%1")
- if token ~= "" then
- if token:match("^<.->$") then
- table.insert(seq, token)
- else
- for _, cp in utf8.codes(token) do
- table.insert(seq, utf8.char(cp))
- end
- end
- end
- end
- return seq
-end
-
-local function format_keys_for_display(keys)
- if type(keys) == "table" then
- return table.concat(keys, ",")
- elseif type(keys) == "string" then
- return keys
- else
- return ""
- end
-end
-
-local function _seq_from_key(k)
- if type(k) == "table" then
- local out = {}
- for _, t in ipairs(k) do
- if t:match("^<.->$") then
- table.insert(out, t)
- else
- for _, cp in utf8.codes(t) do
- table.insert(out, utf8.char(cp))
- end
- end
- end
- return out
- elseif type(k) == "string" then
- return parse_keys_input(k)
- else
- return {}
- end
-end
-
-local function _seq_equal(a, b)
- if #a ~= #b then
- return false
- end
- for i = 1, #a do
- if a[i] ~= b[i] then
- return false
- end
- end
- return true
-end
-
-local function _seq_is_prefix(short, long)
- if #short >= #long then
- return false
- end
- for i = 1, #short do
- if short[i] ~= long[i] then
- return false
- end
- end
- return true
-end
-
-local function _seq_to_string(seq)
- return table.concat(seq, ",")
-end
-
-local function find_path_by_key_sequence(seq)
- if not seq or #seq == 0 then
- return nil
- end
-
- local function matches(candidate)
- if candidate == nil or candidate == "" then
- return false
- end
- local candidate_seq = _seq_from_key(candidate)
- if #candidate_seq == 0 then
- return false
- end
- return _seq_equal(seq, candidate_seq)
- end
-
- for _, item in ipairs(create_special_menu_items() or {}) do
- if matches(item.on) then
- return item.path
- end
- end
-
- local temp = get_temp_bookmarks()
- for path, item in pairs(temp or {}) do
- if matches(item.key) then
- return path
- end
- end
-
- local bookmarks = get_all_bookmarks()
- for path, item in pairs(bookmarks or {}) do
- if matches(item.key) then
- return path
- end
- end
-
- return nil
-end
-
-local function jump_by_key_spec(spec)
- local cleaned = (spec or ""):gsub("^%s*(.-)%s*$", "%1")
- if cleaned == "" then
- ya.notify({ title = "Bookmarks", content = "Missing key sequence", timeout = 1, level = "warn" })
- return false
- end
-
- local seq = parse_keys_input(cleaned)
- if #seq == 0 then
- ya.notify({ title = "Bookmarks", content = "Missing key sequence", timeout = 1, level = "warn" })
- return false
- end
-
- local path = find_path_by_key_sequence(seq)
- if not path then
- ya.notify({
- title = "Bookmarks",
- content = "Bookmark not found for key: " .. _seq_to_string(seq),
- timeout = 1,
- level = "info",
- })
- return false
- end
-
- action_jump(path)
- return true
-end
-
-local generate_key = function()
- local keys = get_state_attr("keys")
- local key2rank = get_state_attr("key2rank")
- local bookmarks = get_all_bookmarks()
- local temp_bookmarks = get_temp_bookmarks()
-
- local mb = {}
- for _, item in pairs(bookmarks) do
- if item and item.key then
- if type(item.key) == "string" and #item.key == 1 then
- table.insert(mb, item.key)
- elseif type(item.key) == "table" then
- for _, k in ipairs(item.key) do
- if type(k) == "string" and #k == 1 then
- table.insert(mb, k)
- end
- end
- end
- end
- end
- if temp_bookmarks then
- for _, item in pairs(temp_bookmarks) do
- if item and item.key then
- if type(item.key) == "string" and #item.key == 1 then
- table.insert(mb, item.key)
- elseif type(item.key) == "table" then
- for _, k in ipairs(item.key) do
- if type(k) == "string" and #k == 1 then
- table.insert(mb, k)
- end
- end
- end
- end
- end
- end
- if #mb == 0 then
- return keys[1]
- end
-
- table.sort(mb, function(a, b)
- return (key2rank[a] or 999) < (key2rank[b] or 999)
- end)
- local idx = 1
- for _, key in ipairs(keys) do
- if idx > #mb or (key2rank[key] or 999) < (key2rank[mb[idx]] or 999) then
- return key
- end
- idx = idx + 1
- end
- return nil
-end
-
-action_save = function(path, is_temp)
- if path == nil or #path == 0 then
- return
- end
-
- local mb_path = get_state_attr("path")
- local all_bookmarks = get_all_bookmarks()
- local temp_bookmarks = get_temp_bookmarks()
- local path_obj
- if is_temp and temp_bookmarks and temp_bookmarks[path] then
- path_obj = temp_bookmarks[path]
- else
- path_obj = all_bookmarks[path] or (temp_bookmarks and temp_bookmarks[path])
- end
- local tag = path_obj and path_obj.tag or path:match(".*[\\/]([^\\/]+)[\\/]?$")
-
- while true do
- local title = is_temp and "Tag β¨alias nameβ© [TEMPORARY]" or "Tag β¨alias nameβ©"
- local value, event = ya.input({ title = title, value = tag, pos = { "top-center", y = 3, w = 40 } })
- if event ~= 1 then
- return
- end
- tag = value or ""
- if #tag == 0 then
- ya.notify({ title = "Bookmarks", content = "Empty tag", timeout = 1, level = "info" })
- else
- local tag_obj = nil
- for _, item in pairs(all_bookmarks) do
- if item.tag == tag then
- tag_obj = item
- break
- end
- end
- if not tag_obj and temp_bookmarks then
- for _, item in pairs(temp_bookmarks) do
- if item.tag == tag then
- tag_obj = item
- break
- end
- end
- end
- if tag_obj == nil or tag_obj.path == path then
- break
- end
- ya.notify({ title = "Bookmarks", content = "Duplicated tag", timeout = 1, level = "info" })
- end
- end
-
- local key = path_obj and path_obj.key or generate_key()
- local key_display = format_keys_for_display(key)
-
- while true do
- local value, event = ya.input({
- title = "Keys β¨space, comma or empty separatorβ©",
- value = key_display,
- pos = { "top-center", y = 3, w = 50 },
- })
- if event ~= 1 then
- return
- end
-
- local input_str = value or ""
- if input_str == "" then
- key = ""
- break
- end
-
- local parsed_keys = parse_keys_input(input_str)
- if #parsed_keys == 0 then
- key = ""
- break
- elseif #parsed_keys == 1 then
- key = parsed_keys[1]
- else
- key = parsed_keys
- end
-
- local new_seq = _seq_from_key(key)
- local conflict, conflict_seq
-
- local function check(items)
- for _, item in pairs(items or {}) do
- if item and item.key and item.path ~= path then
- local exist = _seq_from_key(item.key)
- if #exist > 0 then
- if _seq_equal(new_seq, exist) then
- conflict, conflict_seq = "duplicate", exist
- return true
- end
- if _seq_is_prefix(new_seq, exist) or _seq_is_prefix(exist, new_seq) then
- conflict, conflict_seq = "prefix", exist
- return true
- end
- end
- end
- end
- return false
- end
-
- if check(all_bookmarks) or check(temp_bookmarks) then
- local msg = (conflict == "duplicate") and ("Duplicated key sequence: " .. _seq_to_string(new_seq))
- or ("Ambiguous with existing sequence: " .. _seq_to_string(conflict_seq))
- ya.notify({ title = "Bookmarks", content = msg, timeout = 2, level = "info" })
- key_display = input_str
- else
- break
- end
- end
-
- if is_temp then
- set_temp_bookmarks(path, { tag = tag, path = path, key = key })
- ya.notify({ title = "Bookmarks", content = '[TEMP] "' .. tag .. '" saved', timeout = 1, level = "info" })
- else
- set_bookmarks(path, { tag = tag, path = path, key = key })
- local user_bookmarks = get_state_attr("bookmarks")
- save_to_file(mb_path, user_bookmarks)
- ya.notify({ title = "Bookmarks", content = '"' .. tag .. '" saved', timeout = 1, level = "info" })
- end
-end
-
-action_delete = function(path)
- if path == nil then
- return
- end
-
- local mb_path = get_state_attr("path")
- local user_bookmarks = get_state_attr("bookmarks")
- local temp_bookmarks = get_temp_bookmarks()
- local bookmark = temp_bookmarks[path] or user_bookmarks[path]
-
- if not bookmark then
- ya.notify({
- title = "Bookmarks",
- content = "Cannot delete: Not a user or temp bookmark",
- timeout = 2,
- level = "warn",
- })
- return
- end
- local tag = bookmark.tag
- local is_temp = temp_bookmarks[path] ~= nil
-
- if is_temp then
- set_temp_bookmarks(path, nil)
- ya.notify({ title = "Bookmarks", content = '[TEMP] "' .. tag .. '" deleted', timeout = 1, level = "info" })
- else
- set_bookmarks(path, nil)
- local updated_user_bookmarks = get_state_attr("bookmarks")
- save_to_file(mb_path, updated_user_bookmarks)
- ya.notify({ title = "Bookmarks", content = '"' .. tag .. '" deleted', timeout = 1, level = "info" })
- end
-end
-
-action_delete_multi = function(paths)
- if not paths or #paths == 0 then
- return
- end
-
- local mb_path = get_state_attr("path")
- local user_bookmarks = get_state_attr("bookmarks")
- local temp_bookmarks = get_temp_bookmarks()
-
- local deleted_count = 0
- local deleted_temp_count = 0
- local deleted_names = {}
- local not_found_count = 0
-
- for _, path in ipairs(paths) do
- local bookmark = temp_bookmarks[path] or user_bookmarks[path]
- if bookmark then
- local tag = bookmark.tag
- local is_temp = temp_bookmarks[path] ~= nil
-
- if is_temp then
- set_temp_bookmarks(path, nil)
- deleted_temp_count = deleted_temp_count + 1
- table.insert(deleted_names, "[TEMP] " .. tag)
- else
- set_bookmarks(path, nil)
- deleted_count = deleted_count + 1
- table.insert(deleted_names, tag)
- end
- else
- not_found_count = not_found_count + 1
- end
- end
-
- if deleted_count > 0 then
- local updated_user_bookmarks = get_state_attr("bookmarks")
- save_to_file(mb_path, updated_user_bookmarks)
- end
-
- local total_deleted = deleted_count + deleted_temp_count
- local message_parts = {}
-
- if total_deleted > 0 then
- table.insert(message_parts, string.format("Deleted %d bookmark(s)", total_deleted))
- if deleted_count > 0 and deleted_temp_count > 0 then
- table.insert(message_parts, string.format("(%d permanent, %d temporary)", deleted_count, deleted_temp_count))
- elseif deleted_temp_count > 0 then
- table.insert(message_parts, "(temporary)")
- end
- end
-
- if not_found_count > 0 then
- table.insert(message_parts, string.format("%d not found", not_found_count))
- end
-
- local final_message = table.concat(message_parts, ", ")
- if total_deleted > 0 then
- ya.notify({ title = "Bookmarks", content = final_message, timeout = 2, level = "info" })
- else
- ya.notify({ title = "Bookmarks", content = "No bookmarks were deleted", timeout = 1, level = "warn" })
- end
-end
-
-local action_delete_all = function(temp_only)
- local mb_path = get_state_attr("path")
- local title = temp_only and "Delete all temporary bookmarks? β¨y/nβ©" or "Delete all user bookmarks? β¨y/nβ©"
- local value, event = ya.input({ title = title, pos = { "top-center", y = 3, w = 45 } })
- if event ~= 1 or string.lower(value or "") ~= "y" then
- ya.notify({ title = "Bookmarks", content = "Cancel delete", timeout = 1, level = "info" })
- return
- end
-
- if temp_only then
- set_state_attr("temp_bookmarks", {})
- ya.notify({ title = "Bookmarks", content = "All temporary bookmarks deleted", timeout = 1, level = "info" })
- else
- set_state_attr("bookmarks", {})
- save_to_file(mb_path, {})
- ya.notify({ title = "Bookmarks", content = "All user-created bookmarks deleted", timeout = 1, level = "info" })
- end
-end
-
-return {
- setup = function(state, options)
- local default_path = (ya.target_family() == "windows" and os.getenv("APPDATA") .. "\\yazi\\config\\bookmarks")
- or (os.getenv("HOME") .. "/.config/yazi/bookmarks")
- local bookmarks_path = options.bookmarks_path or options.path
- if type(bookmarks_path) == "string" and bookmarks_path ~= "" then
- state.path = bookmarks_path
- else
- state.path = default_path
- end
- state.jump_notify = options.jump_notify == nil and false or options.jump_notify
- state.home_alias_enabled = options.home_alias_enabled == nil and true or options.home_alias_enabled
- state.path_truncate_enabled = options.path_truncate_enabled == nil and false or options.path_truncate_enabled
- state.path_max_depth = options.path_max_depth or 3
- state.fzf_path_truncate_enabled = options.fzf_path_truncate_enabled == nil and false
- or options.fzf_path_truncate_enabled
- state.fzf_path_max_depth = options.fzf_path_max_depth or 5
- state.path_truncate_long_names_enabled = options.path_truncate_long_names_enabled == nil and false
- or options.path_truncate_long_names_enabled
- state.fzf_path_truncate_long_names_enabled = options.fzf_path_truncate_long_names_enabled == nil and false
- or options.fzf_path_truncate_long_names_enabled
- state.path_max_folder_name_length = options.path_max_folder_name_length or 20
- state.fzf_path_max_folder_name_length = options.fzf_path_max_folder_name_length or 20
-
- state.history_size = options.history_size or 10
- state.history_fzf_path_truncate_enabled = options.history_fzf_path_truncate_enabled == nil and false
- or options.history_fzf_path_truncate_enabled
- state.history_fzf_path_max_depth = options.history_fzf_path_max_depth or 5
- state.history_fzf_path_truncate_long_names_enabled = options.history_fzf_path_truncate_long_names_enabled == nil
- and false
- or options.history_fzf_path_truncate_long_names_enabled
- state.history_fzf_path_max_folder_name_length = options.history_fzf_path_max_folder_name_length or 30
-
- local special_keys_options = options.special_keys or {}
- local special_keys = {}
- for name, default_key in pairs(DEFAULT_SPECIAL_KEYS) do
- local normalized = normalize_special_key(special_keys_options[name], default_key)
- if normalized ~= nil then
- special_keys[name] = normalized
- end
- 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
- local char = keys:sub(i, i)
- table.insert(state.keys, char)
- state.key2rank[char] = i
- end
-
- local function convert_simple_bookmarks(simple_bookmarks)
- local converted = {}
- local path_sep = package.config:sub(1, 1)
- local home_path = ya.target_family() == "windows" and os.getenv("USERPROFILE") or os.getenv("HOME")
-
- for _, bookmark in ipairs(simple_bookmarks or {}) do
- local path = bookmark.path
- if path:sub(1, 1) == "~" then
- path = home_path .. path:sub(2)
- end
-
- if ya.target_family() == "windows" then
- path = path:gsub("/", "\\")
- else
- path = path:gsub("\\", "/")
- end
-
- if path:sub(-1) ~= path_sep then
- path = path .. path_sep
- end
-
- converted[path] = {
- tag = bookmark.tag,
- path = path,
- key = bookmark.key,
- }
- end
-
- return converted
- end
-
- state.config_bookmarks = {}
-
- local bookmarks_to_process = options.bookmarks or {}
- if #bookmarks_to_process > 0 and bookmarks_to_process[1].tag then
- state.config_bookmarks = convert_simple_bookmarks(bookmarks_to_process)
- else
- for _, item in pairs(bookmarks_to_process) do
- state.config_bookmarks[item.path] = { tag = item.tag, path = item.path, key = item.key }
- end
- end
-
- local user_bookmarks = {}
- local file = io.open(state.path, "r")
- if file ~= nil then
- for line in file:lines() do
- local tag, path, key_str = string.match(line, "(.-)\t(.-)\t(.*)")
- if tag and path then
- local key = deserialize_key_from_file(key_str or "")
- user_bookmarks[path] = { tag = tag, path = path, key = key }
- end
- end
- file:close()
- end
- state.bookmarks = user_bookmarks
- save_to_file(state.path, state.bookmarks)
-
- state.temp_bookmarks = {}
- state.directory_history = {}
- state.last_paths = {}
- state.initialized_tabs = {}
-
- ps.sub("cd", function(body)
- local tab = body.tab or cx.tabs.idx
- local new_path = normalize_path(tostring(cx.active.current.cwd))
-
- if not state.initialized_tabs[tab] then
- state.last_paths[tab] = new_path
- state.initialized_tabs[tab] = true
- return
- end
-
- local previous_path = state.last_paths[tab]
-
- if previous_path and previous_path ~= new_path then
- add_to_history(tab, previous_path)
- end
-
- state.last_paths[tab] = new_path
- end)
- end,
-
- entry = function(self, jobs)
- local args = jobs.args or {}
- local action = args[1]
-
- if type(action) == "string" and action:sub(1, 9):lower() == "jump_key_" then
- jump_by_key_spec(action:sub(10))
- return
- end
-
- if not action then
- return
- end
-
- if action == "save" then
- if is_hovered_directory() then
- action_save(get_hovered_path(), false)
- else
- ya.notify({ title = "Bookmarks", content = "Selected item is not a directory", timeout = 2, level = "warn" })
- end
- elseif action == "save_cwd" then
- action_save(get_current_dir_path(), false)
- elseif action == "save_temp" then
- if is_hovered_directory() then
- action_save(get_hovered_path(), true)
- else
- ya.notify({ title = "Bookmarks", content = "Selected item is not a directory", timeout = 2, level = "warn" })
- end
- elseif action == "save_cwd_temp" then
- action_save(get_current_dir_path(), true)
- elseif action == "delete_by_key" then
- action_delete(which_find_deletable())
- elseif action == "delete_by_fzf" then
- action_delete_multi(fzf_find_multi())
- elseif action == "delete_multi_by_fzf" then
- action_delete_multi(fzf_find_multi())
- elseif action == "delete_all" then
- action_delete_all(false)
- elseif action == "delete_all_temp" then
- action_delete_all(true)
- elseif action == "jump_by_key" then
- action_jump(which_find())
- elseif action == "jump_by_fzf" then
- action_jump(fzf_find())
- elseif action == "rename_by_key" then
- local path = which_find()
- if path then
- local temp_b = get_temp_bookmarks()
- action_save(path, temp_b[path] ~= nil)
- end
- elseif action == "rename_by_fzf" then
- local path = fzf_find_for_rename()
- if path then
- local temp_b = get_temp_bookmarks()
- action_save(path, temp_b[path] ~= nil)
- end
- end
- end,
-}
+--- @since 26.1.4
+
+local path_sep = package.config:sub(1, 1)
+
+local DEFAULT_SPECIAL_KEYS = {
+ create_temp = "",
+ fuzzy_search = "",
+ history = "",
+ previous_dir = "",
+}
+
+local function notify(content, level, timeout)
+ ya.notify { title = "Bookmarks", content = content, timeout = timeout or 1, level = level or "info" }
+end
+
+local function default(value, fallback)
+ if value == nil then return fallback end
+ 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
+ local path = tostring(h.url)
+ if h.cha.is_dir then
+ if ya.target_family() == "windows" and path:match("^[A-Za-z]:$") then
+ return path .. "\\"
+ end
+ return path
+ end
+ return path
+ else
+ return ''
+ end
+end)
+
+local is_hovered_directory = ya.sync(function(state)
+ local h = cx.active.current.hovered
+ if h then
+ return h.cha.is_dir
+ end
+ return false
+end)
+
+local get_current_dir_path = ya.sync(function()
+ local path = tostring(cx.active.current.cwd)
+ if ya.target_family() == "windows" and path:match("^[A-Za-z]:$") then
+ return path .. "\\"
+ end
+ return path
+end)
+
+local get_state_attr = ya.sync(function(state, attr)
+ return state[attr]
+end)
+
+local set_state_attr = ya.sync(function(state, attr, value)
+ state[attr] = value
+end)
+
+local set_bookmarks = ya.sync(function(state, path, value)
+ state.bookmarks[path] = value
+end)
+
+local set_temp_bookmarks = ya.sync(function(state, path, value)
+ state.temp_bookmarks[path] = value
+end)
+
+local get_temp_bookmarks = ya.sync(function(state)
+ return state.temp_bookmarks
+end)
+
+local get_current_tab_idx = ya.sync(function(state)
+ return cx.tabs.idx
+end)
+
+local add_to_history = ya.sync(function(state, tab_idx, path)
+ if not state.directory_history[tab_idx] then
+ state.directory_history[tab_idx] = {}
+ end
+
+ local history = state.directory_history[tab_idx]
+ local history_size = state.history_size or 10
+
+ for i = #history, 1, -1 do
+ if history[i] == path then
+ table.remove(history, i)
+ end
+ end
+
+ table.insert(history, 1, path)
+
+ while #history > history_size do
+ table.remove(history, #history)
+ end
+end)
+
+local get_tab_history = ya.sync(function(state, tab_idx)
+ return state.directory_history[tab_idx] or {}
+end)
+
+local function ensure_directory(path)
+ local dir_path = path:match("(.+)[\\/][^\\/]*$")
+ if not dir_path then
+ return
+ end
+ if ya.target_family() == "windows" then
+ os.execute('mkdir "' .. dir_path:gsub("/", "\\") .. '" 2>nul')
+ else
+ os.execute('mkdir -p "' .. dir_path .. '"')
+ end
+end
+
+local function normalize_path(path)
+ local normalized_path = tostring(path):gsub("[\\/]+", path_sep)
+
+ if ya.target_family() == "windows" then
+ if normalized_path:match("^[A-Za-z]:[\\/]*$") then
+ normalized_path = normalized_path:gsub("^([A-Za-z]:)[\\/]*", "%1\\")
+ else
+ normalized_path = normalized_path:gsub("^([A-Za-z]:)[\\/]+", "%1\\")
+ normalized_path = normalized_path:gsub("[\\/]+$", "")
+ end
+ else
+ if normalized_path ~= "/" then
+ normalized_path = normalized_path:gsub("[\\/]+$", "")
+ end
+ end
+
+ return normalized_path
+end
+
+local function apply_home_alias(path)
+ if not path or path == "" then
+ return path
+ end
+
+ local home_alias_enabled = get_state_attr("home_alias_enabled")
+ if home_alias_enabled == false then
+ return path
+ end
+
+ if path:sub(1, 1) == "~" then
+ return path
+ end
+
+ local home = os.getenv("HOME")
+ if ya.target_family() == "windows" and (not home or home == "") then
+ home = os.getenv("USERPROFILE")
+ end
+ if not home or home == "" then
+ return path
+ end
+
+ local normalized_home = normalize_path(home)
+ if not normalized_home or normalized_home == "" then
+ return path
+ end
+
+ local sep = path_sep
+
+ if ya.target_family() == "windows" then
+ local path_lower = path:lower()
+ local home_lower = normalized_home:lower()
+ if path_lower == home_lower then
+ return "~"
+ end
+ local prefix_lower = (normalized_home .. sep):lower()
+ if path_lower:sub(1, #prefix_lower) == prefix_lower then
+ return "~" .. path:sub(#normalized_home + 1)
+ end
+ else
+ if path == normalized_home then
+ return "~"
+ end
+ local prefix = normalized_home .. sep
+ if path:sub(1, #prefix) == prefix then
+ return "~" .. path:sub(#normalized_home + 1)
+ end
+ end
+
+ return path
+end
+
+local function normalize_special_key(value, fallback)
+ if value == nil then
+ return fallback
+ end
+ if value == false then
+ return nil
+ end
+ if type(value) == "string" then
+ local trimmed = value:gsub("^%s*(.-)%s*$", "%1")
+ if trimmed == "" then
+ return nil
+ end
+ return trimmed
+ end
+ if type(value) == "table" then
+ local seq = {}
+ for _, item in ipairs(value) do
+ if type(item) == "string" then
+ local trimmed = item:gsub("^%s*(.-)%s*$", "%1")
+ if trimmed ~= "" then
+ table.insert(seq, trimmed)
+ end
+ end
+ end
+ if #seq == 0 then
+ return nil
+ end
+ return seq
+ end
+ return fallback
+end
+
+local function truncate_long_folder_names(path, max_folder_length)
+ if not max_folder_length or max_folder_length <= 0 then
+ return path
+ end
+
+ local separator = ya.target_family() == "windows" and "\\" or "/"
+ local parts = {}
+
+ for part in path:gmatch("[^" .. separator .. "]+") do
+ if #part > max_folder_length then
+ local keep_length = math.max(3, math.floor(max_folder_length * 0.4))
+ local truncated = part:sub(1, keep_length) .. "..."
+ table.insert(parts, truncated)
+ else
+ table.insert(parts, part)
+ end
+ end
+
+ local result = table.concat(parts, separator)
+
+ if path:sub(1, 1) == separator then
+ result = separator .. result
+ end
+
+ return result
+end
+
+local function truncate_path(path, max_parts)
+ max_parts = max_parts or 3
+ local normalized_path = normalize_path(path)
+ normalized_path = apply_home_alias(normalized_path)
+
+ local parts = {}
+ local separator = ya.target_family() == "windows" and "\\" or "/"
+
+ if ya.target_family() == "windows" then
+ local drive, rest = normalized_path:match("^([A-Za-z]:\\)(.*)$")
+ if drive then
+ table.insert(parts, drive)
+ if rest and rest ~= "" then
+ for part in rest:gmatch("[^\\]+") do
+ table.insert(parts, part)
+ end
+ end
+ else
+ for part in normalized_path:gmatch("[^\\]+") do
+ table.insert(parts, part)
+ end
+ end
+ else
+ if normalized_path:sub(1, 1) == "/" then
+ table.insert(parts, "/")
+ local rest = normalized_path:sub(2)
+ if rest ~= "" then
+ for part in rest:gmatch("[^/]+") do
+ table.insert(parts, part)
+ end
+ end
+ elseif normalized_path:sub(1, 1) == "~" then
+ table.insert(parts, "~")
+ local rest = normalized_path:sub(2)
+ if rest:sub(1, 1) == "/" then
+ rest = rest:sub(2)
+ end
+ if rest ~= "" then
+ for part in rest:gmatch("[^/]+") do
+ table.insert(parts, part)
+ end
+ end
+ else
+ for part in normalized_path:gmatch("[^/]+") do
+ table.insert(parts, part)
+ end
+ end
+ end
+
+ if #parts > max_parts then
+ local result_parts = {}
+ local first_part = parts[1]
+
+ if ya.target_family() == "windows" and first_part:match("^[A-Za-z]:\\$") then
+ first_part = first_part:sub(1, -2)
+ end
+
+ if ya.target_family() ~= "windows" and first_part == "/" then
+ table.insert(result_parts, "")
+ else
+ table.insert(result_parts, first_part)
+ end
+
+ table.insert(result_parts, "β¦")
+ for i = #parts - max_parts + 2, #parts do
+ table.insert(result_parts, parts[i])
+ end
+
+ local out = table.concat(result_parts, separator)
+ if ya.target_family() ~= "windows" then
+ out = out:gsub("^//+", "/")
+ end
+ return out
+ else
+ return normalized_path
+ end
+end
+
+local function path_to_desc(path)
+ local result_path = apply_home_alias(normalize_path(path))
+
+ if get_state_attr("path_truncate_long_names_enabled") == true then
+ local max_folder_length = get_state_attr("path_max_folder_name_length") or 20
+ result_path = truncate_long_folder_names(result_path, max_folder_length)
+ end
+
+ if get_state_attr("path_truncate_enabled") == true then
+ local max_depth = get_state_attr("path_max_depth") or 3
+ result_path = truncate_path(result_path, max_depth)
+ end
+
+ return result_path
+end
+
+local function get_display_width(str)
+ return ui.Line(str):width()
+end
+
+local function path_to_desc_for_fzf(path)
+ local result_path = apply_home_alias(normalize_path(path))
+
+ if get_state_attr("fzf_path_truncate_long_names_enabled") == true then
+ local max_folder_length = get_state_attr("fzf_path_max_folder_name_length") or 20
+ result_path = truncate_long_folder_names(result_path, max_folder_length)
+ end
+
+ if get_state_attr("fzf_path_truncate_enabled") == true then
+ local max_depth = get_state_attr("fzf_path_max_depth") or 5
+ result_path = truncate_path(result_path, max_depth)
+ end
+
+ return result_path
+end
+
+local function path_to_desc_for_history(path)
+ local result_path = apply_home_alias(normalize_path(path))
+
+ if get_state_attr("history_fzf_path_truncate_long_names_enabled") == true then
+ local max_folder_length = get_state_attr("history_fzf_path_max_folder_name_length") or 30
+ result_path = truncate_long_folder_names(result_path, max_folder_length)
+ end
+
+ if get_state_attr("history_fzf_path_truncate_enabled") == true then
+ local max_depth = get_state_attr("history_fzf_path_max_depth") or 5
+ result_path = truncate_path(result_path, max_depth)
+ end
+
+ return result_path
+end
+
+local function format_bookmark_for_fzf(tag, path, key, max_tag_width, max_path_width)
+ local tag_width = math.max(max_tag_width, 15)
+ local path_width = math.max(max_path_width or 30, 30)
+
+ local formatted_tag = tag
+ local tag_display_width = get_display_width(tag)
+ if tag_display_width > tag_width then
+ formatted_tag = tag:sub(1, tag_width - 3) .. "..."
+ else
+ formatted_tag = tag .. string.rep(" ", tag_width - tag_display_width)
+ end
+
+ local display_path = path_to_desc_for_fzf(path)
+ local formatted_path = display_path
+ local path_display_width = get_display_width(display_path)
+ if path_display_width > path_width then
+ formatted_path = display_path:sub(1, path_width - 3) .. "..."
+ else
+ formatted_path = display_path .. string.rep(" ", path_width - path_display_width)
+ end
+
+ local key_display = ""
+ if key then
+ if type(key) == "table" then
+ key_display = table.concat(key, ",")
+ elseif type(key) == "string" and #key > 0 then
+ key_display = key
+ else
+ key_display = tostring(key)
+ end
+ end
+
+ return formatted_tag .. " " .. formatted_path .. " " .. key_display
+end
+
+local function sort_bookmarks(bookmarks, key1, key2, reverse)
+ reverse = reverse or false
+ table.sort(bookmarks, function(x, y)
+ if not x or not y then return false end
+ local x_key1, y_key1 = x[key1], y[key1]
+ local x_key2, y_key2 = x[key2], y[key2]
+ if x_key1 == nil and y_key1 == nil then
+ if x_key2 == nil and y_key2 == nil then
+ return false
+ elseif x_key2 == nil then
+ return false
+ elseif y_key2 == nil then
+ return true
+ else
+ return tostring(x_key2) < tostring(y_key2)
+ end
+ elseif x_key1 == nil then
+ return false
+ elseif y_key1 == nil then
+ return true
+ else
+ return tostring(x_key1) < tostring(y_key1)
+ end
+ end)
+ if reverse then
+ local n = #bookmarks
+ for i = 1, math.floor(n / 2) do
+ bookmarks[i], bookmarks[n - i + 1] = bookmarks[n - i + 1], bookmarks[i]
+ end
+ end
+ return bookmarks
+end
+
+local action_save, action_jump, action_delete, action_delete_multi
+local which_find, which_find_deletable
+local fzf_find, fzf_find_for_rename, fzf_find_multi, fzf_history
+
+local function get_all_bookmarks()
+ local all_b = {}
+ local config_b = get_state_attr("config_bookmarks")
+ local user_b = get_state_attr("bookmarks")
+
+ for path, item in pairs(config_b) do
+ all_b[path] = item
+ end
+ for path, item in pairs(user_b) do
+ all_b[path] = item
+ end
+ return all_b
+end
+
+local function serialize_key_for_file(key)
+ if type(key) == "table" then
+ return table.concat(key, ",")
+ elseif type(key) == "string" then
+ return key
+ else
+ return tostring(key)
+ end
+end
+
+local function deserialize_key_from_file(key_str)
+ if not key_str or key_str == "" then
+ return ""
+ end
+
+ key_str = key_str:gsub("^%s*(.-)%s*$", "%1")
+ if key_str == "" then
+ return ""
+ end
+
+ if key_str:find(",") then
+ local seq = {}
+ for raw_token in key_str:gmatch("[^,%s]+") do
+ local token = raw_token:gsub("^%s*(.-)%s*$", "%1")
+ if token ~= "" then
+ if token:match("^<.->$") then
+ table.insert(seq, token)
+ else
+ for _, cp in utf8.codes(token) do
+ table.insert(seq, utf8.char(cp))
+ end
+ end
+ end
+ end
+ return seq
+ end
+
+ if key_str:match("^<.->$") then
+ return key_str
+ end
+
+ if utf8.len(key_str) > 1 then
+ local seq = {}
+ for _, cp in utf8.codes(key_str) do
+ table.insert(seq, utf8.char(cp))
+ end
+ return seq
+ else
+ return key_str
+ end
+end
+
+local save_to_file = function(mb_path, bookmarks)
+ ensure_directory(mb_path)
+ local file = io.open(mb_path, "w")
+ if file == nil then
+ notify("Cannot create bookmark file: " .. mb_path, "error", 2)
+ return
+ end
+ local array = {}
+ for _, item in pairs(bookmarks) do
+ table.insert(array, item)
+ end
+ 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))
+ end
+ file:close()
+end
+
+-- Unified fzf bookmark picker. opts:
+-- source: "all" (config + user) or "user" (user only). Default: "all".
+-- multi: boolean β allow multi-selection (TAB).
+-- prompt: fzf prompt label (e.g. "Search > ").
+-- empty_msg: text shown via `echo ... | fzf` when there are no items.
+-- Returns: array of selected paths (length 0 if cancelled or no items).
+local function run_fzf_picker(opts)
+ local source = opts.source or "all"
+ local prompt = opts.prompt or "Search > "
+ local empty_msg = opts.empty_msg or "No bookmarks found"
+
+ local temp_bookmarks = get_temp_bookmarks()
+ local perm_bookmarks = source == "user" and get_state_attr("bookmarks") or get_all_bookmarks()
+
+ 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 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
+
+ if #items > 0 then
+ for _, item in ipairs(items) do
+ local formatted_line = format_bookmark_for_fzf(item.tag, item.path, item.key, max_tag_w, max_path_w)
+ temp_file:write(formatted_line .. "\t" .. item.path .. "\n")
+ end
+ temp_file:close()
+ cmd = string.format("fzf %s%s --with-nth=1 --prompt=\"%s\" < \"%s\"",
+ opts.multi and "--multi " or "", get_fzf_delimiter(), prompt, temp_file_path)
+ else
+ temp_file:close()
+ cmd = string.format("echo %s | fzf --prompt=\"%s\"", empty_msg, prompt)
+ end
+ else
+ cmd = string.format("echo %s | fzf --prompt=\"%s\"", empty_msg, prompt)
+ 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
+ permit:drop()
+
+ local paths = {}
+ for raw_line in raw:gmatch("[^\r\n]+") do
+ local line = string.gsub(raw_line, "^%s*(.-)%s*$", "%1")
+ if line ~= "" and line ~= empty_msg then
+ local tab_pos = line:find("\t")
+ if tab_pos then
+ table.insert(paths, line:sub(tab_pos + 1))
+ end
+ end
+ end
+ return paths
+end
+
+fzf_find = function()
+ return run_fzf_picker({ source = "all", prompt = "Search > ", empty_msg = "No bookmarks found" })[1]
+end
+
+fzf_find_for_rename = function()
+ return run_fzf_picker({ source = "all", prompt = "Rename > ", empty_msg = "No bookmarks found" })[1]
+end
+
+fzf_find_multi = function()
+ return run_fzf_picker({ source = "user", multi = true, prompt = "Delete > ", empty_msg = "No deletable bookmarks found" })
+end
+
+fzf_history = 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
+ for _, path in ipairs(history) do
+ if path ~= current_path then
+ table.insert(filtered_history, path)
+ end
+ end
+ end
+
+ if not filtered_history or #filtered_history == 0 then
+ return nil
+ 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")
+ 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
+end
+
+local create_special_menu_items = function()
+ local special_items = {}
+ local special_keys = get_state_attr("special_keys") or DEFAULT_SPECIAL_KEYS
+ 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__" })
+ end
+
+ local fuzzy_search_key = special_keys.fuzzy_search
+ if fuzzy_search_key then
+ table.insert(special_items, { desc = "Fuzzy search", on = fuzzy_search_key, path = "__FUZZY_SEARCH__" })
+ end
+
+ 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
+ for _, path in ipairs(history) do
+ if path ~= current_path then
+ table.insert(filtered_history, path)
+ end
+ end
+ end
+
+ local history_key = special_keys.history
+ if history_key and filtered_history and #filtered_history > 0 then
+ table.insert(special_items, { desc = "Directory history", on = history_key, path = "__HISTORY__" })
+ end
+
+ local previous_dir_key = special_keys.previous_dir
+ if previous_dir_key and filtered_history and filtered_history[1] then
+ local previous_dir = filtered_history[1]
+ local display_path = path_to_desc(previous_dir)
+ table.insert(special_items, { desc = "<- " .. display_path, on = previous_dir_key, path = previous_dir })
+ end
+
+ return special_items
+end
+
+-- Unified `ya.which`-based bookmark picker. opts:
+-- source: "all" (config + user) or "user" (user only).
+-- include_special: prepend create_special_menu_items() candidates.
+-- empty_msg: notification text shown when there are no key-bound bookmarks.
+-- Returns: selected path or nil.
+local function pick_bookmark(opts)
+ local perm_bookmarks = opts.source == "user" and get_state_attr("bookmarks") or get_all_bookmarks()
+ local temp_bookmarks = get_temp_bookmarks()
+
+ local items = {}
+ local function collect(bookmarks, prefix)
+ for path, item in pairs(bookmarks or {}) do
+ if item and item.tag and #item.tag ~= 0 then
+ table.insert(items, {
+ tag = prefix .. item.tag,
+ path = item.path or path,
+ key = item.key or "",
+ })
+ end
+ end
+ end
+ collect(temp_bookmarks, "[TEMP] ")
+ collect(perm_bookmarks, "")
+
+ local cands_bookmarks = {}
+ for _, item in ipairs(items) do
+ if item.key and item.key ~= "" and
+ (type(item.key) == "string" or (type(item.key) == "table" and #item.key > 0)) then
+ table.insert(cands_bookmarks, { desc = item.tag, on = item.key, path = item.path })
+ end
+ end
+
+ sort_bookmarks(cands_bookmarks, "on", "desc", false)
+
+ local cands_static = opts.include_special and create_special_menu_items() or {}
+ local cands = {}
+ for _, item in ipairs(cands_static) do table.insert(cands, item) end
+ for _, item in ipairs(cands_bookmarks) do table.insert(cands, item) end
+
+ if #cands_bookmarks == 0 then
+ notify(opts.empty_msg)
+ end
+ if #cands == 0 then return nil end
+
+ local idx = ya.which { cands = cands }
+ if idx == nil then return nil end
+ return cands[idx].path
+end
+
+which_find = function()
+ return pick_bookmark({ source = "all", include_special = true, empty_msg = "No bookmarks found" })
+end
+
+which_find_deletable = function()
+ return pick_bookmark({ source = "user", include_special = false, empty_msg = "No deletable bookmarks found" })
+end
+
+action_jump = function(path)
+ if path == nil then return end
+
+ local jump_notify = get_state_attr("jump_notify")
+ local all_bookmarks = get_all_bookmarks()
+ local temp_bookmarks = get_temp_bookmarks()
+
+ if path == "__CREATE_TEMP__" then
+ action_save(get_current_dir_path(), true)
+ return
+ elseif path == "__FUZZY_SEARCH__" then
+ local selected_path = fzf_find()
+ if selected_path then action_jump(selected_path) end
+ return
+ elseif path == "__HISTORY__" then
+ local selected_path = fzf_history()
+ if selected_path then action_jump(selected_path) end
+ return
+ end
+
+ local bookmark = temp_bookmarks[path] or all_bookmarks[path]
+ if not bookmark then
+ ya.emit("cd", { path })
+ if jump_notify then notify('Jump to "' .. path_to_desc(path) .. '"') end
+ return
+ end
+
+ local tag = bookmark.tag
+ local is_temp = temp_bookmarks[path] ~= nil
+
+ ya.emit("cd", { path })
+
+ if jump_notify then
+ local prefix = is_temp and "[TEMP] " or ""
+ notify('Jump to "' .. prefix .. tag .. '"')
+ end
+end
+
+local function parse_keys_input(input)
+ if not input or input == "" then return {} end
+ local seq = {}
+ for raw_token in input:gmatch("[^,%s]+") do
+ local token = raw_token:gsub("^%s*(.-)%s*$", "%1")
+ if token ~= "" then
+ if token:match("^<.->$") then
+ table.insert(seq, token)
+ else
+ for _, cp in utf8.codes(token) do
+ table.insert(seq, utf8.char(cp))
+ end
+ end
+ end
+ end
+ return seq
+end
+
+local function format_keys_for_display(keys)
+ if type(keys) == "table" then
+ return table.concat(keys, ",")
+ elseif type(keys) == "string" then
+ return keys
+ else
+ return ""
+ end
+end
+
+local function _seq_from_key(k)
+ if type(k) == "table" then
+ local out = {}
+ for _, t in ipairs(k) do
+ if t:match("^<.->$") then
+ table.insert(out, t)
+ else
+ for _, cp in utf8.codes(t) do
+ table.insert(out, utf8.char(cp))
+ end
+ end
+ end
+ return out
+ elseif type(k) == "string" then
+ return parse_keys_input(k)
+ else
+ return {}
+ end
+end
+
+local function _seq_equal(a, b)
+ if #a ~= #b then return false end
+ for i = 1, #a do if a[i] ~= b[i] then return false end end
+ return true
+end
+
+local function _seq_is_prefix(short, long)
+ if #short >= #long then return false end
+ for i = 1, #short do if short[i] ~= long[i] then return false end end
+ return true
+end
+
+local function _seq_to_string(seq)
+ return table.concat(seq, ",")
+end
+
+local function find_path_by_key_sequence(seq)
+ if not seq or #seq == 0 then return nil end
+
+ local function matches(candidate)
+ if candidate == nil or candidate == "" then return false end
+ local candidate_seq = _seq_from_key(candidate)
+ if #candidate_seq == 0 then return false end
+ return _seq_equal(seq, candidate_seq)
+ end
+
+ for _, item in ipairs(create_special_menu_items() or {}) do
+ if matches(item.on) then
+ return item.path
+ end
+ end
+
+ local temp = get_temp_bookmarks()
+ for path, item in pairs(temp or {}) do
+ if matches(item.key) then
+ return path
+ end
+ end
+
+ local bookmarks = get_all_bookmarks()
+ for path, item in pairs(bookmarks or {}) do
+ if matches(item.key) then
+ return path
+ end
+ end
+
+ return nil
+end
+
+local function jump_by_key_spec(spec)
+ local cleaned = (spec or ""):gsub("^%s*(.-)%s*$", "%1")
+ if cleaned == "" then
+ notify("Missing key sequence", "warn")
+ return false
+ end
+
+ local seq = parse_keys_input(cleaned)
+ if #seq == 0 then
+ notify("Missing key sequence", "warn")
+ return false
+ end
+
+ local path = find_path_by_key_sequence(seq)
+ if not path then
+ notify("Bookmark not found for key: " .. _seq_to_string(seq))
+ return false
+ end
+
+ action_jump(path)
+ return true
+end
+
+local generate_key = function()
+ local keys = get_state_attr("keys")
+ local key2rank = get_state_attr("key2rank")
+ local bookmarks = get_all_bookmarks()
+ local temp_bookmarks = get_temp_bookmarks()
+
+ local mb = {}
+ for _, item in pairs(bookmarks) do
+ if item and item.key then
+ if type(item.key) == "string" and #item.key == 1 then
+ table.insert(mb, item.key)
+ elseif type(item.key) == "table" then
+ for _, k in ipairs(item.key) do
+ if type(k) == "string" and #k == 1 then
+ table.insert(mb, k)
+ end
+ end
+ end
+ end
+ end
+ if temp_bookmarks then
+ for _, item in pairs(temp_bookmarks) do
+ if item and item.key then
+ if type(item.key) == "string" and #item.key == 1 then
+ table.insert(mb, item.key)
+ elseif type(item.key) == "table" then
+ for _, k in ipairs(item.key) do
+ if type(k) == "string" and #k == 1 then
+ table.insert(mb, k)
+ end
+ end
+ end
+ end
+ end
+ end
+ if #mb == 0 then return keys[1] end
+
+ table.sort(mb, function(a, b) return (key2rank[a] or 999) < (key2rank[b] or 999) end)
+ local idx = 1
+ for _, key in ipairs(keys) do
+ if idx > #mb or (key2rank[key] or 999) < (key2rank[mb[idx]] or 999) then return key end
+ idx = idx + 1
+ end
+ return nil
+end
+
+action_save = function(path, is_temp)
+ if path == nil or #path == 0 then return end
+
+ local mb_path = get_state_attr("path")
+ local all_bookmarks = get_all_bookmarks()
+ local temp_bookmarks = get_temp_bookmarks()
+ local path_obj
+ if is_temp and temp_bookmarks and temp_bookmarks[path] then
+ path_obj = temp_bookmarks[path]
+ else
+ path_obj = all_bookmarks[path] or (temp_bookmarks and temp_bookmarks[path])
+ end
+ local tag = path_obj and path_obj.tag or path:match(".*[\\/]([^\\/]+)[\\/]?$")
+
+ while true do
+ local title = is_temp and "Tag β¨alias nameβ© [TEMPORARY]" or "Tag β¨alias nameβ©"
+ local value, event = ya.input({ title = title, value = tag, pos = { "top-center", y = 3, w = 40 } })
+ if event ~= 1 then return end
+ tag = value or ''
+ if #tag == 0 then
+ notify("Empty tag")
+ else
+ local tag_obj = nil
+ for _, item in pairs(all_bookmarks) do
+ if item.tag == tag then
+ tag_obj = item; break
+ end
+ end
+ if not tag_obj and temp_bookmarks then
+ for _, item in pairs(temp_bookmarks) do
+ if item.tag == tag then
+ tag_obj = item; break
+ end
+ end
+ end
+ if tag_obj == nil or tag_obj.path == path then break end
+ notify("Duplicated tag")
+ end
+ end
+
+ local key = path_obj and path_obj.key or generate_key()
+ local key_display = format_keys_for_display(key)
+
+ while true do
+ local value, event = ya.input({
+ title = "Keys β¨space, comma or empty separatorβ©",
+ value = key_display,
+ pos = { "top-center", y = 3, w = 50 }
+ })
+ if event ~= 1 then return end
+
+ local input_str = value or ""
+ if input_str == "" then
+ key = ""
+ break
+ end
+
+ local parsed_keys = parse_keys_input(input_str)
+ if #parsed_keys == 0 then
+ key = ""
+ break
+ elseif #parsed_keys == 1 then
+ key = parsed_keys[1]
+ else
+ key = parsed_keys
+ end
+
+ local new_seq = _seq_from_key(key)
+ local conflict, conflict_seq
+
+ local function check(items)
+ for _, item in pairs(items or {}) do
+ if item and item.key and item.path ~= path then
+ local exist = _seq_from_key(item.key)
+ if #exist > 0 then
+ if _seq_equal(new_seq, exist) then
+ conflict, conflict_seq = "duplicate", exist; return true
+ end
+ if _seq_is_prefix(new_seq, exist) or _seq_is_prefix(exist, new_seq) then
+ conflict, conflict_seq = "prefix", exist; return true
+ end
+ end
+ end
+ end
+ return false
+ end
+
+ if check(all_bookmarks) or check(temp_bookmarks) then
+ local msg = (conflict == "duplicate")
+ and ("Duplicated key sequence: " .. _seq_to_string(new_seq))
+ or ("Ambiguous with existing sequence: " .. _seq_to_string(conflict_seq))
+ notify(msg, "info", 2)
+ key_display = input_str
+ else
+ break
+ end
+ end
+
+ if is_temp then
+ set_temp_bookmarks(path, { tag = tag, path = path, key = key })
+ notify('[TEMP] "' .. tag .. '" saved')
+ 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')
+ end
+end
+
+action_delete = function(path)
+ if path == nil then return end
+
+ local mb_path = get_state_attr("path")
+ local user_bookmarks = get_state_attr("bookmarks")
+ local temp_bookmarks = get_temp_bookmarks()
+ local bookmark = temp_bookmarks[path] or user_bookmarks[path]
+
+ if not bookmark then
+ notify('Cannot delete: Not a user or temp bookmark', "warn", 2)
+ return
+ end
+ local tag = bookmark.tag
+ local is_temp = temp_bookmarks[path] ~= nil
+
+ if is_temp then
+ set_temp_bookmarks(path, nil)
+ notify('[TEMP] "' .. tag .. '" deleted')
+ else
+ set_bookmarks(path, nil)
+ local updated_user_bookmarks = get_state_attr("bookmarks")
+ save_to_file(mb_path, updated_user_bookmarks)
+ notify('"' .. tag .. '" deleted')
+ end
+end
+
+action_delete_multi = function(paths)
+ if not paths or #paths == 0 then return end
+
+ local mb_path = get_state_attr("path")
+ local user_bookmarks = get_state_attr("bookmarks")
+ local temp_bookmarks = get_temp_bookmarks()
+
+ local deleted_count = 0
+ local deleted_temp_count = 0
+ local deleted_names = {}
+ local not_found_count = 0
+
+ for _, path in ipairs(paths) do
+ local bookmark = temp_bookmarks[path] or user_bookmarks[path]
+ if bookmark then
+ local tag = bookmark.tag
+ local is_temp = temp_bookmarks[path] ~= nil
+
+ if is_temp then
+ set_temp_bookmarks(path, nil)
+ deleted_temp_count = deleted_temp_count + 1
+ table.insert(deleted_names, "[TEMP] " .. tag)
+ else
+ set_bookmarks(path, nil)
+ deleted_count = deleted_count + 1
+ table.insert(deleted_names, tag)
+ end
+ else
+ not_found_count = not_found_count + 1
+ end
+ end
+
+ if deleted_count > 0 then
+ local updated_user_bookmarks = get_state_attr("bookmarks")
+ save_to_file(mb_path, updated_user_bookmarks)
+ end
+
+ local total_deleted = deleted_count + deleted_temp_count
+ local message_parts = {}
+
+ if total_deleted > 0 then
+ table.insert(message_parts, string.format("Deleted %d bookmark(s)", total_deleted))
+ if deleted_count > 0 and deleted_temp_count > 0 then
+ table.insert(message_parts, string.format("(%d permanent, %d temporary)", deleted_count, deleted_temp_count))
+ elseif deleted_temp_count > 0 then
+ table.insert(message_parts, "(temporary)")
+ end
+ end
+
+ if not_found_count > 0 then
+ table.insert(message_parts, string.format("%d not found", not_found_count))
+ end
+
+ local final_message = table.concat(message_parts, ", ")
+ if total_deleted > 0 then
+ notify(final_message, "info", 2)
+ else
+ notify("No bookmarks were deleted", "warn")
+ end
+end
+
+local action_delete_all = function(temp_only)
+ local mb_path = get_state_attr("path")
+ local title = temp_only and "Delete all temporary bookmarks? β¨y/nβ©" or "Delete all user bookmarks? β¨y/nβ©"
+ local value, event = ya.input({ title = title, pos = { "top-center", y = 3, w = 45 } })
+ if event ~= 1 or string.lower(value or "") ~= "y" then
+ notify("Cancel delete")
+ return
+ end
+
+ if temp_only then
+ set_state_attr("temp_bookmarks", {})
+ notify("All temporary bookmarks deleted")
+ else
+ set_state_attr("bookmarks", {})
+ save_to_file(mb_path, {})
+ notify("All user-created bookmarks deleted")
+ end
+end
+
+return {
+ setup = function(state, options)
+ local default_path = (ya.target_family() == "windows" and os.getenv("APPDATA") .. "\\yazi\\config\\bookmarks") or
+ (os.getenv("HOME") .. "/.config/yazi/bookmarks")
+ local bookmarks_path = options.bookmarks_path or options.path
+ if type(bookmarks_path) == "string" and bookmarks_path ~= '' then
+ state.path = bookmarks_path
+ else
+ state.path = default_path
+ end
+
+ state.jump_notify = default(options.jump_notify, false)
+ state.home_alias_enabled = default(options.home_alias_enabled, true)
+ state.path_truncate_enabled = default(options.path_truncate_enabled, false)
+ state.path_max_depth = options.path_max_depth or 3
+ state.fzf_path_truncate_enabled = default(options.fzf_path_truncate_enabled, false)
+ state.fzf_path_max_depth = options.fzf_path_max_depth or 5
+ state.path_truncate_long_names_enabled = default(options.path_truncate_long_names_enabled, false)
+ state.fzf_path_truncate_long_names_enabled = default(options.fzf_path_truncate_long_names_enabled, false)
+ state.path_max_folder_name_length = options.path_max_folder_name_length or 20
+ state.fzf_path_max_folder_name_length = options.fzf_path_max_folder_name_length or 20
+
+ state.history_size = options.history_size or 10
+ state.history_fzf_path_truncate_enabled = default(options.history_fzf_path_truncate_enabled, false)
+ state.history_fzf_path_max_depth = options.history_fzf_path_max_depth or 5
+ state.history_fzf_path_truncate_long_names_enabled = default(options.history_fzf_path_truncate_long_names_enabled, false)
+ state.history_fzf_path_max_folder_name_length = options.history_fzf_path_max_folder_name_length or 30
+
+ local special_keys_options = options.special_keys or {}
+ local special_keys = {}
+ for name, default_key in pairs(DEFAULT_SPECIAL_KEYS) do
+ local normalized = normalize_special_key(special_keys_options[name], default_key)
+ if normalized ~= nil then
+ special_keys[name] = normalized
+ end
+ 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
+ local char = keys:sub(i, i)
+ table.insert(state.keys, char)
+ state.key2rank[char] = i
+ end
+
+ local function convert_simple_bookmarks(simple_bookmarks)
+ local converted = {}
+ local home_path = ya.target_family() == "windows" and os.getenv("USERPROFILE") or os.getenv("HOME")
+
+ for _, bookmark in ipairs(simple_bookmarks or {}) do
+ local path = bookmark.path
+ if path:sub(1, 1) == "~" then
+ path = home_path .. path:sub(2)
+ end
+
+ if ya.target_family() == "windows" then
+ path = path:gsub("/", "\\")
+ else
+ path = path:gsub("\\", "/")
+ end
+
+ if path:sub(-1) ~= path_sep then
+ path = path .. path_sep
+ end
+
+ converted[path] = {
+ tag = bookmark.tag,
+ path = path,
+ key = bookmark.key
+ }
+ end
+
+ return converted
+ end
+
+ state.config_bookmarks = {}
+
+ local bookmarks_to_process = options.bookmarks or {}
+ if #bookmarks_to_process > 0 and bookmarks_to_process[1].tag then
+ state.config_bookmarks = convert_simple_bookmarks(bookmarks_to_process)
+ else
+ for _, item in pairs(bookmarks_to_process) do
+ state.config_bookmarks[item.path] = { tag = item.tag, path = item.path, key = item.key }
+ end
+ end
+
+ local user_bookmarks = {}
+ local file = io.open(state.path, "r")
+ if file ~= nil then
+ for line in file:lines() do
+ local tag, path, key_str = string.match(line, "(.-)\t(.-)\t(.*)")
+ if tag and path then
+ local key = deserialize_key_from_file(key_str or "")
+ user_bookmarks[path] = { tag = tag, path = path, key = key }
+ end
+ end
+ file:close()
+ end
+ state.bookmarks = user_bookmarks
+ save_to_file(state.path, state.bookmarks)
+
+ state.temp_bookmarks = {}
+ state.directory_history = {}
+ state.last_paths = {}
+ state.initialized_tabs = {}
+
+ ps.sub("cd", function(body)
+ local tab = body.tab or cx.tabs.idx
+ local new_path = normalize_path(tostring(cx.active.current.cwd))
+
+ if not state.initialized_tabs[tab] then
+ state.last_paths[tab] = new_path
+ state.initialized_tabs[tab] = true
+ return
+ end
+
+ local previous_path = state.last_paths[tab]
+
+ if previous_path and previous_path ~= new_path then
+ add_to_history(tab, previous_path)
+ end
+
+ state.last_paths[tab] = new_path
+ end)
+ end,
+
+ entry = function(self, jobs)
+ local args = jobs.args or {}
+ local action = args[1]
+
+ if type(action) == "string" and action:sub(1, 9):lower() == "jump_key_" then
+ jump_by_key_spec(action:sub(10))
+ return
+ end
+
+ if not action then return end
+
+ if action == "save" then
+ if is_hovered_directory() then
+ action_save(get_hovered_path(), false)
+ else
+ notify("Selected item is not a directory", "warn", 2)
+ end
+ elseif action == "save_cwd" then
+ action_save(get_current_dir_path(), false)
+ elseif action == "save_temp" then
+ if is_hovered_directory() then
+ action_save(get_hovered_path(), true)
+ else
+ notify("Selected item is not a directory", "warn", 2)
+ end
+ elseif action == "save_cwd_temp" then
+ action_save(get_current_dir_path(), true)
+ elseif action == "delete_by_key" then
+ action_delete(which_find_deletable())
+ elseif action == "delete_by_fzf" then
+ action_delete_multi(fzf_find_multi())
+ elseif action == "delete_all" then
+ action_delete_all(false)
+ elseif action == "delete_all_temp" then
+ action_delete_all(true)
+ elseif action == "jump_by_key" then
+ action_jump(which_find())
+ elseif action == "jump_by_fzf" then
+ action_jump(fzf_find())
+ elseif action == "rename_by_key" then
+ local path = which_find()
+ if path then
+ local temp_b = get_temp_bookmarks()
+ action_save(path, temp_b[path] ~= nil)
+ end
+ elseif action == "rename_by_fzf" then
+ local path = fzf_find_for_rename()
+ if path then
+ local temp_b = get_temp_bookmarks()
+ action_save(path, temp_b[path] ~= nil)
+ end
+ end
+ end,
+}
diff --git a/yazi/.config/yazi/yazi.toml b/yazi/.config/yazi/yazi.toml
index f5e3f87..4407362 100644
--- a/yazi/.config/yazi/yazi.toml
+++ b/yazi/.config/yazi/yazi.toml
@@ -46,19 +46,19 @@ rules = [
{ mime = "application/x-shellscript", use = "edit" },
{ mime = "application/javascript", use = "edit" },
# Fallback by extension
- { name = "*.sh", use = "edit" },
- { name = "*.json", use = "edit" },
- { name = "*.md", use = "edit" },
- { name = "*.txt", use = "edit" },
- { name = "*.css", use = "edit" },
- { name = "*.js", use = "edit" },
- { name = "*.toml", use = "edit" },
- { name = "*.yaml", use = "edit" },
- { name = "*.yml", use = "edit" },
- { name = "*.tex", use = "edit" }, # Uncommented!
- { name = "*.text", use = "edit" },
- { name = "*.epub", use = "edit" },
- { name = "*,kdl", use = "edit" },
+ { 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" },
@@ -76,13 +76,13 @@ suppress_preload = false
[plugin]
prepend_fetchers = [
- { id = "mime", url = "*", run = "mime-ext", prio = "high" },
+ { group = "mime", id = "mime", url = "*", run = "mime-ext", prio = "high" },
# git plugin
- { id = "git", url = "*", run = "git" },
- { id = "git", url = "*/", run = "git" },
+ { group = "mime", id = "git", url = "*", run = "git" },
+ { group = "mime", id = "git", url = "*/", run = "git" },
# mime plugin
- { id = "simple-tag", url = "*", run = "simple-tag" },
- { id = "simple-tag", url = "*/", run = "simple-tag" },
+ { group = "mime", id = "simple-tag", url = "*", run = "simple-tag" },
+ { group = "mime", id = "simple-tag", url = "*/", run = "simple-tag" },
]
preloaders = [
# Image
@@ -108,19 +108,19 @@ spotters = [
{ url = "*", run = "file-extra-metadata" },
]
previewers = [
- { name = "*/", run = "folder", sync = true },
+ { url = "*/", run = "folder", sync = true },
# Code
- { mime = "text/*", run = "code" },
- { mime = "*/{xml,javascript,wine-extension-ini}", run = "code" },
+ { url = "text/*", run = "code" },
+ { url = "*/{xml,javascript,wine-extension-ini}", run = "code" },
# JSON
- { mime = "application/{json,ndjson}", run = "json" },
+ { url = "application/{json,ndjson}", run = "json" },
# Image
- { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" },
- { mime = "image/*", run = "image" },
+ { url = "image/{avif,hei?,jxl,svg+xml}", run = "magick" },
+ { url = "image/*", run = "image" },
# # Video
# { mime = "video/*", run = "video" },
# PDF
- { mime = "application/pdf", run = "pdf" },
+ { url = "application/pdf", run = "pdf" },
# extra-metadata plugin
{ url = "*", run = "file-extra-metadata" },
]