Vim: инвертировать строку (по словам) - PullRequest
10 голосов
/ 04 апреля 2011

Это моя строка:

"this is my sentence"

Я хотел бы получить такой вывод:

"sentence my is this"

Я хотел бы выделить несколько слов в строке (в буфере) и поменять их слово за словом.

Кто-нибудь может мне помочь?

Ответы [ 4 ]

16 голосов
/ 04 апреля 2011

Не совсем понятно, какой здесь контекст: вы могли бы говорить о тексте в строке в буфере или о строке, хранящейся в переменной VimScript.

note: Различные интерпретации вопроса привели к различным подходам и решениям.
Есть некоторые " старые обновления ", которые начинаются примерно на полпути и которые были более или менее устарели с помощью плагина, упомянутого чуть выше:раздел.Я оставил их, потому что они могут предоставить полезную информацию для некоторых людей.

замена полной строки

Таким образом, чтобы сохранить текст из текущей строки в текущем буфере в vimscriptпеременная, вы делаете

let words = getline('.')

А затем, чтобы изменить их порядок, вы просто делаете

let words = join(reverse(split(words)))

Если вы хотите заменить текущую строку обратными словами, вы делаете

call setline('.', words)

Вы можете сделать это в одной несколько непостижимой строке с помощью

call setline('.', join(reverse(split(getline('.')))))

или даже определить команду, которая делает это с помощью

command! ReverseLine call setline('.', join(reverse(split(getline('.')))))

частичной строки (посимвольно)) selections

Как объяснено в разделе «старые обновления», запуск общих команд для символьного или блочного визуального выбора - первый из которых - то, что OP хочет сделать здесь - может быть довольно сложным.Ex-команды, такие как :substitute, будут выполняться на целых строках, даже если только часть строки выбрана с помощью посимвольного визуального выбора (как инициировано с несмещенным v ).

Я понял, после ОП, прокомментированного ниже, что перестановка слов в выделенном для строки символьном выделении может быть довольно легко выполнена с помощью

:s/\%V.*\%V./\=join(reverse(split(submatch(0))))/

, где

  • \%V в RE соответствует некоторой части визуального выбора.Очевидно, это не распространяется после последнего символа в выделении: пропуск последнего . исключит последний выбранный символ.
  • \= в начале замены означает, чтооно должно быть оценено как выражение vimscript, с некоторыми различиями .
  • submatch(0) возвращает полное совпадение.Это работает немного как Perl $&, $1 и т. Д., За исключением того, что оно доступно только при оценке замены.Я думаю, это означает, что его можно использовать только в команде :substitute или при вызове substitute()

Так что если вы хотите сделать замену на одномВыбор линии, это будет работать довольно хорошо.Вы можете даже передать выделение через системную команду, используя ...\=system(submatch(0)).

многострочный выбор символов

Это также работает для многострочного выбора символов, ноВы должны быть осторожны, чтобы удалить диапазон ('<,'>, который vim помещает в начало команды при выходе из визуального режима).Вы хотите выполнить команду только на той строке, с которой начинается ваш визуальный выбор.Вам также нужно будет использовать \_.* вместо .* для сопоставления между новыми строками.

блочный выбор

Для блочного выбора я не думаю, что естьдостаточно удобный способ манипулировать ими.Я написал плагин, который можно использовать для облегчения такого рода изменений, предоставляя способ запуска произвольных команд Ex для любого визуального выделения, как если бы это было все содержимое буфера.

Он доступен по адресуhttps://github.com/intuited/visdo. В настоящее время нет упаковки, и она еще не доступна на vim.org, но вы можете просто git clone ее и скопировать содержимое (за исключением файла README) в ваш vimdir.

Если вы используете vim-addon-manager , просто клонируйте visdo в каталог vim-addons, и вы впоследствии сможете ActivateAddons visdo.Я добавил запрос на добавление его в хранилище аддонов VAM, так что в какой-то момент вы сможете обойтись без клонирования и просто выполните ActivateAddons visdo.

Плагин добавляет команду :VisDo, предназначенную для добавления префикса к другой команде (аналогично тому, как работают :tab или :silent). Выполнение команды с предварительно добавленным VisDo приведет к тому, что эта команда будет выполняться в буфере, содержащем только текущее содержимое визуального выделения. После завершения команды содержимое буфера вставляется в визуальный выбор исходного буфера, а временный буфер удаляется.

Таким образом, чтобы завершить задачу ОП с VisDo, вы должны сделать (с выбранными словами, которые нужно повернуть вспять, и с доступной выше определенной командой ReverseLine):

:'<,'>VisDo ReverseLine


старые обновления

... следуют предыдущие обновления ... предупреждение: многословно, несколько устарело и в основном не нужно ...

Редактирование OP делает более ясным, что цель здесь состоит в том, чтобы иметь возможность полностью изменить слова, содержащиеся в визуальном выделении, и, в частности, символьное визуальное выделение.

Это решительно не простая задача. Тот факт, что vim не облегчает подобные вещи, по-настоящему смутил меня, когда я впервые начал им пользоваться. Я предполагаю, что это потому, что его корни все еще очень сильно связаны с функциональностью ed, ориентированной на строки, и его предшественниками и потомками. Например, команда замены :'<,'>s/.../.../ всегда будет работать на целых строках, даже если вы находитесь в режиме символьного или блочного ( ctrl v ) режима визуального выбора.

