13

How can I fold all the methods in a Python class? I use Evil and would prefer a pure Evil solution, if possible.

I'm looking for a quick way to take something like this:

class LongClassIDidNotWrite():

    def method1():
        junk...
        junk...
        junk...

    def method2():
        junk...
        junk...
        junk...

    def method3():
        junk...
        junk...
        junk...

and make it look like this:

class LongClassIDidNotWrite():

    def method1():    
    def method2():    
    def method3():

I don't care if it has a ... at the end of the line or has a pretty {{{}}} notation telling me the number of lines folded. I just want it to fold!

I know I can press zc within a method to fold it. If I try something like VGzc, it doesn't fold. The fastest thing I've come up with is to record a macro qazc]]q and then replay it doing something like 100@a.

Looking at how Vim does it, posts say to :set foldmethod=indent. To which Evil says,

"State foldmethod=indent cannot be set as initial Evil state".

I've tried vimish-fold, along with evil-vimish-fold as well as origami. I can't get them to fold the methods within a class like above. They supposedly "just work". But that's not what I'm experiencing. Before I waste more time doing trial-and-error and scrounging, is there something I'm missing? It seems like this problem would be solved already.

3
  • 1
    Did you ever figure this out? Im having the exact same issue and have yet to find a working solution. Commented Apr 24, 2019 at 14:29
  • 2
    Assuming your point/cursor is on the class ... line, hs-hide-level will collapse everything with in the class, giving you your requested output. This would be unlike hs-hide-block which would collapse the entire class to one line.
    – nega
    Commented Apr 24, 2019 at 16:38
  • 1
    @nega, that's precisely what I was looking for! :) Throw that in an answer and I'll accept it. Commented Apr 24, 2019 at 20:36

2 Answers 2

16

It's not documented well enough, and possibly poorly named, but the hideshow function hs-hide-level will collapse all the blocks within the current block. That is, if your cursor is on the class ... line (or below it) in your example input, it will give you something very similar to your desired output. Since hideshow works with indentation I've found that it works best with well formated code or text.

Given:

class LongClassIDidNotWrite():

    def method1():
        print("")
        print("")
        print("")

    def method2():
        print("")
        print("")
        print("")

    def method3():
        print("")
        print("")
        print("")

Placing the cursor in "Long" on the class ... line and calling M-x hs-hide-level or C-c @ C-l will give you:

class LongClassIDidNotWrite():

    def method1():...)

    def method2():...)

    def method3():...)

This works on any point that is part of the class ... block. That is, that line, on the blank lines, and on the def ... lines. If you try it on the print ... lines it won't work, as you're in a new block, and there are no child blocks to fold.

hs-hide-level also runs a hook hs-hide-hook which could prove useful.

There is also a hs-hide-level-recursive which will recursively hide blocks in a region. It does not run hs-hide-hook, according to its documentation.

5

I believe the method given in the question, although not efficient, is sufficient. Let me explain it in greater detail. Assuming you have evil-mode enabled:

  1. Toggle hs-minor-mode to enable folding
  2. Place your cursor at the first column of a def statement line. This can be done by pressing 0
  3. Record a macro which folds the current section and then moves to the next section: qazc]]q.
    • q: begin recording a macro
    • a: name the new macro "a"
    • zc: close the current section (fold the section)
    • ]]: go to the next section via evil-forward-section-begin
    • q: finish recording the macro
  4. Replay the macro 100 times: 100@a
    • 100: the number of times to call the macro
    • @a: call macro with name "a"

Replace 100 with whatever number of folds you want to make. Or, simply repeat the macro call with @@.


I found that the real question I had was something like, "How can I get a quick overview of a class's methods?" or "How can I quickly navigate my code?"

To this end, elpy-mode has elpy-occur-definitions which lists all class and def statements with the occur function.

I find the defaults for occur clunky. Here is how I adjust the behavior to my tastes.

(evil-mode)                              ; enable evil mode
(add-hook 'python-mode-hook 'elpy-mode)  ; always use elpy-mode with python files

;; Make *Occur* window size to the contents
(setq fit-window-to-buffer-horizontally t)
(add-hook 'occur-hook
       (lambda ()
         (save-selected-window
           (pop-to-buffer "*Occur*")
           (fit-window-to-buffer))))

;; Make *Occur* window always open on the right side
(setq
 display-buffer-alist
 `(("\\*Occur\\*"
    display-buffer-in-side-window
    (side . right)
    (slot . 0)
    (window-width . fit-window-to-buffer))))

;; Automatically switch to *Occur* buffer 
(add-hook 'occur-hook
          '(lambda ()
             (switch-to-buffer-other-window "*Occur*")))

;; Modify *Occur* buffer navigation
;;; Set initial state of *Occur* buffer to normal mode
(evil-set-initial-state 'occur-mode 'normal)

;;; Use q or ESC to quit close the *Occur* window
(evil-define-key 'normal occur-mode-map (kbd "q") 'quit-window)
(evil-define-key 'normal occur-mode-map (kbd "<escape>") 'quit-window)

;;; Use TAB or SPC to show the occurrence in the other window
(evil-define-key 'normal occur-mode-map (kbd "TAB") 'occur-mode-display-occurrence)
(evil-define-key 'normal occur-mode-map (kbd "SPC") 'occur-mode-display-occurrence)

;;; Use <return> to jump to the occurrence and close the *Occur* window
(evil-define-key 'normal occur-mode-map (kbd "<return>") '(lambda () (interactive)
                                (occur-mode-goto-occurrence-other-window)
                                (kill-buffer "*Occur*")))

3
  • 2
    Nice tweaks to occur
    – nega
    Commented Apr 24, 2019 at 16:38
  • What is occur? I can not find this in the elpy docs.
    – buhtz
    Commented Sep 5, 2021 at 10:56
  • 1
    @buhtz, It's a native Emacs function to show all lines matching a pattern. See C-h f occur for details. Commented Sep 7, 2021 at 19:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.