我在vi中打开了一个Ruby代码文件,其中有行注释为#:

class Search < ActiveRecord::Migration
  def self.up
    # create_table :searches do |t|
    #   t.integer :user_id
    #   t.string :name
    #   t.string :all_of
    #   t.string :any_of
    #   t.string :none_of
    #   t.string :exact_phrase
    # 
    #   t.timestamps
    # end
  end

  def self.down
    # drop_table :searches
  end
end

假设我想取消注释第一个定义中的所有行。。。端部。在Vim中有什么有效的方法可以做到这一点?

总的来说,我正在寻找一种简单、流畅的方式来注释和取消注释行。这里我处理的是Ruby代码,但可能是JavaScript(//)或Haml(-#)。


当前回答

:g/.spare[1-9].*/,+2s/^/\/\//

上面的代码将注释掉所有包含“备用”的行以及其后的一个数字,再加上它将从找到的行中注释两行。有关更多此类用途,请访问:http://vim.wikia.com/wiki/Search_and_replace#Details

其他回答

如何取消注释vi中的以下三行:

#code code
#code
#code code code

将光标放在左上角的#符号上,然后按CtrlV。这将使您进入视觉块模式。按下向下箭头或J三次以选择所有三条线。然后按D。所有评论都会消失。要撤消,请按U。

如何注释vi中的以下三行:

code code
code
code code code

将光标放在左上角的字符上,按CtrlV。这将使您进入视觉块模式。按↓ 或J三次以选择所有三条线。然后按:

I//Esc键

这是大写的I、//和Escape。

按ESC键时,所有选定的行都将获得指定的注释符号。

我喜欢/*…*/(安西评论),所以这是我给你的诀窍。当然,您可以调整它以适应不同的情况。


注释/**/

选择文本(转到开始,开始视觉块,用}跳转):

<c-V>}

键入要在选择中应用的命令

:norm i/* <c-v><esc>$a */

命令将显示为::'<,'>norm i/*^[$a*/

详见(i*)。


取消注释/**/

选择文本(如前所述,或其他您喜欢的方式):

<c-V>}

键入要在选择中应用的命令

:norm :s-\s*/\*\s*-<c-v><enter>$bbld$

命令将如下::“<,”>norm:s-\s*/\*\s*-^M$bbld$

详见(ii*)。


后果

效果是逐行注释:

Comment block
Comment block
Comment block

变为(反之亦然):

/* Comment block */
/* Comment block */
/* Comment block */

最好将其保存为.vimrc中的一些map或@reg,因为它需要大量输入。如果您喜欢单个/*和*/而不是整个块,请使用:

用单个/**/整个块注释

将其保存在一个寄存器中,记录,例如,qc,然后在段落开头注释:

v}di/*  */<esc>hhhp

不要再忘记q,完成记录。

详见(iii*)。


从块中取消注释单个/**/

将其保存在寄存器中,例如@u。将光标放在块内的任意位置,然后:

?/\*<enter>xx/\*/<enter>xx

通过完成q命令保存寄存器。

详见(iv*)。


后果

效果是多行的单个注释:

Comment block
Comment block
Comment block

变为(反之亦然):

/* Comment block
Comment block
Comment block */

解释

(i*)它通过使用norm来工作,norm在每个选定的行中重复应用相同的命令。命令只需插入一个/*,找到该行的结尾,然后插入*/

:norm i/* <c-v><esc>$a */

(ii*)它还使用规范在每一行重复搜索/替换。搜索空格/*空格,然后不替换。之后,找到行的结尾,返回两个单词,右对齐一个字母,删除到末尾。

:norm :s-\s*/\*\s*-<c-v><enter>$bbld$

(iii*)通过v}选择段落,删除它,打开和关闭插入注释,移动到其中间并粘贴删除的块。

v}di/*  */<esc>hhhp

(iv*)中间的任何位置,向后查找a/*,删除它;查找转发的*/,将其删除。

?/\*<enter>xx/\*/<enter>xx

如果您已经知道行号,那么n,ms/#//将起作用。

有时我会被推到一个远程盒子里,我的插件和.vimrc无法帮助我,或者有时NerdCommenter会出错(例如嵌入HTML中的JavaScript)。

在这些情况下,一种低技术的替代方法是内置的norm命令,它只在指定范围内的每一行运行任意vim命令。例如:

评论#:

1. visually select the text rows (using V as usual)
2. :norm i#

这将在每行的开头插入“#”。请注意,当您键入:范围将被填充,因此它看起来真的像:'<,'>norm i#

