如何在Emacs中实现代码折叠效果?

149

如何实现类似代码折叠或 org-mode 使用的循环类型?在 elisp 中创建此类行为的最佳解决方案是什么?

编辑:对不起,我的表述不够清晰。我想在 elisp 中编写某些与代码折叠非常相似的程序,更确切地说是与具有可扩展层次结构的 org-mode 最相似的程序。我想知道实现这种效果的最佳方法。我认为我听说过 Emacs 可以使用覆盖层来解决这个问题,但我不确定。

至于折叠,我只是使用内置的 set-selective-display。

第二次编辑:

感谢答案,但我认为我问错了问题,所以让我尝试更清楚地解释我要做什么。我想创建以下内容:

当你将光标放在一个函数上并调用这个elisp函数时,它会从任何位置(我想简单地使用 find-tag)获取函数定义并在当前缓冲区中展开。如果必须跳转到另一个缓冲区来查看函数定义,我觉得这是一种上下文切换到另一个文件。因此,我希望它的行为类似于代码折叠,但它会从缓冲区外部拉入代码。这会带来一些问题,因为它不能实际粘贴代码到缓冲区中,如果有人保存它,它将保存所提取的代码。因此,我想知道是否有办法在缓冲区内创建一个不是缓冲区的区域。我认为这很有意义。


1
你正在寻找一种方法,当你在函数调用中使用点时,它可以跳转到函数定义?就像获取有关elisp函数的帮助将让您跳转到elisp代码一样?我认为如果没有“项目”的概念,您无法做到这一点。例如,您如何知道要跳转到哪个“swap”函数? - Brian Postow
2
参见:https://dev59.com/cXRC5IYBdhLWcg3wMd9S#448724,其中选择性显示被微调以实现一些类似折叠的效果。 - Michael Paulukonis
我建议您删除您的 EDIT NB 2 并为此提出一个新问题。 - Stefan
14个回答

154

您可以使用hide-show模式 (hs-minor-mode)。要仅在当前缓冲区中即时激活此模式,请键入M-x hs-minor-mode RET

假设您有默认的按键绑定:

  • C-c @ C-M-h 折叠所有内容 (hs-hide-all)。
  • C-c @ C-h 隐藏当前块 (hs-hide-block)。光标必须位于该块内。
  • C-c @ C-s 显示当前块 (hs-show-block)。光标必须位于隐藏代码的{...}标记中。

21
这个功能很好用。在使用evil模式时,可以通过普通的vim命令来触发它:zRzMzozc - Justin M. Keyes
1
这应该是被接受的答案,这是在Emacs中折叠代码的方法。无论在哪里都非常有效,并且内置功能。 - learner

152

在Emacs中,折叠通常是不必要的,因为它具有明确实现手动折叠代码时所需操作的工具。

大多数人都成功地使用简单的渐进式搜索。看到某处提到了“foo”?键入C-sfoo,找到定义,按Enter键阅读,然后按C-x C-x返回原位置。这很简单且非常有用。

大多数模式都支持imenu。键入M-ximenu,可通过名称跳转到函数定义等内容。您还可以将其绑定到鼠标单击以获取函数菜单(或将其添加到菜单栏;有关详细信息,请参见Info页面)。它为which-function-mode提供数据,该模式可让您在模型行中查看当前内部的功能。 (但是为什么您的函数如此之长呢?)

还有speedbar,它以图形方式显示imenu信息(和其他内容)。

