Skip to content

paredit-kill freezes when killing the last form in the buffer #81

Open
@alexander-yakushev

Description

@alexander-yakushev
Member

Expected behavior

When standing at the beginning of a form that is the last one in the buffer and pressing C-k (which is bound to paredit-kill when Paredit is enabled), the form should be killed like any other form.

Actual behavior

Emacs freezes and spins CPU at 100%. Pressing C-g gets it out of this, but the form still remains.

Steps to reproduce the problem

Go to any Clojure file, enable Paredit, perform paredit-kill on the last form, e.g.:

Image

Environment & Version information

clojure-ts-mode version

clojure-ts-mode 0.4.0-snapshot (package: 20250415.804)

tree-sitter-clojure grammar version

Not sure.

Activity

rrudakov

rrudakov commented on Apr 15, 2025

@rrudakov
Contributor

Looks like a Emacs bug (or paredit bug).

it's only reproducible if there are one or more empty lines after the last expression. In normal clojure-mode when forward-sexp is called, the job is delegated to forward-sexp-default-function, which moves point to the end of the buffer if point is currently after the last sexp:

(defun forward-sexp-default-function (&optional arg)
  "Default function for `forward-sexp-function'."
  (goto-char (or (scan-sexps (point) arg) (buffer-end arg)))
  (if (< arg 0) (backward-prefix-chars)))

In clojure-ts-mode the job is done by treesit-forward-sexp, which doesn't move point to the end of the buffer, but keep it after the last closing paren of the last sexp.

When paredit-kill is called, at some point the function paredit-forward-sexps-to-kill calls forward-sexp until it reaches the end of the buffer:

(defun paredit-forward-sexps-to-kill (beginning eol)
  (let ((end-of-list-p nil)
        (firstp t))
    ;; Move to the end of the last S-expression that started on this
    ;; line, or to the closing delimiter if the last S-expression in
    ;; this list is on the line.
    (catch 'return
      (while t
        ;; This and the `kill-whole-line' business below fix a bug that
        ;; inhibited any S-expression at the very end of the buffer
        ;; (with no trailing newline) from being deleted.  It's a
        ;; bizarre fix that I ought to document at some point, but I am
        ;; too busy at the moment to do so.
        (if (and kill-whole-line (eobp)) (throw 'return nil))
        (save-excursion
          (paredit-handle-sexp-errors (forward-sexp)
            (up-list)
            (setq end-of-list-p (eq (point-at-eol) eol))
            (throw 'return nil))
          (if (or (and (not firstp)
                       (not kill-whole-line)
                       (eobp))
                  (paredit-handle-sexp-errors
                      (progn (backward-sexp) nil)
                    t)
                  (not (eq (point-at-eol) eol)))
              (throw 'return nil)))
        (forward-sexp)
        (if (and firstp
                 (not kill-whole-line)
                 (eobp))
            (throw 'return nil))
        (setq firstp nil)))
    end-of-list-p))

I'm not sure on which level this issue should be fixed, ideally treesit-forward-sexp should be fully compatible with forward-sexp-default-function, so maybe we should report it to Emacs bug tracker, I doubt though, that the fix will be installed to Emacs-30.

bbatsov

bbatsov commented on Apr 15, 2025

@bbatsov
Member

Yeah, I think this is an Emacs bug. Those are still quite common, when it comes to TreeSitter unfortunately.

rrudakov

rrudakov commented on Apr 16, 2025

@rrudakov
Contributor

I've just checked, and it's not reproducible on the latest Emacs master. We use correct treesit-things-settings in clojure-ts-mode and forward-sexp-function is set to forward-sexp-list, which behaves in a compatible way with forward-sexp-default-function.

I was planning to report this to Emacs bug tracker, but now it's not necessary.

bbatsov

bbatsov commented on Apr 16, 2025

@bbatsov
Member

@rrudakov Let's just add a note about this in the Caveats then.

rrudakov

rrudakov commented on Apr 16, 2025

@rrudakov
Contributor

OK, will do. I tried to come up with some advice function to fix it for Emacs-30, but I couldn't do it quickly.

rrudakov

rrudakov commented on Apr 16, 2025

@rrudakov
Contributor

@alexander-yakushev could you please try to add this to your init file and check if it solves the issue?

(defun treesit-fix (orig-fn &optional arg)
  (when (not (funcall orig-fn arg))
    (goto-char (buffer-end arg))))

(advice-add 'treesit-forward-sexp :around #'treesit-fix)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @bbatsov@alexander-yakushev@rrudakov

        Issue actions

          paredit-kill freezes when killing the last form in the buffer · Issue #81 · clojure-emacs/clojure-ts-mode