Рубрики
Без рубрики

Переписать Z с нуля

Переписывание Z для развлечения и прибыли. Tagged with ZSH, Python, Neovim.

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()
nnoremap l :ListWorkingDirs

И там у тебя это есть.

z 458 строк zsh код.

Мое повторное внедрение-75 лигнов питона, 6 строк zsh и 8 строк vimscript Анкет

Он делится базой данных между оболочкой и редактором, она никогда не ошибается, и база данных остается чистой и редактируемой вручную.

Неплохо, я думаю:)

PS1: Вы также можете использовать z прямо с fzf с несколькими строками кода, как показано в FZF Wiki

PS2: есть продолжение этого поста В моем блоге Если тебе любопытно.

  1. Здесь, так как Python 3.2 ↩

  2. Это кошка В обратном направлении вы это понимаете? ↩

  3. TCD это функция только для Neovim. Я уже упомянул об этом в своем Сообщение о VIM, CWD и NEOVIM

Оригинал: “https://dev.to/dmerejkowsky/rewriting-z-from-scratch”