Làm cách nào để dừng macro đệ quy ở cuối dòng?


13

Làm cách nào tôi có thể tạo một macro đệ quy để nó chỉ chạy cho đến hết dòng?

Hoặc làm thế nào để chạy một macro đệ quy cho đến cuối dòng chỉ?

Câu trả lời:


11

Có lẽ có một phương pháp đơn giản hơn nhưng có lẽ bạn có thể thử như sau.

Giả sử bạn sẽ sử dụng đăng ký qđể ghi lại macro đệ quy của mình.

Ở phần đầu của bản ghi, gõ:

:let a = line('.')

Sau đó, ở phần cuối của bản ghi, thay vì nhấn @qđể tạo macro đệ quy, hãy gõ lệnh sau:

:if line('.') == a | exe 'norm @q' | endif

Cuối cùng kết thúc ghi âm của macro với q.

Lệnh cuối cùng bạn đã nhập sẽ phát lại macro q( exe 'norm @q') nhưng chỉ khi số dòng hiện tại ( line('.')) giống với lệnh ban đầu được lưu trong biến a.

Các :normallệnh cho phép bạn gõ lệnh thông thường (như @q) từ chế độ Ex.
Và lý do tại sao lệnh được gói thành một chuỗi và được thực thi bởi lệnh :executelà để ngăn chặn :normalviệc tiêu thụ (gõ) phần còn lại của lệnh ( |endif).


Ví dụ sử dụng.

Giả sử bạn có bộ đệm sau:

1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4

Và bạn muốn tăng tất cả các số từ một dòng tùy ý với một macro đệ quy.

Bạn có thể nhập 0để di chuyển con trỏ đến đầu dòng sau đó bắt đầu ghi macro:

qqq
qq
:let a=line('.')
<C-a>
w
:if line('.')==a|exe 'norm @q'|endif
q
  1. qqqxóa nội dung của thanh ghi qđể khi ban đầu bạn gọi nó trong định nghĩa của macro, nó sẽ không can thiệp
  2. qq bắt đầu ghi âm
  3. :let a=line('.') lưu trữ số dòng hiện tại bên trong biến a
  4. Ctrl+ atăng số dưới con trỏ
  5. w di chuyển con trỏ đến số tiếp theo
  6. :if line('.')==a|exe 'norm @q'|endif nhớ lại macro nhưng chỉ khi số dòng không thay đổi
  7. q dừng ghi âm

Khi bạn đã xác định macro của mình, nếu bạn định vị con trỏ của mình trên dòng thứ ba, nhấn 0để di chuyển nó đến đầu dòng, sau đó nhấn @qđể phát lại macro q, nó chỉ ảnh hưởng đến dòng hiện tại chứ không ảnh hưởng đến dòng khác:

1 2 3 4
1 2 3 4
2 3 4 5
1 2 3 4

Tạo một đệ quy macro sau khi ghi

Nếu bạn muốn, bạn có thể làm cho macro của mình đệ quy sau khi ghi bằng cách sử dụng thực tế là nó được lưu trong một chuỗi bên trong một thanh ghi và bạn có thể nối hai chuỗi với .toán tử dấu chấm .

Điều này sẽ cung cấp cho bạn một số lợi ích:

  • không cần xóa sổ đăng ký trước khi ghi, vì các ký tự @qsẽ được thêm vào macro sau khi được xác định và sau khi bạn đã ghi đè lên bất cứ nội dung cũ nào ở đó
  • không cần phải gõ bất cứ điều gì bất thường trong quá trình ghi, bạn có thể tập trung vào việc tạo một macro hoạt động đơn giản
  • khả năng kiểm tra nó trước khi làm cho nó đệ quy để xem cách nó hoạt động

Nếu bạn ghi lại macro của mình như bình thường (không đệ quy), bạn có thể làm cho nó đệ quy sau đó bằng lệnh sau:

let @q = @q . "@q"

Hoặc thậm chí ngắn hơn: let @q .= "@q"
.=là một toán tử cho phép nối một chuỗi với một chuỗi khác.

Điều này sẽ thêm 2 ký tự @qở cuối chuỗi phím được lưu trong thanh ghi q. Bạn cũng có thể xác định một lệnh tùy chỉnh:

command! -register RecursiveMacro let @<reg> .= "@<reg>"

Nó định nghĩa lệnh :RecursiveMacrochờ tên của thanh ghi làm đối số (vì -registerthuộc tính được truyền vào :command).
Đó là lệnh tương tự như trước đây, điểm khác biệt duy nhất là bạn thay thế mọi lần xuất hiện qbằng <reg>. Khi lệnh sẽ được thực thi, Vim sẽ tự động mở rộng mỗi lần xuất hiện <reg>với tên đăng ký bạn cung cấp.

Bây giờ, tất cả những gì bạn phải làm là ghi lại macro của bạn như bình thường (không đệ quy), sau đó nhập :RecursiveMacro qđể làm cho macro được lưu trữ bên trong thanh ghi qđệ quy.


Bạn có thể làm điều tương tự để tạo một đệ quy macro với điều kiện nó nằm trên dòng hiện tại:

let @q = ":let a=line('.')\r" . @q . ":if line('.')==a|exe 'norm @q'|endif\r"

Đó là điều tương tự chính xác được mô tả ở đầu bài, ngoại trừ lần này bạn làm điều đó sau khi ghi. Bạn chỉ cần nối hai chuỗi, một chuỗi trước và một chuỗi sau bất kỳ thao tác gõ phím nào mà qthanh ghi hiện có:

  1. let @q = xác định lại nội dung đăng ký q
  2. ":let a=line('.')\r"lưu trữ số dòng hiện tại bên trong biến atrước khi macro thực hiện công việc của nó
    \rlà cần thiết để báo cho Vim nhấn Enter và thực thi lệnh, xem :help expr-quotedanh sách các ký tự đặc biệt tương tự,
  3. . @q .nối các nội dung hiện tại của thanh qghi với chuỗi trước đó và chuỗi tiếp theo,
  4. ":if line('.')==a|exe 'norm @q'|endif\r"nhớ lại macro qvới điều kiện dòng không thay đổi

Một lần nữa, để lưu một số tổ hợp phím, bạn có thể tự động hóa quy trình bằng cách xác định lệnh tùy chỉnh sau:

command! -register RecursiveMacroOnLine let @<reg> = ":let a=line('.')\r" . @<reg> . ":if line('.')==a|exe 'norm @<reg>'|endif\r"

Và một lần nữa, tất cả những gì bạn phải làm là ghi lại macro của bạn như bình thường (không đệ quy), sau đó nhập :RecursiveMacroOnLine qđể làm cho macro được lưu trữ bên trong thanh ghi qđệ quy với điều kiện nó nằm trên dòng hiện tại.


Hợp nhất 2 lệnh

Bạn cũng có thể điều chỉnh :RecursiveMacrosao cho nó bao gồm 2 trường hợp:

  • tạo một đệ quy vĩ mô vô điều kiện,
  • tạo một đệ quy vĩ mô với điều kiện nó nằm trên dòng hiện tại

Để làm điều này, bạn có thể truyền đối số thứ hai cho :RecursiveMacro. Cái sau chỉ đơn giản là kiểm tra giá trị của nó và, tùy thuộc vào giá trị, sẽ thực thi một trong 2 lệnh trước đó. Nó sẽ cung cấp một cái gì đó như thế này:

command! -register -nargs=1 RecursiveMacro if <args> | let @<reg> .= "@<reg>" | else | let @<reg> = ":let a=line('.')\r" . @<reg> . ":if line('.')==a|exe 'norm @<reg>'|endif\r" | endif

Hoặc (sử dụng tiếp tục dòng / dấu gạch chéo ngược để dễ đọc hơn một chút):

command! -register -nargs=1 RecursiveMacro
           \ if <args> |
           \     let @<reg> .= "@<reg>" |
           \ else |
           \     let @<reg> = ":let a = line('.')\r" .
           \                  @<reg> .
           \                  ":if line('.')==a | exe 'norm @<reg>' | endif\r" |
           \ endif

Nó giống như trước đây, ngoại trừ lần này bạn phải cung cấp đối số thứ 2 cho :RecursiveMacro(vì -nargs=1thuộc tính).
Khi lệnh mới này sẽ được thực thi, Vim sẽ tự động mở rộng <args>với giá trị bạn cung cấp.
Nếu đối số thứ 2 này khác không / đúng ( if <args>) phiên bản đầu tiên của lệnh sẽ được thực thi (phiên bản tạo macro đệ quy vô điều kiện), nếu không, nếu không / sai thì phiên bản thứ hai sẽ được thực thi (phiên bản thứ hai sẽ được thực thi một đệ quy vĩ mô với điều kiện nó nằm trên dòng hiện tại).

Vì vậy, quay trở lại ví dụ trước, nó sẽ đưa ra điều sau:

qq
<C-a>
w
q
:RecursiveMacro q 0
3G
0@q
  1. qq bắt đầu ghi một macro bên trong thanh ghi q
  2. <C-a> tăng số dưới con trỏ
  3. w di chuyển con trỏ đến số tiếp theo
  4. q kết thúc ghi âm
  5. :RecursiveMacro q 0làm cho macro được lưu trữ bên trong thanh ghi q đệ quy nhưng chỉ cho đến cuối dòng (vì đối số thứ hai 0)
  6. 3G di chuyển con trỏ của bạn đến một dòng tùy ý (ví dụ 3)
  7. 0@q phát lại macro đệ quy từ đầu dòng

Nó sẽ cho kết quả tương tự như trước:

1 2 3 4
1 2 3 4
2 3 4 5
1 2 3 4

Nhưng lần này bạn không phải gõ các lệnh gây mất tập trung trong quá trình ghi macro, bạn có thể chỉ cần tập trung vào thực hiện một lệnh làm việc.

Và trong bước 5, nếu bạn đã chuyển một đối số khác không cho lệnh, nghĩa là nếu bạn đã gõ :RecursiveMacro q 1thay vì :RecursiveMacro q 0, macro qsẽ trở thành đệ quy vô điều kiện, sẽ đưa ra bộ đệm sau:

1 2 3 4
1 2 3 4
2 3 4 5
2 3 4 5

Lần này, macro sẽ không dừng ở cuối dòng thứ 3 mà ở cuối bộ đệm.


Để biết thêm thông tin, xem:

:help line()
:help :normal
:help :execute
:help :command-nargs
:help :command-register

2
Danh sách vị trí có thể được sử dụng để tiến qua các kết quả tìm kiếm trong một macro, miễn là macro không thay đổi vị trí của các kết quả khớp, ví dụ như :lv /\%3l\d/g %<CR>qqqqq<C-a>:lne<CR>@qq@qsẽ tăng tất cả các số trên dòng 3. Có thể có cách nào để giải pháp này trở nên dễ vỡ hơn?
djjcast

@djjcast Bạn có thể đăng nó dưới dạng câu trả lời, tôi đã thử và nó hoạt động rất tuyệt. Chỉ có một trường hợp tôi không hiểu, khi tôi thực thi macro trên dòng sau 1 2 3 4 5 6 7 8 9 10, tôi nhận được 2 3 4 5 6 7 8 9 10 12thay vì 2 3 4 5 6 7 8 9 10 11. Tôi không biết tại sao, có lẽ tôi đã nhầm lẫn một cái gì đó. Dù sao, nó có vẻ phức tạp hơn cách tiếp cận đơn giản của tôi và nó liên quan đến các biểu thức để mô tả nơi macro sẽ di chuyển con trỏ, cũng như một danh sách vị trí mà tôi chưa từng thấy được sử dụng theo cách này. Tôi rất thích nó!
saginaw

@djjcast Xin lỗi, tôi mới hiểu, vấn đề đơn giản đến từ regex của tôi, lẽ ra tôi nên sử dụng \d\+để mô tả nhiều chữ số.
saginaw

@djjcast Bây giờ tôi hiểu ý của bạn khi bạn nói macro không nên thay đổi vị trí của các trận đấu. Nhưng tôi không biết làm thế nào để giải quyết vấn đề này. Ý tưởng duy nhất tôi có là cập nhật danh sách vị trí từ bên trong macro nhưng tôi không quen với danh sách vị trí, nó quá phức tạp đối với tôi, thực sự xin lỗi.
saginaw

1
@saginaw Lặp lại các trận đấu theo thứ tự ngược có vẻ như nó sẽ giải quyết vấn đề trong hầu hết các trường hợp vì có vẻ như macro sẽ ít thay đổi vị trí của các trận đấu trước đó. Vì vậy, sau :lv ...lệnh, :llalệnh có thể được sử dụng để nhảy đến trận đấu cuối cùng và :lplệnh có thể được sử dụng để tiến lên các trận đấu theo thứ tự ngược lại.
djjcast

9

Một macro đệ quy sẽ dừng ngay khi gặp lệnh không thành công. Do đó, để dừng ở cuối dòng, bạn cần một lệnh sẽ thất bại ở cuối dòng.

Theo mặc định *, llệnh là một lệnh như vậy, vì vậy bạn có thể sử dụng nó để dừng macro đệ quy. Nếu con trỏ không ở cuối dòng, thì bạn chỉ cần di chuyển nó trở lại sau đó bằng lệnh h.

Vì vậy, sử dụng macro ví dụ tương tự như saginaw :

qqqqq<c-a>lhw@qq

Hỏng:

  1. qqq: Xóa đăng ký q,
  2. qq: Bắt đầu ghi một macro trong thanh qghi,
  3. <c-a>: Tăng số theo con trỏ,
  4. lh: Nếu chúng ta ở cuối dòng, hãy hủy bỏ macro. Nếu không, không làm gì cả.
  5. w: Chuyển sang từ tiếp theo trên dòng.
  6. @q: Tái diễn
  7. q: Dừng ghi âm.

Sau đó, bạn có thể chạy macro với cùng một 0@qlệnh như được mô tả bởi saginaw.


* 'whichwrap'Tùy chọn cho phép bạn xác định phím di chuyển nào sẽ quấn quanh dòng tiếp theo khi bạn ở đầu hoặc cuối dòng (Xem :help 'whichwrap'). Nếu bạn đã lđặt trong tùy chọn này, thì nó sẽ phá vỡ giải pháp được mô tả ở trên.

Tuy nhiên, có khả năng là bạn chỉ sử dụng một trong các lệnh bình thường chế độ mặc định cho ba tiến một nhân vật duy nhất ( <Space>, l<Right>), vì vậy nếu bạn đã lcó trong bạn 'whichwrap'thiết lập, bạn có thể loại bỏ một mà bạn không sử dụng từ 'whichwrap'tùy chọn, ví dụ <Space>:

:set whichwrap-=s

Sau đó, bạn có thể thay thế llệnh trong bước 4 của macro bằng một <Space>lệnh.


1
Cũng lưu ý rằng cài đặt virtualedit=onemoresẽ can thiệp vào việc sử dụng lđể phát hiện cuối dòng, mặc dù không nghiêm trọng như whichwrap=l.
Kevin

@Kevin Điểm tốt! Tôi sẽ cập nhật câu trả lời của mình để đề cập've'
Rich
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.