Глобальный поиск и замена в 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.