From 658498860e874fe1ad49f2cb77a44342cd7a6580 Mon Sep 17 00:00:00 2001 From: liph Date: Tue, 12 May 2026 23:21:51 +0200 Subject: [PATCH] updated yazi --- yazi/.config/yazi/init.lua | 1 + yazi/.config/yazi/plugins/whoosh.yazi/LICENSE | 42 +- .../yazi/plugins/whoosh.yazi/README-RU.md | 458 +++ .../yazi/plugins/whoosh.yazi/README.md | 2 +- .../yazi/plugins/whoosh.yazi/bookmarks | 26 +- .../.config/yazi/plugins/whoosh.yazi/main.lua | 3134 ++++++++--------- yazi/.config/yazi/yazi.toml | 50 +- 7 files changed, 1901 insertions(+), 1812 deletions(-) create mode 100644 yazi/.config/yazi/plugins/whoosh.yazi/README-RU.md 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" }, ]