如果您想获得文件的概述,请尝试M-xoccur。给定一个正则表达式,它将创建一个新缓冲区,其中包含当前缓冲区中的每个匹配项。您可以搜索“(defun”以获取当前文件实现的功能概述。单击结果将使您移动到该文件中的该位置。

因此,考虑您真正想要实现什么,Emacs可能会实现它。不要花费时间来反复折叠和展开不完美的工具。


24
Vim提供了各种功能,但我仍然觉得它的代码折叠功能非常宝贵。 - intuited
158
我对第一句话(“通常情况下,使用Emacs时不需要折叠代码,因为......”)进行了反对投票。我经常使用基于缩进级别的简单折叠来扫描陌生代码的结构。 - Simon Michael
5
M-s o 是 M-x occur 的快捷键。 - Joshua Olson
6
我点赞了这个回答,因为它提供了许多有用的快捷方式,同时我也点赞了@SimonMichael的评论,因为折叠/展开有时仍然很有用! - Adrien Coquio
81
答案思考周到且内容丰富。然而,提问者询问如何进行代码折叠,而非每个人避免代码折叠的最爱方式。因此,被点踩了。 - wkschwartz
显示剩余3条评论

52

另一种皮猫方法:

碰巧的是,你不需要任何包或额外配置。只需进入任何源文件,输入

M-1 C-x $ 然后神奇地发生了!

像往常一样,这是白魔法:C-x $ 将使您的代码回归。

我们可以使用Emacs的帮助系统来发现发生了什么:C-h k C-x $告诉我们上述键组合调用了set-selective-display函数,该函数接受一个数值参数(M-1前缀将1作为该参数的值传递)并设置变量selective-display的值为该参数的值。

来自minor emacs wizardry 博客

完整性起见:M-3 C-x $会显示更深层嵌套的代码等等。

顺便说一句,今天我根据在这里找到的东西制作了一个小助手,使F5基于当前光标位置切换代码折叠:

(global-set-key (kbd "<f5>") 'set-selective-display-dlw)

(defun set-selective-display-dlw (&optional level)
"Fold text indented same of more than the cursor.
If level is set, set the indent level to LEVEL.
If 'selective-display' is already set to LEVEL, clicking
F5 again will unset 'selective-display' by setting it to 0."
  (interactive "P")
  (if (eq selective-display (1+ (current-column)))
      (set-selective-display 0)
    (set-selective-display (or level (1+ (current-column))))))

这很完美,而且set-selective-display-dlw非常好。谢谢。 - Hettomei

24
我使用大纲次要模式来折叠我的python代码。它不需要像折叠模式那样放置{{{和}}},而是使用定义块的位置。 http://www.gnu.org/software/emacs/manual/html_node/emacs/Outline-Mode.html http://www.emacswiki.org/emacs/OutlineMinorMode 我很确定这已经包含在了Emacs中。然后我将这个添加到我的.emacs文件中。
;;======= Code folding =======
(add-hook 'python-mode-hook 'my-python-outline-hook)
; this gets called by outline to deteremine the level. Just use the length of the whitespace
(defun py-outline-level ()
  (let (buffer-invisibility-spec)
    (save-excursion
      (skip-chars-forward "    ")
      (current-column))))
; this get called after python mode is enabled
(defun my-python-outline-hook ()
  ; outline uses this regexp to find headers. I match lines with no indent and indented "class"
  ; and "def" lines.
  (setq outline-regexp "[^ \t]\\|[ \t]*\\(def\\|class\\) ")
  ; enable our level computation
  (setq outline-level 'py-outline-level)
  ; do not use their \C-c@ prefix, too hard to type. Note this overides some bindings.
  (setq outline-minor-mode-prefix "\C-t")
  ; turn on outline mode
  (outline-minor-mode t)
  ; initially hide all but the headers
  ;(hide-body)
  ; make paren matches visible
  (show-paren-mode 1)
)

9
在Emacs 24中,似乎outline-mode支持Python的开箱即用,不需要自定义的outline-level函数。只需在user.el文件中添加以下代码即可:(add-hook 'python-mode-hook 'outline-minor-mode),即可启用大纲模式。 - scytale
2
哦,你肯定想要覆盖 outline-minor-mode-prefix - 默认的按键绑定很糟糕 - 我使用 (setq outline-minor-mode-prefix (kbd "C-;")) - scytale
@scytale 是的,outline-mode 在 emacs 中原生支持 python-mode - 但是上面的代码使用了 py-,因此仍然需要针对 python python-mode 使用该代码。 - mmmmmm

13
您也可以使用CEDET来实现代码折叠,只需在初始化文件中添加以下代码即可:
(global-semantic-folding-mode t)

在对此代码进行评估后,小三角形将出现在fringle区域,您将能够使用它来折叠和展开代码。这种方法更加精确,因为它使用从源代码中提取的句法信息。

1
如果遇到一些错误,请查看以下问题: https://dev59.com/9G_Xa4cB1Zd3GeqP02tp - AdrieanKhisbe
这个已经被弃用了吗? - undefined

11

hs-minor-mode非常有效。

如果与fold-dwim(意思是我想要什么)配对使用,效果更加出色。然后还有fold-dwim-org,为代码折叠提供了类似org-mode的按键绑定!两者都可以通过marmalade(我认为还有elpa)安装。


8

我很惊讶没有人提到缩窄-扩宽功能。这个功能非常棒,而且它也随 emacs 一起打包。只需选择要关注的代码,然后按下C-x n n进入缩窄模式,要返回完整视图模式只需执行C-x n w即可。我总是喜欢使用内置的emacs功能,而不是在我的用户空间中添加其他软件包。如果emacs可以使用内置功能完成它,那么我会更喜欢它,而不是一些第三方软件包。但这仅代表我的个人偏好,除此之外并没有其他含义。


1
缩小与折叠是不同的。折叠更多地涉及隐藏东西。当我们专注于代码的特定部分时,缩小非常有用。 - undefined

7

vimish-fold 是一个不错且易于使用的解决方案。

https://github.com/mrkkrp/vimish-fold

从主页上可以看到:

这是一个类似于 Vim 的文本折叠包。它具有以下功能:

  • 活动区域的折叠;
  • 良好的视觉反馈:很明显哪部分文本被折叠了;
  • 默认情况下持久性:当您关闭文件时,折叠不会消失;
  • 持久性能够很好地扩展,您可以在拥有大量折叠的数百个文件上工作而不会产生负面影响;
  • 它不会破坏缩进或其他东西;
  • 折叠可以非常容易地从折叠状态切换到展开状态和反之;
  • 快速导航到现有折叠之间;
  • 您可以使用鼠标展开折叠(适合初学者和其他人);
  • 对于 avy 包的粉丝:您可以使用最少的按键来折叠文本!

使用优秀的 use-package,我在我的配置文件中使用以下代码片段进行安装和激活:

(use-package vimish-fold
  :ensure t
  :config (vimish-fold-global-mode t))

6

3
我认为你将“项目”与折叠进行比较并不是很合适,因为折叠是改变外观而保持缓冲区内容(文本)不变。根据我理解,你的项目需要在保持缓冲区内容不变的同时显示额外的文本。因此,它不能作为文本插入和折叠的组合来实现(否则,缓冲区内容将会更改)。
但也许,使用与折叠相同的机制——“覆盖”——确实是可能的……考虑“before-string”和“after-string”覆盖属性;也许,您可以将函数定义放入这些字符串中,这些字符串属于一个零长度的覆盖点。查看大纲标志区域函数,以了解如何在大纲模式中使用覆盖。

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