jdtsmith / indent-bars

Fast, configurable indentation guide-bars for Emacs
GNU General Public License v3.0
272 stars 7 forks source link

Does anyone have experience if the treesitter indentation works with Lisp languages? #19

Closed Dima-369 closed 9 months ago

Dima-369 commented 10 months ago

I suppose one first needs to install https://github.com/Wilfred/tree-sitter-elisp or https://github.com/Alhadis/language-emacs-lisp and then test out if indent-bars behaves correctly with indent-bars-treesit-support: t?

Or is there a easier 'Lisp' mode in indent-bars?

For instance, if I open up a .el file in PyCharm, the indent bars correctly nest and excessive ones are hidden (I am a bit surprised that it even works out of the box and even inside the doc string) and here the indent bars are highly useful, even without proper syntax highlighting:

CleanShot 2023-09-11 at 08 03 11

Here, one also sees the nice usage of skipping indent bars for the (lambda as noted in https://github.com/jdtsmith/indent-bars/issues/18

I also noticed when one places the cursor over one parentheses (starting or closing), that the fringe shows the length of the entire wrapped expression (but this should be out-of-scope for this package, I think).

CleanShot 2023-09-11 at 08 08 05


Text:

(defmacro async-let (bindings &rest forms)
  "Implements `let', but each binding is established asynchronously.
For example:

  (async-let ((x (foo))
              (y (bar)))
     (message \"%s %s\" x y))

    expands to ==>

  (async-start (foo)
   (lambda (x)
     (async-start (bar)
      (lambda (y)
        (message \"%s %s\" x y)))))"
  (declare (indent 1))
  (async--fold-left
   (lambda (acc binding)
     (let ((fun (pcase (cadr binding)
                  ((and (pred functionp) f) f)
                  (f `(lambda () ,f)))))
       `(async-start ,fun
                     (lambda (,(car binding))
                       ,acc))))
   `(progn ,@forms)
   (reverse bindings)))
jdtsmith commented 10 months ago

I haven't tried an e-lisp tree-sitter, but in principle it should work the same as any other language. Please give it a try and comment back.; you'll need to use treesit-explore-mode and pick some relevant node types for indent-bars-treesit-wrap. The problem with lisp languages is that they do not respect indentation spacing really at all. I.e. most lines are lined up with opening parens of lines before them, not a fixed indent spacing.

For a fringe indicator, that shouldn't be too hard, but that should be a separate package.

Dima-369 commented 10 months ago

Please give it a try and comment back.; you'll need to use treesit-explore-mode and pick some relevant node types for indent-bars-treesit-wrap.

I tried with https://github.com/Wilfred/tree-sitter-elisp

Here is a code-dump if anyone wants to try it out as well. It doesn't seem to work well on nested structures apparently Maybe you have improvement ideas?

(dolist (grammar
         '((elisp "https://github.com/Wilfred/tree-sitter-elisp")))
  (add-to-list 'treesit-language-source-alist grammar)
  (unless (treesit-language-available-p (car grammar))
    (treesit-install-language-grammar (car grammar))))

(dolist (mapping '((emacs-lisp-mode . emacs-lisp-ts-mode)))
    (add-to-list 'major-mode-remap-alist mapping))

(define-derived-mode emacs-lisp-ts-mode emacs-lisp-mode "Emacs Lisp"
  "Major mode for editing Emacs Lisp files, using tree-sitter library."
  (when (treesit-ready-p 'elisp)
    (treesit-parser-create 'elisp)
    (treesit-major-mode-setup)))

;; remap 

(defun dima-indent-bars-emacs-lisp-setup ()
  "Setup for `emacs-lisp-ts-mode'."
  (setq-local indent-bars-treesit-support t)
  (setq-local indent-bars-treesit-wrap '((elisp list)))
  (setq-local indent-bars-treesit-ignore-blank-lines-types '("source_file"))
  (indent-bars-mode))

(add-hook 'emacs-lisp-ts-mode-hook #'dima-indent-bars-emacs-lisp-setup)

CleanShot 2023-09-11 at 21 49 55

I stuffed in more into indent-bars-treesit-wrap, but it doesn't get any better: (setq-local indent-bars-treesit-wrap '((elisp special_form quote list symbol)))

CleanShot 2023-09-11 at 21 51 38

Explorer

Lots of lists as expected ;)

CleanShot 2023-09-11 at 21 52 42

Text

(defmacro async-let (bindings &rest forms)
  "Implements `let', but each binding is established asynchronously.
For example:

  (async-let ((x (foo))
              (y (bar)))
     (message \"%s %s\" x y))

    expands to ==>

  (async-start (foo)
   (lambda (x)
     (async-start (bar)
      (lambda (y)
        (message \"%s %s\" x y)))))"
  (declare (indent 1))
  (async--fold-left
   (lambda (acc binding)
     (let ((fun (pcase (cadr binding)
                  ((and (pred functionp) f) f)
                  (f `(lambda () ,f)))))
       `(async-start ,fun
                     (lambda (,(car binding))
                       ,acc))))
   `(progn ,@forms)
   (reverse bindings)))

(defun test ()
  (if t
      (let ((yoyo)
            (bobo 123))
        (if t
            1
          2))
    2))
Dima-369 commented 10 months ago

Here is a screenshot how PyCharm does it (to me that's the reference implementation since it's the most advanced):

CleanShot 2023-09-11 at 22 02 27

Dima-369 commented 10 months ago

As an idea, I used https://github.com/sogaiu/tree-sitter-clojure for emacs-lisp-ts-mode, but it just doesn't work out at all. As soon as anything is in indent-bars-treesit-wrap, no indent bars are shown.

(setq-local indent-bars-treesit-wrap '((clojure list_lit)))
(setq-local indent-bars-treesit-ignore-blank-lines-types '("source"))
jdtsmith commented 10 months ago

Yeah. If you put list into wrap, indent-bars will simply avoid descending into any list further than the top level. So the result is not surprising. What did you expect to see?

I don't see PyCharm as being a reference here. The bar structure here looks strange, e.g. why doesn't the fun binding have its own bar?

image

I'll leave this open in case anyone has some ideas for a sensible skip/resume bar algorithm. With tree-sitter we have fast access to all containing nodes matching the wrap type, along with their starting buffer positions.