12

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.

Lorem Ipsum
  • 4,327
  • 2
  • 14
  • 35
  • 1
    Did you ever figure this out? Im having the exact same issue and have yet to find a working solution. – Mikael Hernrup Apr 24 '19 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 Apr 24 '19 at 16:38
  • 1
    @nega, that's precisely what I was looking for! :) Throw that in an answer and I'll accept it. – Lorem Ipsum Apr 24 '19 at 20:36

2 Answers2

14

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.

nega
  • 3,091
  • 15
  • 21
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*")))

Lorem Ipsum
  • 4,327
  • 2
  • 14
  • 35