Bad-ptr / persp-mode.el

named perspectives(set of buffers/window configs) for emacs
394 stars 44 forks source link

Non-nil uniquify-buffer-name-style breaks persp switching #104

Open hlissner opened 5 years ago

hlissner commented 5 years ago

The problem:

If uniquify-buffer-name-style is non-nil, then buffers with conflicting names are renamed retroactively. i.e. opening x.el in one directory and x.el in another will cause uniquify to rename them to a/x.el and b/x.el. This is a problem if you've opened a/x.el in perspective 1 and b/x.el in perspective 2. Switching between the two perspectives will cause persp-mode to silently fail to load the newly activated perspective's window configuration because, presumably, the buffers were serialized with their old names and now cannot find the aforementioned buffers by their original names.

Steps to reproduce:

From vanilla Emacs (emacs -Q):

;; omitted `load-path` setup

(require 'persp-mode)
(setq uniquify-buffer-name-style 'forward
      persp-auto-save-opt 0
      persp-auto-resume-time -1)
(persp-mode +1)

(persp-switch "a")
(find-file "a/file")
(persp-add-buffer)

(persp-switch "b")
(find-file "b/file")
(persp-add-buffer)

(persp-switch "a")

Expected result:

Observed result:

No messages are printed to *Messages* and no error is produced.

System information:

Bad-ptr commented 2 years ago

Something like:

(with-eval-after-load "persp-mode"
  (with-eval-after-load "uniquify"
    (defun persp--wc-bufs (wc)
      (let (buf-list)
        (cl-labels
            ((collect-wc-bufs (lst)
                              (cond
                               ((and lst (listp lst))
                                (let (head)
                                  (while (and (listp lst)
                                              (setq head (pop lst)))
                                    (cond
                                     ((listp head)
                                      (collect-wc-bufs head))
                                     (t (cl-case head
                                          ((hc vc leaf)
                                           nil)
                                          (buffer
                                           (push lst buf-list)))))))))))
          (collect-wc-bufs wc))
        buf-list))

    (defun persp-around-uniquify-rename-buffer (urb-f item buf-new-name)
      (if persp-mode
          (let* ((buf (uniquify-item-buffer item))
                 (buf-old-name (buffer-name buf))
                 (buf-persps (persp-persps))
                 (buf-new-name (funcall urb-f item buf-new-name)))
            (when (and buf-new-name (not (string= buf-old-name buf-new-name)))
              (cl-mapc
               #'(lambda (bl)
                   (when (string= (first bl) buf-old-name)
                     (setf (first bl) buf-new-name)))
               (cl-mapcan
                #'persp--wc-bufs
                (cl-mapcar #'safe-persp-window-conf buf-persps))))
            buf-new-name)
        (funcall urb-f item buf-new-name))))
  (advice-add #'uniquify-rename-buffer :around #'persp-around-uniquify-rename-buffer))

OR

(with-eval-after-load "persp-mode"
  (defun persp--wc-bufs (wc)
    (let (buf-list)
      (cl-labels
          ((collect-wc-bufs (lst)
                            (cond
                             ((and lst (listp lst))
                              (let (head)
                                (while (and (listp lst)
                                            (setq head (pop lst)))
                                  (cond
                                   ((listp head)
                                    (collect-wc-bufs head))
                                   (t (cl-case head
                                        ((hc vc leaf)
                                         nil)
                                        (buffer
                                         (push lst buf-list)))))))))))
        (collect-wc-bufs wc))
      buf-list))

  (defun persp-around-rename-buffer (rb-f &rest args)
    (if persp-mode
        (let* ((buf (current-buffer))
               (buf-old-name (buffer-name buf))
               (buf-persps ;; (persp--buffer-in-persps buf)
                (persp-persps))
               (buf-new-name (apply rb-f args)))
          ;;(message "AAA: %s %s %s" buf buf-old-name buf-new-name)
          (when (and buf-new-name (not (string= buf-old-name buf-new-name)))
            (cl-mapc
             #'(lambda (bl)
                 (when (string= (first bl) buf-old-name)
                   (setf (first bl) buf-new-name)))
             (cl-mapcan
              #'persp--wc-bufs
              (cl-mapcar #'safe-persp-window-conf buf-persps))))
          buf-new-name)
      (apply rb-f args)))
  (advice-add #'rename-buffer :around #'persp-around-rename-buffer))
Bad-ptr commented 2 years ago

OR

(with-eval-after-load "persp-mode"
  (defun persp--wc-to-writable (wc)
    (cl-labels
        ((wc-to-writable
          (it)
          (cond
           ((and it (listp it))
            (cl-destructuring-bind (head . tail) it
              (cond
               ;; ((listp head)
               ;;  (cons (wc-to-writable head)
               ;;        (wc-to-writable tail)))
               ((eq 'parameters head)
                (let ((rw-params
                       (delq nil
                             (mapcar
                              #'(lambda (pc)
                                  (when
                                      (and
                                       (alist-get (car pc) window-persistent-parameters)
                                       (persp-elisp-object-readable-p (cdr pc)))
                                    pc))
                              tail))))
                  (if rw-params
                      `(parameters
                        ,@rw-params)
                    :delete)))
               (t
                (let ((new-head (wc-to-writable head))
                      (new-tail (wc-to-writable tail)))
                  (when (eq :delete new-tail)
                    (setq new-tail nil))
                  (if (eq :delete new-head)
                      new-tail
                    (cons new-head
                          new-tail)))))))
           ((bufferp it)
            (if (buffer-live-p it)
                (buffer-name it)
              "*Messages*"))
           ((markerp it)
            (marker-position it))
           (t it))))
      (wc-to-writable wc)))

  (setq persp-window-state-get-function
        #'(lambda (&optional frame rwin)
            (when (or rwin (setq rwin (frame-root-window
                                       (or frame (selected-frame)))))
              (window-state-get rwin nil))))

  (add-hook 'persp-before-save-state-to-file-functions
            #'(lambda (_fname phash _rpfp)
                (mapc
                 #'(lambda (persp)
                     (if persp
                         (setf (persp-window-conf persp)
                               (persp--wc-to-writable (persp-window-conf persp)))
                       (setq persp-nil-wconf
                             (persp--wc-to-writable persp-nil-wconf))))
                 (persp-persps phash)))))

updated

awkspace commented 1 year ago

@Bad-ptr It seems like the fix you wrote in your comment works. Have you considered adding it to the main repo?