z это инструмент, который запомнит все каталоги, которые вы посещаете при использовании своего терминала, а затем позволят быстро прыгать вокруг этих каталогов.
Давайте попробуем переписать эту функциональность с нуля, возможно, мы узнаем несколько вещей таким образом.
Я начал использовать z
Около года назад.
Это сработало нормально, за исключением одной вещи.
Допустим, у меня есть два каталога, соответствующие спам
, /path/to/spam-eggs
и /Другой путь/до/яйца-с-спам
Анкет
z
Вычислит оценку для каждого каталога, который зависит от того, как часто и как недавно доступ к ней.
Так , теоретически:
Справочник, который имеет низкий рейтинг, но был доступен недавно, Уилл быстро иметь более высокий рейтинг, чем каталог часто давно давно. (Выдержка z
Читает, акцент мой)
Но на практике, если вы начнете работать с спам-яйца
к Яйца с помощью спама
Вы получите неправильный ответ для z spam
Несколько раз («быстро» не означает «мгновенно»)
Также Потому что Алгоритм использует дату доступа, вы не можете (легко) предсказать, какой каталог он выберет.
z
использует базу данных, которая выглядит так
/other/path/to/eggs-with-spam|24|1495372880 /path/to/spam-eggs|4|1495372491 ...
Есть путь, количество раз, когда он был доступен, и временная метка последнего визита.
Мы будем использовать json
, и так как мы хотим алгоритм, который не зависит от времени, мы сохраним только количество доступа:
{ "/other/path/to/eggs-with-spam": 24, "/path/to/spam-eggs": 4, ... }
Почему json
? Потому что на любом языке довольно легко читать и писать (включая симпатичную печать), но и все еще редактируется от людей.
По -прежнему можно добавить новые данные, если нам нужно.
Допустим, вы работаете в /tmp/foo
и ни один другой каталог не называется Фу
. Вы также создаете каталоги /tmp/foo/src
и /tmp/foo/include
Анкет
С z
, когда -то три пути, /tmp/foo
, /tmp/foo/src/
и /tmp/foo/include
хранятся в базе данных, они останутся там навсегда.
Это означает, что если вы удалите /tmp/foo
, Z foo
все еще попытается перейти в несуществующий каталог. Но, если вы воссоздаете /tmp/foo
Позже, Z foo
будет работать снова.
В нашем переписывании мы рассмотрим эту ситуацию по -другому:
- Один , если мы не можем
CD
В каталог мы немедленно удалим его из базы данных - Во -вторых, мы сделаем возможным явно «очистить» базу данных: в нашем примере это означает удаление всех трех путей за один шаг. Для этого мы рассмотрим каждый путь в базе данных и удалим те, которые больше не существуют.
Я решил назвать инструмент CWD-History
и использовать синтаксис командной строки, похожий на git
, с несколькими возможными «глаголами» для различных действий:
Список проживания CWD
: Чтобы отобразить пути в правильном порядкеCWD-History Добавить путь
: Добавьте путь к базе данныхCWD-History удалить путь
: Чтобы удалить путь из базы данныхCWD-History Редактировать
: Чтобы редактировать файл JSON напрямую (может стать удобным)CWD-History Clean
: Чтобы удалить не существующие пути из базы данных
Код на GitHub Если вы хотите посмотреть.
Некоторые примечания:
def get_db_path(): zsh_share_path = os.path.expanduser("~/.local/share/zsh") os.makedirs(zsh_share_path, exist_ok=True)
Мы чтим стандарт XDG (в действительности мы также должны проверить на наличие переменной среды
xdg_data_home
, но это лучше, чем загрязнение$ Home
).Мы используем
существует_ok
1 аргумент дляOs.madkedirs
, так что команда не взимает неудачу, если каталог уже существует.
def add_to_db(path): path = os.path.realpath(path)
- Мы используем
OS.Path.RealPath
Чтобы убедиться, что все символики разрешены. Это означает, что у нас не будет дублированных путей в нашей базе данных.
def clean_db(): cleaned = 0 entries = read_db() for path in list(entries.keys()): if not os.path.exists(path): cleaned += 1 del entries[path] if cleaned: print("Cleaned", cleaned, "entries") write_db(entries) else: print("Nothing to do")
- Мы стараемся предоставить как можно больше информации только в одной строке, отображая либо количество очищенных каталогов, либо ничего не было сделано. Перечисление всех удаленных путей было бы слишком многословным, и если бы мы не отобразили ничего, мы бы никогда не были уверены, что команда действительно сработала.
import operator def list_db(): entries = read_db() sorted_entries = sorted(entries.items(), key=operator.itemgetter(1)) print("\n".join(x[0] for x in sorted_entries))
- Мы используем
operator.itemgetter
в качестве ярлыка вLambda x: x [1]
- Таким образом, мы сортируем пути по количеству случаев, к ним обращались.
- Мы отображаем только пути, каждый из которых разделен одной линией. Это сделает вывод
Список проживания CWD
Действительно легко разобрать. (Мы могли бы добавить опцию-verbose
для отображения, например, полных данных базы данных)
Мы хотим позвонить CWD-History Добавить $ (CWD)
каждый раз zsh
меняет текущий рабочий каталог.
Это делается путем написания функции и добавить ее в специальный массив:
function register_cwd() { cwd-history add "$(pwd)" } typeset -gaU chpwd_functions chpwd_functions+=register_cwd
- Обратите внимание, как
chpwd_functions
это массив ZSH, поэтому мы должны использоватьнабранная
. Мы называем это-g
Потому чтоchpwd_functions
это Глобальная ценность , а также-U
Чтобы убедиться, что список не содержит дубликатов. Я не уверен, что-a
для, извините.
Вместо того, чтобы пытаться угадать лучший результат, мы позволим пользователю выбирать, зацепившись в fzf
Анкет
Я уже говорил о fzf
на Предыдущая статья Анкет Суть этого в том, что вы можете передать любую команду в качестве входного ввода в fzf
и пусть пользователь интерактивно выбирает один результат из списка.
Реализация выглядит так:
cwd_list=$(cwd-history list) ret="$(echo $cwd_list| fzf --no-sort --tac --query=${1})" cd "${ret}" if [[ $? -ne 0 ]]; then cwd-history remove "${ret}" fi
Заметки:
Мы используем
fzf
с-Query
, чтобы вы могли введитеZ foo
, или простоz
, и только после типаfoo
шаблон вFZF
В окнеПоскольку наиболее вероятные ответы на дно из
Список проживания CWD
Команда, мы используемfzf
с--tac
опция 2 Анкет Нам также нужно сказатьFZF
к не Сортируйте ввод заранее.Как объяснялось ранее, мы удаляем путь из базы данных, если не можем
CD
внутрь. (Это также охватывает случай, когда нам не хватает разрешений на посещение папки, кстати)
Последняя вещь. Поскольку я делаю большую часть своего редактирования в Neovim, я всегда ищу способы достичь подобного поведения в моей оболочке и в моем редакторе.
Итак, давайте посмотрим, как мы можем передать информацию о посещаемых каталогах из одного инструмента другому.
От Neovim до оболочки
Это своего рода уродливый хак.
Во-первых, я добавляю автоматическую команду, чтобы написать ток Neovim непосредственно в жесткий файл в /tmp/
:
" Write cwd when leaving function! WriteCWD() call writefile([getcwd()], "/tmp/nvim-cwd") endfunction autocmd VimLeave * silent call WriteCWD()
А потом я оберную звонок neovim
В функции, которая считывает содержимое файла, а затем вызывает CD
, но Только если neovim
вышел успешно.
# Change working dir when Vim exits function neovim_wrapper() { nvim $* if [[ $? -eq 0 ]]; then cd "$(cat /tmp/nvim-cwd 2>/dev/null || echo .)" fi }
От оболочки Невим
Чтобы пойти в другую сторону, я просто звоню fzf#run ()
из fzf.vim плагин с Список проживания CWD
как источник и : TCD
как раковина: 3
function! ListWorkingDirs() call fzf#run({ \ 'source': "cwd-history list", \ 'sink': "tcd" \}) endfunction command! -nargs=0 ListWorkingDirs :call ListWorkingDirs() nnoremapl :ListWorkingDirs
И там у тебя это есть.
z
458 строк zsh
код.
Мое повторное внедрение-75 лигнов питона, 6 строк zsh
и 8 строк vimscript
Анкет
Он делится базой данных между оболочкой и редактором, она никогда не ошибается, и база данных остается чистой и редактируемой вручную.
Неплохо, я думаю:)
PS1: Вы также можете использовать z
прямо с fzf
с несколькими строками кода, как показано в FZF Wiki
PS2: есть продолжение этого поста В моем блоге Если тебе любопытно.
Здесь, так как Python 3.2 ↩
Это
кошка
В обратном направлении вы это понимаете? ↩TCD
это функция только для Neovim. Я уже упомянул об этом в своем Сообщение о VIM, CWD и NEOVIM ↩
Оригинал: “https://dev.to/dmerejkowsky/rewriting-z-from-scratch”