自动折叠Ruby代码在Vim中

12

我是否可以设置Vim,使其自动折叠Ruby源文件,但仅在方法级别折叠,而不考虑它们定义的级别?

这样当我有以下代码时会自动折叠:

class MyClass
  def method
    ...
  end
end

但也当我拥有以下情况时:

module FirstModule
  module SecondModule
    class MyClass
      def method
        ...
      end
    end
  end
end

我尝试了foldmethod=syntax和各种折叠级别,但它没有考虑到方法定义的深度。

而且我不想折叠方法内部的任何内容(如果块、每个块等)。

我认为foldmethod=expr可能是最好的选择,但我还没有弄清楚折叠表达式的工作原理,vim中的帮助也没有提供很多启示。

5个回答

11

我相信你对使用expr方法的直觉是正确的!

你可以利用文件的语法结构来模拟自己的syntax样式折叠。以下是我的.vimrc中产生预期行为的代码:

function! RubyMethodFold(line)
  let line_is_method_or_end = synIDattr(synID(a:line,1,0), 'name') == 'rubyMethodBlock'
  let line_is_def = getline(a:line) =~ '\s*def '
  return line_is_method_or_end || line_is_def
endfunction

set foldexpr=RubyMethodFold(v:lnum)

注意:

我不确定 synID 的最后一个参数是否应该是 0 还是 1。该参数决定您在所提供位置获取顶层 透明非透明 元素的语法信息。当没有透明元素时,该参数没有影响。在我尝试的简单示例中,它没有引起任何问题,但可能会。

值得注意的是,line_is_def 正则表达式可能过于宽容。在这种情况下,返回 -1 可能更好,因此仅当匹配正则表达式的行紧挨着折叠的方法块时才进行折叠。更严格的正则表达式也可以起作用。

如果您感到有点兴奋,您可以扩展此功能,并为 rubyClassrubyModule 元素返回单独的折叠级别。

如果您决定采用这种方式,那么在我的 .vimrc 文件中有一些有用的自定义函数,可以用于内省语法元素和突出显示。将它们映射到快捷键上以进行快速使用最为方便:

nnoremap g<C-h> :echo GetSynInfo()<CR>

如果这对您有用,请务必让我知道!解决问题是一件有趣的事情。另外,值得一提的是,尽管:help'foldexpr'缺乏细节,但:help'fold-expr'更加有帮助,甚至在顶部还有一些示例。


2
这是一个很棒的答案。看起来这似乎是正确的解决方案,或者至少是一个很好的起点,可以帮助我达到想要的目标。我今天稍后会测试它的工作情况并接受你的答案。此外,你的vimrc看起来是一个很好的学习资源,特别是你定义的所有函数。谢谢。 - adivasile
顺便问一下,你对这个有什么想法? - Max Cantor
首先,synIDattr从未返回行的rubyMethodBlock。我只得到了rubyBlock。所以我将其更改为rubyBlock,但折叠方式很奇怪。如果从def语句折叠到具有if语句的方法中的第一行。我不太熟悉语法内省命令。今天稍后会进行一些研究,然后看看是否能解决这个问题。 - adivasile
请注意,synIDattr和synID适用于字符而不是行,因此在同一行上,您可能会遇到rubyMethodBlock字符后跟着rubyBlock字符。我想知道我们的语法文件版本是否非常不同?我的维护者是Doug Kearns,VCS Id为“ruby.vim,v1.152 2008/06/29 04:33:41 tpope Exp”。 - Max Cantor
1
完全没有问题。这根本不是一场徒劳的追逐,你指引了我一个好的方向,我学到了很多关于vim语法和折叠的知识。我想我会升级我的vim文件,或者只是调整你的解决方案以适应我的需求。 - adivasile
显示剩余3条评论

4

我对Max Cantor的解决方案进行了修改,我的方法完美地运行,它将折叠def方法(包括end关键字)和文档(=begin =end)块,无论它们在哪里(在类中还是缩进的位置)。

function! RubyMethodFold(line)
  let stack = synstack(a:line, (match(getline(a:line), '^\s*\zs'))+1)

  for synid in stack
    if GetSynString(GetSynDict(synid)) ==? "rubyMethodBlock" || GetSynString(GetSynDict(synid)) ==? "rubyDefine" || GetSynString(GetSynDict(synid)) ==? "rubyDocumentation"
      return 1
    endif
  endfor

  return 0
endfunction

set foldexpr=RubyMethodFold(v:lnum)

set foldmethod=expr

感谢Max Cantor提供的助手方法来打印语法信息。

让我逐行解释一下:

最重要的是第二行,它获取了一行中各种级别语法元素的堆栈。因此,如果您有一个带有空格或制表符的行,如 def methodname,它将始终在第一个非空字符处获取堆栈。Max Cantor解决方案的问题在于,他的代码只会打印出在行的第一列找到的最低语法元素,因此它总是返回一些随机的元素,例如我们不关心的"rubyContant"元素。

因此,通过使用match(getline(a:line), '^\s*\zs'),我们可以将光标移动到第一个非空字符的列,以便更准确地获取堆栈。+1,因为匹配是从零开始的。

然后剩下的就类似于GetSynInfo方法,在堆栈中循环匹配我们想要的元素,并返回1,以便它们都在同一折叠级别上,对于其他内容返回0。

这就是它,一个有效的折叠表达式函数,只会折叠Ruby定义方法 :)

祝您愉快,祝您有美好的一天!


1
你的回答看起来很有希望,但不幸的是当我尝试它时没有反应。似乎解决方案缺少需要定义的这些函数:GetSynStringGetSynDict。能否请您提供一下?谢谢。 - user777337

4

如果您已经使用语法高亮,那么以下内容可能会更加适合您:

将以下内容添加到您的 ~/.vimrc 文件中:

"" 基于语法规则启用折叠
set foldmethod=syntax
"" 调整折叠区域的高亮颜色 highlight Folded guibg=grey guifg=blue "" 将折叠映射到空格键 nnoremap za

这里还有一个关于折叠的不错的VIMcast: http://vimcasts.org/episodes/how-to-fold/


2
我为ruby编写了一个名为vim-ruby-fold的插件,用于简化方法折叠。我希望它能帮助人们更好地使用。

1

您也可以在方法内部使用zO命令将解决方案折叠起来,zO会递归打开折叠,因此如果您使用它而不是zo,那么好像方法内部没有任何东西被折叠一样。如果您感到狡猾,还可以重新映射zozO


这确实会有帮助,但主要问题仍然存在:需要自动折叠方法。 - adivasile

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