Глобальний пошук і заміна у 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) не зупиняють запуск другої команди-замінника
Steve Vermeulen

@eventualEntropy Дивіться моє рішення нижче про запит на ще одну "q".
q335r49

2
Не забувайте (як я це робив), щоб уникнути труби, якщо ви кладете це в ключове відображення.
jazzabeanie

11
Боже мій, що означають усі ці довільні символи. Як ви це дізналися?
скелястий єнот

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
ось

10
незважаючи на те, що це не те, що шукала ОП, це мені дуже допомогло, дякую!
Тобіас J

51

%- це ярлик для 1,$
(Vim help => :help :%рівний 1, $ (весь файл).)

. це позиція курсору, щоб ви могли це зробити

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

Замінити з початку документа до курсору

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

тощо

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


Дякую, я вже про це знав. Я хочу, щоб пошук і заміна завершилися, як тільки він досягне кінця документа. З:., $ S це не робить, він просто говорить "Шаблон не знайдено".
basteln

@basteln: в публікації була помилка друку // ти скопіював і вставив?
1111

Отже, ви хочете замінити ВСЕ, але, як ви хочете запитати підтвердження, ви хочете почати з поточної позиції. Просто зробіть це два рази тоді.
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 випадки "нехай" на "скасувати", а потім - це ключ - продовжуйте редагування абзацу . Ваш підхід "Натисніть y, y, y, зараз натисніть q. Тепер ви почнете пошук у першому рядку абзацу. Натисніть q ще раз, щоб вийти. Тепер поверніться туди, де ви були раніше, щоб продовжити редагування. Добре, г ;. Отже, в основному: 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 тут .
Вінсент

Щойно встановлений ваш плагін, він працює як шарм !! Дуже дякую вам за це!
Тропіліо

1

Я запізнююсь на партію, але я занадто часто покладався на такі пізні відповіді стаковерху, щоб не робити цього. Я зібрав підказки щодо 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.