取消注释#:

1. visually select the text as before (or type gv to re-select the previous selection)
2. :norm x

这将删除每行的第一个字符。如果我使用了一个2个字符的注释,比如//,那么我只需要:norm xx来删除这两个字符。

如果注释如OP问题中那样缩进,那么您可以这样锚定删除:

:norm ^x

意思是“转到第一个非空格字符,然后删除一个字符”。请注意,与块选择不同,即使注释有不均匀的缩进,此技术也有效!

注意:由于norm实际上只是执行常规的vim命令,所以您不限于注释,还可以对每一行进行一些复杂的编辑。如果需要将转义符作为命令序列的一部分,请键入ctrl-v,然后按转义键(或者更简单,只需录制一个快速宏,然后使用norm在每行上执行该宏)。

注2:如果您发现自己经常使用规范,当然也可以添加映射。例如,在~/.vimrc中放入以下行可以让您在进行视觉选择后键入ctrl-n而不是:norm

vnoremap <C-n> :norm

注意3:裸骨vim有时没有编译规范命令,因此请确保使用增强版本,即通常为/usr/bin/vim,而不是/bin/vi

(感谢@Manbroski和@rakslice对本答案的改进)

首先,我要感谢@mike的回答,因为我使用的是它的修改版本。我想发布我的版本,以防有人感兴趣。我的主要功能区别在于,它将始终在射程的每一行上执行相同的动作。如果所选范围包含任何未注释的行,则每一行都会添加注释引线。这样,如果在未注释的代码块中有人类可读的注释,则它们不会变为未注释。然后,当您取消注释块时,人类可读的文本将保持注释状态,因为它有两个注释引线。完成后,它还会恢复光标位置。

ToggleComment函数:

function! ToggleComment() range
    "Ensure we know the comment leader.
    if !exists('b:comment_leader')
        echo "Unknown comment leader."
        return
    endif
    "Save the initial cursor position, to restore later.
    let l:inipos = getpos('.')
    "Make a list of all of the line numbers in the range which are already commented.
    let l:commented_lines = []
    for i in range(a:firstline, a:lastline)
        if getline(i) =~ '^\s*' . b:comment_leader
            let l:commented_lines = add(l:commented_lines, i)
        endif
    endfor
    "If every line in the range is commented, set the action to uncomment.
    "  Otherwise, set it to comment.
    let l:i1 = index(l:commented_lines, a:firstline)
    let l:i2 = index(l:commented_lines, a:lastline)
    if l:i1 >= 0 && l:i2 >= 0 && (l:i2 - l:i1) == (a:lastline - a:firstline)
        let l:action = "uncomment"
    else
        let l:action = "comment"
    endif
    "Loop through the range, commenting or uncommenting based on l:action.
    for i in range(a:firstline, a:lastline)
        "Move to line i.
        exec "exe " . i
        "Perform the action.
        if l:action == "comment"
            exec 'normal! 0i' . b:comment_leader
        else
            execute 'silent s,' . b:comment_leader . ',,'
        endif
    endfor
    "Restore the initial position.
    call setpos('.', l:inipos)
endfunction

键映射(注意,我将注释键更改为“k”):

noremap <Leader>k :call ToggleComment()<CR>

最后,我将autocmds放在下面的“if”语句和augroup中,但它们保持不变。它们大多只是供参考。

if has("autocmd")
    augroup autocmds
        autocmd!
        autocmd FileType c,cpp,java      let b:comment_leader = '//'
        autocmd FileType arduino         let b:comment_leader = '//'
        autocmd FileType sh,ruby,python  let b:comment_leader = '#'
        autocmd FileType zsh             let b:comment_leader = '#'
        autocmd FileType conf,fstab      let b:comment_leader = '#'
        autocmd FileType matlab,tex      let b:comment_leader = '%'
        autocmd FileType vim             let b:comment_leader = '"'
    augroup END
endif

编辑:我从原始答案更新了ToggleComment函数。最重要的是,我修改了正则表达式。在许多情况下,如果字符串中有另一个注释引线实例,它将无法正常工作。我相信它可能必须在前面或后面加一个空格,但我记不起来了。不管怎样,下面是Python的错误示例。

print("Hello, World!") # Says hello to the world.

我已经解决了这个问题,并稍微简化了正则表达式。一个副作用是它不再在评论标题后面添加空格,但这并不困扰我。我在一些变量中添加了一个局部声明,我在最初的答案中忘记了这些变量。