6

Most everyone who uses paredit has used paredit-kill (i.e., C-k), which allows one to delete a line forwards whilst keeping delimiters balanced properly. But how does one do this in the reverse direction? When one does C-0 C-k, which is something that's typically used to kill the line backwards, paredit-kill actually calls the built-in kill-line to handle it. Unfortunately, this loses the delimiter-balancing that is otherwise had with paredit-kill. Since discovering that C-0 C-k does not work quite right, I've tried implementing a paredit-backward-kill-line function that will kill backwards and balance delimiters a la paredit-kill. However, I've not had much luck with this and have only come up with an implementation which uses paredit-backward-delete under certain conditions. This solution has only worked marginally well for me thus far. I've also tried reworking some of paredit's functions that relate to killing lines, but it seems that they are all hard-coded to work with forwards deletion. This is making certain functions here significantly difficult to rework.

All in all, this seems like a lot of work to get something working that seems like it should be relatively simple, given that paredit already works as well as it does. So, I guess my question is: is there a way to do a backwards paredit-kill which retains all the delimiter-balancing functionality that is had from a normal, forwards paredit-kill?

4 Answers 4

1

Since no one here has come up with an answer that is sufficient for my needs, I've continued with my aforementioned implementation which uses paredit-backward-delete under certain conditions. What I've come up with isn't very pretty (nor very efficient), but it works well enough and seems to keep true to the spirit of paredit from an end-user perspective. Here is the actual function itself, which I named paredit-backward-delete-line:

(defun paredit-backward-delete-line ()
  "Delete line backwards, preserving delimiters and not adding to the kill ring."
  (interactive)
  ;; These variables are set upon invocation. The first one indicates whether this command
  ;; was invoked inside of a string and the second one is a placeholder variable that will
  ;; be used later.
  (setq-local paredit--started-in-string-p (paredit-in-string-p))
  (setq-local paredit--backward-region-p nil)

  ;; If a sexp or a string is behind us, and
  ;; * it's not an escaped character
  ;; * it's not a comment
  ;; * we're not in a string
  ;; ...
  (if (or (and (not (paredit-in-char-p (1- (point))))
               (not (paredit-in-comment-p))
               (eq (char-syntax (char-before)) ?\) ))
          (and (not (paredit-in-string-p))
               (eq (char-syntax (char-before)) ?\" )))
      (progn
        ;; Select the region...
        (set-mark-command nil)
        (setq deactivate-mark nil)
        ;; Do a `paredit-backward'...
        (paredit-backward)
        ;; Save the region as a string and indicate that this block of code has been visited.
        (setq-local paredit--backward-region (buffer-substring (region-beginning) (region-end)))
        (setq-local paredit--backward-region-p t)
        ;; Then delete the selected region.
        (delete-active-region)))
  ;; If the previous `progn' block has not been run or the region that was selected is only one line...
  (if (or (null paredit--backward-region-p) (<= (s-count-matches "\n" paredit--backward-region) 1))
      ;; Do the following in a loop for the number of the current column...
      (dotimes (i (current-column))
        ;; If a sexp is (still) behind us, recur (this will cause the previous `progn' block to run
        ;; and get rid of that sexp).
        (if (and (not (paredit-in-char-p (1- (point))))
                 (not (paredit-in-comment-p))
                 (eq (char-syntax (char-before)) ?\) ))
            (paredit-backward-delete-line))
        ;; Otherwise, make sure: we're not in a comment, that the thing behind us isn't an escaped character,
        ;; that it's the beginning of a form or list, and that we're in an empty form / list. If that check does
        ;; not pass, see whether we started in a string and if we're in an empty string currently. If either of
        ;; these checks pass, do nothing.
        (unless (or (and (not (paredit-in-char-p (1- (point))))
                         (not (paredit-in-comment-p))
                         (eq (char-syntax (char-before)) ?\( )
                         (eq (char-after) (matching-paren (char-before))))
                    (and paredit--started-in-string-p
                         (eq (1- (point)) (car (paredit-string-start+end-points)))
                         (eq (point) (cdr (paredit-string-start+end-points)))))
          ;; If neither of those checks pass, delete backwards when we're in a comment or do a `paredit-backward-delete'
          ;; otherwise.
          (if (paredit-in-comment-p) (delete-backward-char 1) (paredit-backward-delete 1)))))
  ;; Finally, clear the echo area (`paredit-backward-delete' can be noisy).
  (message nil))

Note that it does not kill the line backwards, but rather deletes it. This is actually the behavior that I really wanted in the first place, but I asked the question with killing in mind because I thought it would be easier for someone to implement that because paredit-kill already exists and does this sort of thing.

Anyway, I bound this function to <C-backspace> like so:

(define-key paredit-mode-map (kbd "<C-backspace>") 'paredit-backward-delete-line)

And now I enjoy backwards deletion of lines which respects delimiters. Feel free to convert this function to utilize killing instead; it would be fairly easy to do.

0

You could use lispy. What you describe can be achieved by marking a sexp (m or M-m), switching to the different side of the region (d), and growing the region by as many sexps as possible (0>). At this point you have marked the stuff that you want killed or deleted. Press C-w or C-d appropriately.

1
  • Thanks for the suggestion. I think I'll try it out, although I harbor distaste for modal editing (even lispy's partial modal editing seems a bit too much for me).
    – GDP2
    Jan 3, 2017 at 20:54
0

Good question. I've come up with this (still, first) draft. As queried it's about killing line backward from point including keeping sexp balance as paredit does watch for it.

(defun paredit-kill-backward-from-point ()
     (interactive)
     (let ((prev-pos (point)))
       (paredit-backward-up)
       (paredit-forward-down)
       (kill-region (point) prev-pos)))
0

There is a function in Vanilla Emacs for this backward-kill-sexp, so I do the following bind:

(global-set-key (kbd "C-S-k") (lambda () (interactive) (backward-kill-sexp)))

https://www.gnu.org/software/emacs/manual/html_node/emacs/Expressions.html

I don't think it is anymore a standard binding (if somebody knows why it was removed, I would be curious to know, perhaps because it was bound to C-M-DEL), but below is the source code:

(defun backward-kill-sexp (&optional arg)
    "Kill the sexp (balanced expression) preceding the cursor.
    With ARG, kill that many sexps before the cursor.
    Negative arg -N means kill N sexps after the cursor."
    (interactive "p")
    (kill-sexp (- (or arg 1))))
2
  • (global-set-key (kbd "C-S-k") (lambda () (interactive) (backward-kill-sexp))) is the same as (global-set-key (kbd "C-S-k") 'backward-kill-sexp) (except the latter also lets you optionally use a prefix arg).
    – Drew
    Dec 24, 2020 at 0:13
  • Thanks. Sorry what does your comment mean in the context of the answer I posted? Was my answer incorrect? Reading your comment suggests I should use the second approach in your comment since that is better, with the additional optionality. Was that what you were trying to say? I'm very new to Emacs so apologise for the stupid questions Dec 24, 2020 at 14:33

Your Answer

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

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