Skip to content

Folding blocks in python #31

@lijoantony

Description

@lijoantony

The current fold method used for folding blocks in python-ts-mode is a generic method which folds the entire function it is located in. For example consider the below code:

Image
  1. When point is on the for loop line, if I fold, this is what I get
Image

... because the default fold method fold's at the enclosing method or enclosing class level, not at the block level.

  1. If I try try to fold the first if block under the for loop,
Image

Again the entire method is folded.

Image

I cannot fold the outer if block either.

The ideal behavior would be to fold just that code block. This can be achieved by using a custom fold method for blocks in python-ts-mode (code given at the end).

AFTER

After setting up a custom fold method for blocks in the python-ts-mode, if my point is on the for loop and then when I fold the code..

Image

Only the for loop block is folded.

Image

I can also fold any individual if/else/elif blocks as I like.

Image

This is how I setup a custom method to fold blocks in python:

(defun my/treesit-fold-range-python-block (node offset)
    "Define fold range for `if_statement` and other blocks in python.
For arguments NODE and OFFSET, see function `treesit-fold-range-seq' for
more information."
    (when-let* ((colon-node (car (treesit-fold-find-children node ":")))
                (beg (treesit-node-start colon-node)))
      (let ((current-node (treesit-node-next-sibling colon-node))
            (last-body-node nil)
            (end nil))

        ;; Iterate through siblings until we hit an elif or else clause
        (while (and current-node
                    (not (member (treesit-node-type current-node)
                                 '("elif_clause" "else_clause"))))

          ;; Only consider non-comment nodes as body nodes
          (unless (string-match-p "comment" (treesit-node-type current-node))
            (setq last-body-node current-node))
          (setq current-node (treesit-node-next-sibling current-node)))

        ;; Set end position based on the last body node or fallback to node end
        (setq end (if last-body-node
                      (treesit-node-end last-body-node)
                    (treesit-node-end node)))

        ;; Return the range and offset to fold
        (treesit-fold--cons-add (cons (+ beg 1) end) offset))))

  (defun my/treesit-fold-parsers-python ()
    (append
     (treesit-fold-parsers-python)
     (mapcar (lambda (keyword) (cons keyword 'my/treesit-fold-range-python-block))
             '(while_statement for_statement if_statement elif_clause else_clause
                               match_statement case_clause try_statement except_clause with_statement))))

  ;; python mode
  (setq treesit-fold-range-alist (assq-delete-all 'python-mode treesit-fold-range-alist))
  (setq treesit-fold-range-alist (assq-delete-all 'python-ts-mode treesit-fold-range-alist))
  (add-to-list 'treesit-fold-range-alist `(python-ts-mode . ,(my/treesit-fold-parsers-python)))
  (add-to-list 'treesit-fold-range-alist `(python-mode . ,(my/treesit-fold-parsers-python)))

Not sure it can be added into the core since everyone's folding preferences are different. If it makes sense, then I can raise a PR for the same.
Otherwise, this issue migh help people who are searching for.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions