AmaiKinono/puni

`puni-kill-line` breaks the string

twlz0ne opened this issue · 5 comments

Test:

(with-temp-buffer
  (emacs-lisp-mode)
  (insert (format "%S" "(foo; bar)"))
  (goto-char 3) ;; -> "(|foo; bar)"
  (puni-kill-line)
  (buffer-string))

Results:

Environment:

$ sw_vers | xargs
ProductName: Mac OS X ProductVersion: 10.13.6 BuildVersion: 17G14042

$ emacs --batch --eval "(message emacs-version)"
28.0.50

$ git hist -10
* a076d32  (HEAD -> master, origin/master, origin/develop, origin/HEAD) fix: logic of "within" style [AmaiKinono] 2021-09-28
* 43e94e5  fix: kill single line comment properly [AmaiKinono] 2021-09-21
* 9145bd5  (rev2) enh: consider opening quotes of a single line comment to be balanced when deleting [AmaiKinono] 2021-09-20
* c3a2748  fix: cases when go to the start of next line from the last line in buffer, and the reverse case [AmaiKinono] 2021-09-20
* 3d971e1  fix: corner case when checking if at the beginning of single line comment [AmaiKinono] 2021-09-20
* 2e6ce8f  fix: go forward/backward a string only when outside one [AmaiKinono] 2021-09-20
* bffd398  (rev1) enh: more reasonable definition of syntax block [AmaiKinono] 2021-09-20
* a4f0031  (rev0) enh: handle "generic string fence" in forward/backward string functions [AmaiKinono] 2021-09-20
* 2cb9fc5  fix: when forward-sexp ignores the skippable punctuation forward [AmaiKinono] 2021-09-20
* 3ae440c  fix: kill a line properly to the end/start of the buffer [AmaiKinono] 2021-09-20

Should be fixed.

The cause is Emacs thinks the part between ; and the line end is a comment:

"(foo|; bar)"    ;; Call (forward-comment 1)
"(foo; bar)"|

I've recovered the behavior in rev1~rev2, so when this happens, Puni says "oh we can not move forward a sexp in this string, so we consider there's no sexp forward".

There may be better fixes. One way is to assume comments can't appear in strings, but I'm afraid people may actually put comment in template strings (which typically contain code snippets).

There may be better fixes. One way is to assume comments can't appear in strings, but I'm afraid people may actually put comment in template strings (which typically contain code snippets).

It will be better if there is an option switch. For me, I prefer not to consider the comment in string and edit the code snippet in an indirect buffer with specific language mode.

Nice idea!

I think I've find a better solution so no user option is needed.

When:

  1. moving forward a syntax block inside a string/comment
  2. there are multiple syntax constructs after point

Puni now choose one that doesn't take us outside of the string/comment. The same is done for moving backward.

So in this example:

"(foo|; bar)"

There's a comment after point (which ends after the string), and a ; after point (which is in the string). So now when you call puni-kill-line here, Puni will jump over that ;, then the bar, then we can't go forward within the string, so these things are deleted.

Do you like this solution?

I've been using the following for a long time:

(defvar dotemacs-puni-kill-line-timer nil)
(defvar dotemacs-puni-kill-line-thing nil)
(defvar dotemacs-puni-kill-line-point nil)

(defun dotemacs-puni-kill-line-advice (orig-fn &optional n)
  "Advice arround `puni-kill-line' to kill the rest part of string or comment.

If `puni-kill-line' (ORIG-FN) is triggered continuously at the same place for a
short period of time, it can be considered that the current deletion result is
not satisfied.

Original behavior:

  \"(|foo; bar))\"    -- C-k C-k -->    \"(|))\"

Changed behavior:
  
  \"(|foo; bar))\"    -- C-k C-k -->    \"(|)\""
  (if (and dotemacs-puni-kill-line-timer
           (memq dotemacs-puni-kill-line-thing '(string comment))
           (equal dotemacs-puni-kill-line-point (point)))
      ;; Supplemental kill
      (let ((kill-end
             (save-excursion
               (puni-up-list)
               (when (or (and (eq 'string dotemacs-puni-kill-line-thing)
                              (progn
                                (when (memq (char-before) '(?\' ?\"))
                                  (backward-char))
                                (puni--in-string-p)))
                         (and (eq 'comment dotemacs-puni-kill-line-thing)
                              (progn
                                (when (eq (char-before) ?\n)
                                  (backward-char))
                                (puni--in-comment-p))))
                 (point)))))
        (cancel-timer dotemacs-puni-kill-line-timer)
        (setq dotemacs-puni-kill-line-thing nil)
        (setq dotemacs-puni-kill-line-point nil)
        (when kill-end
          (kill-region (point) (min kill-end ))))
    ;; New kill
    (prog1 (funcall orig-fn n)
      (setq dotemacs-puni-kill-line-thing
            (cond ((puni--in-string-p) 'string)
                  ((puni--in-comment-p) 'comment)
                  (t nil)))
      (when dotemacs-puni-kill-line-thing
        (setq dotemacs-puni-kill-line-point (point))
        (setq dotemacs-puni-kill-line-timer
              (run-with-timer
               1 nil (lambda ()
                       (setq dotemacs-puni-kill-line-timer nil))))))))

(advice-add 'puni-kill-line :around #'dotemacs-puni-kill-line-advice)