ค้นหาส่วนกลางและแทนที่ใน Vim โดยเริ่มจากตำแหน่งเคอร์เซอร์และล้อมรอบ


112

เมื่อฉันค้นหาด้วย/ คำสั่งโหมดปกติ:

/\vSEARCHTERM

Vim เริ่มการค้นหาจากตำแหน่งเคอร์เซอร์และลงไปเรื่อย ๆ โดยตัดไปด้านบน อย่างไรก็ตามเมื่อฉันค้นหาและแทนที่โดยใช้:substituteคำสั่ง:

:%s/\vBEFORE/AFTER/gc

กลุ่มเริ่มต้นที่ด้านบนสุดของไฟล์แทน

มีวิธีทำให้ Vim ทำการค้นหาและแทนที่โดยเริ่มจากตำแหน่งเคอร์เซอร์และห่อรอบ ๆ ไปด้านบนเมื่อถึงจุดสิ้นสุดหรือไม่?


2
\vpattern- รูปแบบ 'วิเศษมาก': อักขระที่ไม่ใช่ตัวเลขและตัวอักษรจะถูกตีความว่าเป็นสัญลักษณ์ regex พิเศษ (ไม่จำเป็นต้องมีการหลบหนี)
Carlos Araya

อะไรคือจุดเริ่มต้นจากตำแหน่งปัจจุบันและล้อมรอบไฟล์แทนที่จะใช้เพียงแค่%? ฉันไม่เห็นการใช้งานที่สมเหตุสมผลสำหรับการที่คุณกำลังใช้งานไฟล์ทั้งหมดอยู่แล้ว
tymik

@tymik จุดประสงค์คือเริ่มค้นหาจากเคอร์เซอร์และดูผลลัพธ์ทีละขั้นตอน แต่ฉันไม่ได้ใช้ Vim มาหลายปีแล้ว
basteln

คำตอบ:


50

1.  ไม่ยากที่จะบรรลุพฤติกรรมโดยใช้การทดแทนสองขั้นตอน:

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

ขั้นแรกคำสั่งการแทนที่ถูกเรียกใช้สำหรับแต่ละบรรทัดโดยเริ่มจากบรรทัดปัจจุบันจนถึงจุดสิ้นสุดของไฟล์:

,$s/BEFORE/AFTER/gc

จากนั้น:substituteคำสั่งนั้นจะถูกทำซ้ำด้วยรูปแบบการค้นหาสตริงแทนที่และแฟล็กเดียวกันโดยใช้:& คำสั่ง (ดู:help :&):

1,''-&&

อย่างไรก็ตามอย่างหลังจะทำการแทนที่ในช่วงของบรรทัดจากบรรทัดแรกของไฟล์ไปจนถึงบรรทัดที่มีการตั้งค่าเครื่องหมายบริบทก่อนหน้าโดยลบหนึ่ง เนื่องจาก:substituteคำสั่งแรกเก็บตำแหน่งเคอร์เซอร์ก่อนที่จะเริ่มการแทนที่จริงบรรทัดที่จ่าหน้า  ''คือบรรทัดที่เป็นบรรทัดปัจจุบันก่อนที่จะรันคำสั่งการแทนที่ (ที่'' อยู่หมายถึง ' เครื่องหมายหลอกดู: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
ปัญหาเดียวของวิธีแก้ปัญหานี้คือตัวเลือกสุดท้าย (l) และออก (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
สำหรับ "The next ten lines":10:s/pattern/replace/gc
ที่นี่

10
แม้ว่าจะไม่ใช่สิ่งที่ 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 อีกครั้งเพื่อออกตอนนี้กลับไปที่จุดที่คุณเคยอยู่ก่อนหน้านี้เพื่อทำการแก้ไขต่อ Ok, 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
ฉันสกัดนี้เป็น plug-in ที่ขนาดเล็กและวางไว้บน GitHub ที่นี่
wincent

เพียงแค่ติดตั้งปลั๊กอินของคุณก็ใช้งานได้อย่างมีเสน่ห์ !! ขอบคุณมากที่ทำสิ่งนี้!
Tropilio

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.