Глобальный поиск и замена в Vim, начиная с позиции курсора и заканчивая


112

Когда я ищу с помощью команды / нормального режима:

/\vSEARCHTERM

Vim начинает поиск с позиции курсора и продолжается вниз, возвращаясь к началу. Однако, когда я ищу и заменяю с помощью :substituteкоманды:

:%s/\vBEFORE/AFTER/gc

Вместо этого Vim запускается с начала файла.

Есть ли способ заставить Vim выполнять поиск и замену, начиная с позиции курсора и переходя к началу, когда он достигает конца?


2
\vpattern- «очень магический» шаблон: не буквенно-цифровые символы интерпретируются как специальные символы регулярного выражения (экранирование не требуется)
Карлос Арайя

какой смысл начинать с текущей позиции и оборачивать файл вместо использования просто %? Я не вижу разумного смысла в том, что вы все равно просматриваете весь файл.
tymik

@tymik Цель состояла в том, чтобы начать поиск с курсора и просмотреть каждый результат шаг за шагом. Но я уже много лет не использую Vim.
basteln

Ответы:


50

1.  Нетрудно добиться такого поведения с помощью двухэтапной замены:

:,$s/BEFORE/AFTER/gc|1,''-&&

Сначала выполняется команда подстановки для каждой строки, начиная с текущей и до конца файла:

,$s/BEFORE/AFTER/gc

Затем эта :substituteкоманда повторяется с тем же шаблоном поиска, строкой замены и флагами, используя :& команду (см. :help :&):

1,''-&&

