5

В bash вы можете использовать Mf и Mb, чтобы переместить курсор на одно слово вперед и назад, но есть ли способ переместить один аргумент вперед или назад? Если не из коробки, возможно, по какой-то конфигурации?

Другими словами, я бы хотел, чтобы курсор перемещался между отмеченными позициями ниже.

cp "foo bar.txt" "/tmp/directory with space"
^  ^             ^
|  |             |

1 ответ1

5

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

В ZSH есть ZSH Line Editor (zle для краткости). Это предоставляет все клавиши перемещения как привязываемые команды, так же, как bash. Куда это идет дальше, это способность определять пользовательские команды. Пользовательская команда - это любая функция оболочки, которая была превращена в виджет.

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

  • $ BUFFER - это вся строка, которую вы сейчас редактируете
  • $ CURSOR - это позиция вставки в текущей строке

Есть и другие доступные, такие как:

  • $ LBUFFER - это все перед курсором
  • $ RBUFFER - это все после курсора

Теперь случается, что ZSH не только способен предоставлять пользовательские привязки клавиш, но и имеет гораздо более полный набор операций, которые вы можете выполнять над переменными. Один из тех, которые интересны для этой проблемы:

  • z - разделить результат раскрытия на слова, используя синтаксический анализ оболочки, чтобы найти слова, т.е. с учетом любых кавычек в значении.

Вы можете назначить расширенный $ BUFFER непосредственно переменной, например так:

line=${(z)BUFFER}

(строка теперь является массивом, но, к сожалению, этот массив начинается с индекса 1, в отличие от bash!)

Это не приведет к расширению глобальных символов, поэтому вернет массив фактических аргументов в вашей текущей строке. Если у вас есть это, вас интересует положение начальной точки каждого слова в буфере. К сожалению, вы можете иметь несколько пробелов между любыми двумя словами, а также повторяющиеся слова. Лучшее, что я могу придумать на данный момент, - это удалить каждое рассматриваемое слово из текущего буфера, как вы его считаете. Что-то вроде:

buffer=$BUFFER
words=${(z)buffer}
for word in $words[@]
do
    # doing regular expression matching here,
    # so need to quote every special char in $word.
    escaped_word=${(q)word}
    # Fancy ZSH to the rescue! (q) will quote the special characters in a string.

    # Pattern matching like this creates $MBEGIN $MEND and $MATCH, when successful
    if [[ ! ${buffer} =~ ${${(q)word}:gs#\\\'#\'#} ]]
    then
        echo "Something strange happened... no match for current word"
        return 1
    fi
    buffer=${buffer[$MEND,-1]}
done

Мы почти у цели! Необходим способ узнать, какое слово является последним словом перед курсором, а какое слово является началом следующего слова после курсора.

buffer=$BUFFER
words=${(z)buffer}
index=1
for word in $words[@]
do
    if [[ ! ${buffer} =~ ${${(q)word}:gs#\\\'#\'#} ]]
    then
        echo "Something strange happened... no match for current word"
        return 1
    fi

    old_length=${#buffer}
    buffer=${buffer[$MEND,-1]}
    new_length=${#buffer}
    old_index=$index
    index=$(($index + $old_length - $new_length))

    if [[ $old_index -lt $CURSOR && $index -ge $CURSOR ]]
    then
        # $old_index is the start of the last argument.
        # you could move back to it.
    elif [[ $old_index -le $CURSOR && $index -gt $CURSOR ]]
    then
        # $index is the start of the next argument.
        # you could move forward to it.
    fi
    # Obviously both of the above conditions could be true, you would
    # have to have a way to tell which one you wanted to test - but since
    # you will have two widgets (one forward, one back) you can tell quite easily. 
done

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

Переменная $ CURSOR может быть обновлена, и если вы это сделаете, вы можете переместить текущую точку вставки. Довольно легко!

Привязка функций к клавишам включает в себя промежуточный этап привязки к виджету:

zle -N WIDGET_NAME FUNCTION_NAME

Затем вы можете привязать виджет к клавише. Вам, вероятно, придется поискать конкретные идентификаторы клавиш, но я обычно просто связываюсь с Ctrl-LETTER, что достаточно просто:

bindkey '^LETTER' WIDGET_NAME

Давайте соберем все это вместе и исправим вашу проблему:

function move_word {
    local direction=$1

    buffer=$BUFFER
    words=${(z)buffer}
    index=1
    old_index=0
    for word in $words[@]
    do
        if [[ ! ${buffer} =~ ${${(q)word}:gs#\\\'#\'#} ]]
        then
            echo "Something strange happened... no match for current word $word in $buffer"
            return 1
        fi

        old_length=${#buffer}
        buffer=${buffer[$MEND,-1]}
        new_length=${#buffer}
        index=$(($index + $old_length - $new_length))

        case "$direction" in
            forward)
                if [[ $old_index -le $CURSOR && $index -gt $CURSOR ]]
                then
                    CURSOR=$index
                    return
                fi
                ;;
            backward)
                if [[ $old_index -lt $CURSOR && $index -ge $CURSOR ]]
                then
                    CURSOR=$old_index
                    return
                fi
                ;;
        esac
        old_index=$index
    done
    case "$direction" in
        forward)
            CURSOR=${#BUFFER}
            ;;
        backward)
            CURSOR=0
            ;;
    esac
}

function move_forward_word {
    move_word "forward"
}

function move_backward_word {
    move_word "backward"
}

zle -N my_move_backwards move_backward_word
zle -N my_move_forwards move_forward_word
bindkey '^w' my_move_backwards
bindkey '^e' my_move_forwards

Что касается моего собственного тестирования, то, похоже, это делает работу. Возможно, вы захотите изменить ключи, к которым он привязан. Для справки я проверил это с помощью строки:

one 'two three' "four five"    "'six seven' eight nine" * **/ **/**/*
^  ^           ^           ^                           ^ ^   ^       ^

и он перемещался между каретами. Это не обернуть.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .