如何使vim的`:global`命令在执行ex命令之前进行确认?

12

我该如何使vim的:global命令询问用户是否要执行ex命令?类似于使用“c”选项的:substite命令所发生的情况,例如%s:Foo:Fighters:gc

我尝试过:

:g/mypattern/.s:.*\n::gc

:g/mypattern/s:.*\n::gc

但如果以下行匹配,程序将跳过该行。例如:

但如果以下行匹配,它会被跳过。例如:

MATCH
NONMATCH
MATCH

MATCH
MATCH

结果是:

NONMATCH

MATCH  <<-- this should be erased.

一个可提示的 g/FOO/d 是完美的。


相关:https://dev59.com/a2w15IYBdhLWcg3wZ68Y - Ciro Santilli OurBigBook.com
2个回答

5

这方面没有本地方法。典型的方法是记录宏并重复执行它。确保在宏末尾使用n/以跳转到下一个匹配项。现在跳过简单地使用n@@来执行宏。

自定义:Global命令

但是如果您真正想要带有确认的:global命令,可以通过在您的命令中使用confirm()来模拟这个功能。基本思路是这样的:

:g/pat/if confirm("&yes\n&no", 2) == 1 | cmd | endif

以下方法存在以下问题:

  • 无法确定光标所在位置。需要使用类似于:match:redraw的东西。
  • 无法有效地终止。需要一种抛出异常以终止的方式。
  • 输入太繁琐。

我想到了以下确认性:Global/:G命令:

command! -nargs=+ -range=% -complete=command Global <line1>,<line2>call <SID>global_confirm(<q-args>)
command! -nargs=+ -range=% -complete=command G <line1>,<line2>call <SID>global_confirm(<q-args>)
function! s:global_confirm(args) range
  let args = a:args
  let sep = args[0]
  let [pat, cmd; _] = split(args[1:], '\v([^\\](\\\\)*\\)@<!%d' . char2nr(sep), 1) + ['', '']
  match none
  let options = ['throw "Global: Abort"', cmd, '', 'throw "Global: Abort"']
  let cmd = 'exe ''match IncSearch /\c\%''.line(''.'').''l''.@/.''/'''
  let cmd .= '| redraw'
  let cmd .= '| exe get(options, confirm("Execute?", "&yes\n&no\n&abort", 2))'
  try
    execute a:firstline . ',' . a:lastline . 'g'.sep.pat.sep.cmd
  catch /Global: Abort/
  finally
    match none
  endtry
endfunction

注意:按原样使用。使用IncSearch进行高亮并强制使用\c
现在您可以运行:G / foo / d
自定义:Confirm命令
如果您更喜欢使用与@Randy Morris提供的技术类似的技术,并使用以下: Confirm {cmd} 命令在执行之前确认{cmd}
command! -nargs=+ -complete=command Confirm execute <SID>confirm(<q-args>) | match none
function! s:confirm(cmd)
  let abort = 'match none | throw "Confirm: Abort"'
  let options = [abort, a:cmd, '', abort]
  match none
  execute 'match IncSearch /\c\%' . line('.') . 'l' . @/ . '/'
  redraw
  return get(options, confirm('Execute?', "&yes\n&no\n&abort", 2), abort)
endfunction

这将允许您使用 :g/foo/Confirm d
查看更多帮助,请参阅:
:h @
:h q
:h confirm()
:h :exe
:h get()
:h :match
:h :redraw

非常喜欢这个答案。我强烈建议使用matchaddmatchdelete来突出显示当前行,而不是使用:match。因为这样做会清除任何手动匹配或其他插件的匹配。matchaddmatchdelete允许您在清理时仅清除您的匹配。 - Randy Morris

2
据我所知,本地没有办法做到这一点。我想我已经拼凑出了一种方法来做到这一点,但可能会有错误,因为我已经很长时间没有编写vimscript了。在这个方法中,我定义了一个命令C,它接受一个ex命令作为参数。如果您按下yY,则通过:global返回的每一行都会传递给这个ex命令。任何其他键都会跳过此行。
let s:GlobalConfirmSignNumber = 42
sign define GlobalConfirmMarker text=>> texthl=Search

function GlobalConfirm(cmd)
    let line = getpos(".")[1]
    execute "sign place " . s:GlobalConfirmSignNumber . " line=" . line . " name=GlobalConfirmMarker file=" . expand("%:p")
    redraw!
    echomsg "Execute? (y/N) "
    try
        let char = nr2char(getchar())
        if (char == "y" || char == "Y")
             execute a:cmd
        endif
    finally
        " Ensure signs are cleaned up if execution is aborted.
        execute "sign unplace " . s:GlobalConfirmSignNumber
    endtry
    redraw!
endfunction

command -nargs=* C call GlobalConfirm(<q-args>)

这是它运行时的gif。在这个gif中,我对每一行中包含“ba”的内容运行命令norm! gUU。在这种情况下,我通过按三次y确认了每个匹配项。

gif in action

如果有人能够改进这个(尤其是标志那部分),请随意编辑。

我使用 :match IncSearch /pat/ 来标记匹配项。我喜欢使用 :C 而不是像我最初做的那样创建一个新的 :global 命令。希望你不介意,但我在我的帖子中使用了这个想法作为另一种选择。 - Peter Rincker
不,我一点也不介意。我并不是很喜欢这个答案,主要是因为标志的原因。就目前而言,我对你的答案唯一的问题是使用 confirm 似乎会让我的显示出现问题(这也是我没有使用它的原因)。由于确认对话框占用了多行,它会将文本向上推,并隐藏我的测试中的前几行。不确定那里发生了什么。这可能是我使用的终端的副产品,我不确定。 - Randy Morris
我真的很喜欢你使用IncSearch来突出显示当前行。我会更新我的答案,但我认为我们基本上会趋于相同的结果,所以我会保持原样,这样就有一些不同的东西在这里。 - Randy Morris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接