Последний, однако, выполняет замену диапазона строк от первой строки файла до строки, в которой была установлена ​​предыдущая метка контекста, минус один. Поскольку первая :substituteкоманда сохраняет позицию курсора до начала фактических замен, адресуемая строка  ''- это строка, которая была текущей до того, как была запущена эта команда замены. (The '' адрес относится к ' псевдо-метке; см :help :rangeи :help ''для деталей.)

Обратите внимание, что вторая команда (после | разделителя команд - см. :help :bar) Не требует никаких изменений при изменении шаблона или флагов в первой.

2.  Чтобы сэкономить время на вводе текста, чтобы вызвать скелет вышеуказанной команды подстановки в командной строке, можно определить отображение в нормальном режиме, например:

:noremap <leader>cs :,$s///gc\|1,''-&&<c-b><right><right><right><right>

Завершающая <c-b><right><right><right><right>часть необходима для перемещения курсора в начало командной строки ( <c-b>), а затем на четыре символа вправо ( <right> × 4), таким образом, помещая его между первыми двумя знаками косой черты, чтобы пользователь мог начать вводить шаблон поиска. . Когда желаемый узор и замена готовы, получившуюся команду можно запустить, нажав Enter.

(Можно было бы подумать о том, //чтобы вместо ///приведенного выше сопоставления, если кто-то предпочитает вводить шаблон, затем введите разделяющую косую черту самостоятельно, за которой следует строка замены, вместо использования стрелки вправо для перемещения курсора над уже существующей разделяющей косой чертой, начиная с запасная часть.)


1
Единственная проблема с этим решением заключается в том, что параметры last (l) и quit (q) не останавливают выполнение второй замещающей команды
Стив Вермёлен,

@eventualEntropy См. мое решение ниже о запросе еще одного нажатия клавиши «q».
q335r49

2
Не забудьте (как это сделал я) выйти из пайпа, если вы помещаете это в сопоставление клавиш.
jazzabeanie

11
Боже мой, что вообще означают все эти произвольные символы? Как ты это узнал?
rocky

2
@Tropilio: Тогда вы можете попробовать что - то вроде этого: :noremap <leader>R :,$s///gc\|1,''-&&<c-b><right><right><right><right>. Он помещается :,$s///gc|1,''-&&в командную строку с курсором между первыми двумя знаками косой черты. После того, как вы введете желаемый узор и замену, вы можете запустить полученную команду, нажав Enter .
ib.

174

Вы уже используете диапазон, %который является сокращением для 1,$обозначения всего файла. Чтобы перейти от текущей строки к концу, используйте .,$. Точка означает текущую строку и $означает последнюю строку. Итак, команда будет такой:

:.,$s/\vBEFORE/AFTER/gc

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

:,$s/\vBEFORE/AFTER/gc

Для получения дополнительной помощи см.

:h range

Спасибо, я уже знал об этом. Я хочу, чтобы поиск и замена выполнялись циклически, когда доходит до конца документа. С:., $ S этого не происходит, а просто написано «Шаблон не найден».
Basteln

7
Для «следующих десяти строк»:10:s/pattern/replace/gc
здесь

11
хотя это не то, что искал OP, это было очень полезно для меня, спасибо!
Tobias J

51

%- это ярлык для 1,$
(Vim help => :help :%равно 1, $ (весь файл).)

. это позиция курсора, поэтому вы можете сделать

:.,$s/\vBEFORE/AFTER/gc

Заменить от начала документа до курсора

:1,.s/\vBEFORE/AFTER/gc

и т.д

Я настоятельно рекомендую вам прочитать руководство по диапазону, так :help rangeкак практически все команды работают с диапазоном.


Спасибо, я уже знал об этом. Я хочу, чтобы поиск и замена выполнялись циклически, когда доходит до конца документа. С:., $ S этого не происходит, а просто написано «Шаблон не найден».
Basteln

@basteln: в посте была опечатка // вы копировали и вставляли?
sehe

Итак, вы хотите заменить ВСЕ, но, поскольку вы хотите запросить подтверждение, вы хотите начать с текущей позиции. Тогда просто сделай это дважды.
mb14

вместо этого вы можете использовать n и &.
mb14

хм, я думаю, n и & - лучшее решение, хотя это не совсем так, как должно быть (с подсказкой да / нет при каждом совпадении). Но определенно достаточно хорошо, спасибо! :)
basteln

4

Я НАКОНЕЦ придумал решение того факта, что выход из поиска переносится в начало файла без написания огромной функции ...

Вы не поверите, сколько времени мне понадобилось, чтобы это придумать. Просто добавьте запрос, следует ли переносить: если пользователь qснова нажимает , не переносить. В общем, выйдите из поиска, нажав qqвместо q! (И если вы хотите обернуть, просто введите y.)

:,$s/BEFORE/AFTER/gce|echo 'Continue at beginning of file? (y/q)'|if getchar()!=113|1,''-&&|en

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

exe 'nno q* :,$s/\<<c-r>=expand("<cword>")<cr>\>//gce\|echo "Continue at beginning of file? (y/q)"\|if getchar()==121\|1,''''-&&\|en'.repeat('<left>',77)

На мой взгляд, этот подход - добавление дополнительного приглашения между двумя командами, предложенными в моем ответе, - добавляет ненужную сложность без улучшения удобства использования. В исходной версии , если вы выходите из первой команды подстановки (с помощью q, lили Esc ) и не хотите продолжать с начала, вы уже можете снова нажать qили Esc, чтобы сразу выйти из второй команды. То есть предлагаемое добавление подсказки, похоже, эффективно дублирует уже существующую функциональность.
ib.

Чувак ... ты ОПРОБОВАЛ свой подход? Скажем, я хочу заменить следующие 3 появления слова «let» на «unlet», а затем - это ключ - продолжить редактирование абзаца . Ваш подход «Нажмите y, y, y, теперь нажмите q. Теперь вы начинаете поиск в первой строке абзаца. Нажмите q еще раз, чтобы выйти. Теперь вернитесь к тому месту, где вы были раньше, чтобы продолжить редактирование. Хорошо, g ;. Итак, в основном: yyyqq ... запутаться ... g; (или u ^ R) Мой метод: yyyqq
q335r49

Идеальный метод - это, конечно yyyq, продолжать редактирование без дезориентирующих скачков. Но yyyqqэто почти так же хорошо, если вы рассматриваете двойное нажатие в значительной степени с точки зрения простоты использования.
q335r49

Ни исходная команда, ни ее версия с приглашением не возвращают курсор в его предыдущую позицию в этом сценарии. Первый оставляет пользователя при первом появлении шаблона сверху (там, где он был в момент qвторого нажатия ). Последний оставляет курсор на последнем вхождении, которое было заменено (непосредственно перед qнажатием первого ).
ib.

1
В первоначальном варианте , если желательно , чтобы автоматически вернуть курсор на свое прежнее положение (без пользовательского ввода Ctrl + O ), можно просто добавить в norm!``команду: :,$s/BEFORE/AFTER/gc|1,''-&&|norm!``.
ib.

3

Вот что-то очень грубое, что устраняет опасения по поводу обертывания поиска двухэтапным подходом ( :,$s/BEFORE/AFTER/gc|1,''-&&) или промежуточным «Продолжить в начале файла?» подходить:

" Define a mapping that calls a command.
nnoremap <Leader>e :Substitute/\v<<C-R>=expand('<cword>')<CR>>//<Left>

" And that command calls a script-local function.
command! -nargs=1 Substitute call s:Substitute(<q-args>)

function! s:Substitute(patterns)
  if getregtype('s') != ''
    let l:register=getreg('s')
  endif
  normal! qs
  redir => l:replacements
  try
    execute ',$s' . a:patterns . 'gce#'
  catch /^Vim:Interrupt$/
    return
  finally
    normal! q
    let l:transcript=getreg('s')
    if exists('l:register')
      call setreg('s', l:register)
    endif
  endtry
  redir END
  if len(l:replacements) > 0
    " At least one instance of pattern was found.
    let l:last=strpart(l:transcript, len(l:transcript) - 1)
    " Note: type the literal <Esc> (^[) here with <C-v><Esc>:
    if l:last ==# 'l' || l:last ==# 'q' || l:last ==# '^['
      " User bailed.
      return
    endif
  endif

  " Loop around to top of file and continue.
  " Avoid unwanted "Backwards range given, OK to swap (y/n)?" messages.
  if line("''") > 1
    1,''-&&"
  endif
endfunction

Эта функция использует несколько приемов, чтобы проверить, следует ли нам переходить к началу:

  • Нет переноса, если пользователь нажал «l», «q» или «Esc», любой из которых указывает на желание прервать выполнение.
  • Определите это последнее нажатие клавиши, записав макрос в регистр «s» и проверив его последний символ.
  • Избегайте перезаписи существующего макроса путем сохранения / восстановления регистра «s».
  • Если вы уже записываете макрос при использовании команды, все ставки отключены.
  • Пытается поступать правильно с прерываниями.
  • Избегает предупреждений о "обратном диапазоне" с помощью явной защиты.

1
Я извлек это в крошечный плагин и разместил здесь на GitHub .
wincent

Только что установил плагин, он работает как шарм !! Большое спасибо за это!
Тропилио,

1

Я опаздываю на вечеринку, но я слишком часто полагался на таких запаздывающих ответчиков stackoverflow, чтобы этого не сделать. Я собрал подсказки по Reddit и stackoverflow, и лучший вариант - использовать \%>...cшаблон в поиске, который соответствует только после вашего курсора.

Тем не менее, это также портит шаблон для следующего шага замены, и его трудно набрать. Чтобы противостоять этим эффектам, пользовательская функция должна впоследствии отфильтровать шаблон поиска, тем самым сбросив его. Увидеть ниже.

Я попытался использовать отображение, которое заменяет следующее вхождение и переходит к следующему после, но не более (в любом случае это была моя цель). Я уверен, что, опираясь на это, можно разработать глобальную замену. Имейте в виду, работая над решением, нацеленным на что-то подобное :%s/.../.../g, приведенный ниже шаблон отфильтровывает совпадения во всех строках, оставшихся до позиции курсора, но очищается после завершения одиночной замены, поэтому он теряет этот эффект сразу после этого, переходит к следующий матч и, таким образом, может проходить все совпадения один за другим.

fun! g:CleanColFromPattern(prevPattern) return substitute(a:prevPattern, '\V\^\\%>\[0-9]\+c', '', '') endf nmap <F3>n m`:s/\%><C-r>=col(".")-1<CR>c<C-.r>=g:CleanColFromPattern(getreg("/"))<CR>/~/&<CR>:call setreg("/", g:CleanColFromPattern(getreg("/")))<CR>``n


Спасибо, я согласен, что поздний ответ часто бывает полезным. Лично я давно не использую Vim (по таким причинам), но уверен, что многие другие сочтут это полезным.
basteln
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.