Вывод функции ZSH игнорируется при вводе текущего запроса.

Вопрос или проблема

У меня есть зарегистрированная функция в ZSH, которая ищет содержимое файлов в текущем каталоге на совпадение и затем захватывает имя найденного файла. Это привязано к сочетанию клавиш. Сочетание клавиш, поиск и результат работают нормально (то есть возвращают желаемое значение).

Когда я использую сочетание клавиш, значение выводится на текущую строчку оболочки, но не распознается как ввод (т.е. если я нажму Enter, значение будет считаться пустым, и ничего не добавится в историю команд). Как вывести результат так, как если бы он был напечатан и был корректным значением? Я пытался использовать echo/print/printf (предполагая, что результат будет одинаковым для каждого), а также добавлял к BUFFER и устанавливал CURSOR.

Я уверен, что это хорошо задокументировано, но мне не удается найти примеры из-за отсутствия правильной терминологии. Может кто-то направить меня в правильном направлении?

Вывод в терминал не поможет вам. Это отображает сообщение для пользователя (оно не окажется в нужном месте на экране и запутает редактор строк, потому что изменяет положение курсора в терминале — используйте zle -M или zle -R вместо этого). Это не ввод для оболочки.

Магическая переменная BUFFER содержит строки, которые пользователь редактирует, а CURSOR содержит позицию курсора ввода. Вы можете изменить буфер ввода пользователя и позицию курсора, изменив BUFFER и CURSOR. Вы были на верном пути. Однако есть более простой способ вставить что-то в курсор: просто добавьте к магической переменной LBUFFER, и zsh сделает все остальное. LBUFFER содержит ввод пользователя до курсора, и его изменение работает интуитивно.

Вот рабочий пример из моего .zshrc о вставке результата команды в буфер ввода. Он вставляет имя текущей ветки Git, когда я нажимаю Alt+v b.

vc_insert_branch () {
  LBUFFER+=$(git symbolic-ref --short HEAD 2>/dev/null)
}
zle -N vc_insert_branch
bindkey '^[vb' vc_insert_branch

Обратите внимание, что этот простой пример вставляет результат команды как есть. Для имени файла вам, вероятно, следует заключить его в кавычки на случай, если в нем есть специальные символы. Например, если результат команды это A Song.mp3, то приведенный выше код просто вставляет A Song.mp3 в строку команды. Но чтобы сделать его полезным, следует вставить что-то, что является корректным аргументом для команды. Используйте q- или q+ флаг расширения параметров, чтобы заключить результат команды в кавычки, если это необходимо.

insert_file_search_result () {
  LBUFFER+=${(q-)$(search command goes here 2>/dev/null)}
}

Обратите внимание, что если вы хотите провести поиск, основываясь на слове вокруг курсора, zsh имеет вспомогательные функции, которые избавляют вас от необходимости самостоятельно парсить командную строку и могут также позаботиться о внедрении нового текста. В частности, modify-current-argument заменяет текущее слово вокруг курсора на новое слово оболочки по вашему выбору, которое оно заключает в кавычки, если это необходимо. Вот концепт, который показывает, как заменить слово, в котором находится курсор, на самый последний файл, содержащий это слово.

function grep_word_and_reply_with_file_name {
  if [[ -z $ARG ]]; then
    zle -M 'Курсор не в слове'
    return 1
  fi
  local -a files
  files=(${(@0)${$(grep -l -Z -F -e $ARG -- /dev/null *(-.DNom))%$'\000'}})
  if (($#files == 0)); then
    zle -M "Нет совпадений для $ARG"
    return 1
  fi
  REPLY=$files[1]
}

function grep_word_and_replace_with_file_name {
  autoload -Uz modify-current-argument
  modify-current-argument grep_word_and_reply_with_file_name
}
zle -N grep_word_and_replace_with_file_name
bindkey '\eG' grep_word_and_replace_with_file_name

Как правило, я находил свой ответ на следующей вещи, на которую смотрел.

Я нашел вдохновение на https://github.com/junegunn/fzf/blob/master/shell/key-bindings.zsh – в частности в функции fzf-history-widget. Там она устанавливает переменную LBUFFER с пользовательским вводом (если это не команда истории ревизий) и затем вызывает zle reset-prompt. Я протестировал это с моим подходом BUFFER + CURSOR, и это тоже работает, но подход с LBUFFER более лаконичен.

Ответ или решение

Проблема, с которой вы столкнулись, заключается в том, что вывод вашей ZSH-функции, который отображается в терминале, не воспринимается как ввод для текущей строки командной оболочки. Даже если текст успешно выводится на экран, он не добавляется в буфер ввода, и нажатие клавиши Enter приводит к тому, что строка считается пустой.

Для решения этой задачи можно использовать специальные магические переменные ZSH, такие как LBUFFER и CURSOR. Эти переменные позволяют менять содержание строки ввода и позицию курсора. Ваша изначальная попытка манипулировать переменными BUFFER и CURSOR была правильной, но, как вы уже заметили, это не совсем интуитивно.

Переменная LBUFFER хранит содержимое строки ввода до позиции курсора. Изменяя эту переменную, вы автоматически обновляете вводимую строку. Таким образом, для добавления результата выполнения вашей функции в текущую строку командной оболочки вы можете просто использовать LBUFFER.

Вот пример функции, которая вставляет результат выполнения команды, выводящей имя файла, по сочетанию клавиш:

function insert_file_search_result {
    # Здесь предполагается, что ваша команда поиска сохранена в переменной
    local result=$(search command goes here 2>/dev/null)
    LBUFFER+=${(q-)result} # Добавляем результат в LBUFFER, экранируя специальные символы
}
zle -N insert_file_search_result
bindkey '^[f' insert_file_search_result # Замените '^[f' на желаемое сочетание клавиш

В этом примере команда search command goes here заменяется на вашу команду, которая генерирует имя файла. Команда ${(q-)result} экранирует все специальные символы в строке, чтобы избежать возможных ошибок при вводе.

Если вы хотите заменить слово в строке ввода на основании текста, на который указывает курсор, можете использовать встроенные функции ZSH, такие как modify-current-argument. В примере ниже показано, как это сделать:

function grep_word_and_reply_with_file_name {
    if [[ -z $ARG ]]; then
        zle -M 'Курсор не над словом'
        return 1
    fi
    local -a files
    files=(${(@0)${$(grep -l -Z -F -e $ARG -- /dev/null *(-.DNom))%$'\000'}})
    if (($#files == 0)); then
        zle -M "Нет совпадений для $ARG"
        return 1
    fi
    REPLY=$files[1] # Используем REPLY для возвращения найденного файла
}

function grep_word_and_replace_with_file_name {
    autoload -Uz modify-current-argument
    modify-current-argument grep_word_and_reply_with_file_name
}
zle -N grep_word_and_replace_with_file_name
bindkey '\eG' grep_word_and_replace_with_file_name # Измените '\eG' на желаемое сочетание клавиш

Эта функция заменяет слово, на которое указывает курсор, на имя первого найденного файла, соответствующего введённому слову. Она также экранирует результат при необходимости и предоставляет обратную связь пользователю при отсутствии совпадений.

Таким образом, используя переменную LBUFFER и встроенные функции, вы можете добиться желаемого результата без необходимости выводить текст в терминале, что считается неверным способом добавления ввода в текущую строку командной оболочки.

Оцените материал
Добавить комментарий

Капча загружается...