Vimscript позволяет найти номер столбца любой «метки», включая начало визуального выбора ('<) и конец визуального выбора ('>). То есть, насколько я могу судить, предел его поддержки. Нет прямого способа получить содержимое визуального выделения, и нет способа заменить визуальный выбор чем-то другим. Конечно, вы можете делать обе эти вещи, используя команды в обычном режиме (y и p), но это забивает регистрацию и выглядит немного грязно. Но затем вы можете сохранить начальные регистры, а затем восстановить их после вставки ...

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

Этот 40-строчный кодовый блок объявляет команду ReverseCharwiseVisualWords, которую можно вызывать из визуального режима. Это будет работать только в том случае, если посимвольный визуальный выбор целиком находится на одной строке. Он работает, получая всю строку, содержащую визуальный выбор (используя getline()), запуская параметризованную функцию преобразования (ReverseWords) в выбранной ее части, и вставляя всю частично преобразованную строку обратно. Оглядываясь назад, я думаю, что, вероятно, стоит пойти по маршруту y / p для чего-то более интересного.

" Return 1-based column numbers for the start and end of the visual selection.
function! GetVisualCols()
  return [getpos("'<")[2], getpos("'>")[2]]
endfunction

" Convert a 0-based string index to an RE atom using 1-based column index
" :help /\%c
function! ColAtom(index)
  return '\%' . string(a:index + 1) . 'c'
endfunction

" Replace the substring of a:str from a:start to a:end (inclusive)
" with a:repl
function! StrReplace(str, start, end, repl)
  let regexp = ColAtom(a:start) . '.*' . ColAtom(a:end + 1)
  return substitute(a:str, regexp, a:repl, '')
endfunction

" Replace the character-wise visual selection
" with the result of running a:Transform on it.
" Only works if the visual selection is on a single line.
function! TransformCharwiseVisual(Transform)
  let [startCol, endCol] = GetVisualCols()

  " Column numbers are 1-based; string indexes are 0-based
  let [startIndex, endIndex] = [startCol - 1, endCol - 1]

  let line = getline("'<")
  let visualSelection = line[startIndex : endIndex]
  let transformed = a:Transform(visualSelection)
  let transformed_line = StrReplace(line, startIndex, endIndex, transformed)

  call setline("'<", transformed_line)
endfunction

function! ReverseWords(words)
  return join(reverse(split(a:words)))
endfunction

" Use -range to allow range to be passed
" as by default for commands initiated from visual mode,
" then ignore it.
command! -range ReverseCharwiseVisualWords
      \ call TransformCharwiseVisual(function('ReverseWords'))

обновление 2

Оказывается, что делать вещи с y и p намного проще, поэтому я подумал, что тоже опубликую это. Предостережение: я не проверял это слишком тщательно, поэтому могут быть крайние случаи.

Эта функция заменяет TransformCharwiseVisual (и некоторые его зависимости) в предыдущем блоке кода. Теоретически он должен работать и для блочного выбора - до переданной функции Transform можно делать соответствующие действия с разделителями строк.

function! TransformYankPasteVisual(Transform)
  let original_unnamed = [getreg('"'), getregtype('"')]
  try
    " Reactivate and yank the current visual selection.
    normal gvy
    let @" = a:Transform(@")
    normal gvp
  finally
    call call(function('setreg'), ['"'] + original_unnamed)
  endtry
endfunction

Итак, вы можете просто добавить второе объявление команды

command! -range ReverseVisualWords
      \ call TransformYankPasteVisual(function('ReverseWords'))

тангенциально связанная кровавая деталь

Обратите внимание, что полезность функции более высокого уровня, подобной тем, что используются здесь, несколько ограничена тем фактом, что нет (простого или установленного) способа объявить встроенную функцию или блок кода в vimscript. Это не было бы таким ограничением, если бы язык не предназначался для интерактивного использования. Вы можете написать функцию, которая подставляет строковый аргумент в объявление функции словаря и возвращает функцию. Однако функции словаря не могут быть вызваны с использованием обычного синтаксиса вызова и должны быть переданы в call call(dictfunct, args, {}).

примечание: Более свежее обновление, приведенное выше, устарело вышеупомянутый код. См. Различные разделы, предшествующие старым обновлениям , для более понятного способа сделать это.

1 голос
/ 04 апреля 2011

Я сама нашла решение благодаря вашим ответам и много попыток :) 1001 *

Это работает:

function! Test()
exe 'normal ' . 'gv"ay'
let r = join(reverse(split(getreg('a'))))
let @a = r
exe 'normal ' . 'gv"ap'
endfunction

Не думал, что мне позволят написать такую ​​функцию:)

1 голос
/ 04 апреля 2011

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

!ruby -e 'puts ARGF.read.strip.split(/\b/).reverse.join'
1 голос
/ 04 апреля 2011

Может быть:

:s/\v(.*) (.*) (.*) (.*)/\4 \3 \2 \1/

Конечно, вам, вероятно, нужно быть более конкретным в первой части, чтобы найти это конкретное предложение. Обычно вы можете ссылаться на группы совпадений как \ число, где \ 0 - это полное совпадение.

...