bmag / emacs-purpose

Manage Windows and Buffers According to Purposes
GNU General Public License v3.0
496 stars 23 forks source link

purpose-mode is not compatible with gdb command #163

Closed emacs18 closed 4 years ago

emacs18 commented 4 years ago

I use latest develop branch of spacemacs on emacs 27 on ubuntu 20.04.

purpose-mode seem to break built-in command gdb, i.e., M-x gdb fails to come up. Turning off purpose-mode minor mode still cause gdb to fail. However adding one line of code in purpose-mode minor mode to reset a variable seems to allow gdb to work at least when purpose-mode minor is disabled. If this minor is enabled, gdb still fail even with the one line change. Details follow.

If I type M-x gdb and type ls to debug ls program (or any other program), I get this stack trace where I shortened several very long lines with trailing "…":

Debugger entered--Lisp error: (wrong-type-argument listp display-buffer-in-previous-window)
  purpose-alist-combine((display-buffer-in-previous-window display-buffer-same-window display-buffer-pop-up-window) ((user-action-sequence display-buffer-reuse-window)))
  purpose--action-function(#<buffer *gud-ls*> (display-buffer-in-previous-window display-buffer-same-window display-buffer-pop-up-window))
  #f(compiled-function (buffer-or-name &optional action frame) "Display BUFFER-OR-NAME in some window, without selecting it.\nBUFFER-OR-NAME must be a buffer or a ...
  purpose-display-buffer-advice(#f(compiled-function (buffer-or-name &optional action frame) "Display BUFFER-OR-NAME in some window, without selecting it. ...
  apply(purpose-display-buffer-advice #f(compiled-function (buffer-or-name &optional action frame) "Display BUFFER-OR-NAME in some window, without selecting it. ...
  display-buffer(#<buffer *gud-ls*> (display-buffer-reuse-window display-buffer-in-previous-window display-buffer-same-window display-buffer-pop-up-window))
  gud-common-init("gdb -i=mi ls" nil gud-gdbmi-marker-filter)
  gdb("gdb -i=mi ls")
  funcall-interactively(gdb "gdb -i=mi ls")
  call-interactively(gdb record nil)
  command-execute(gdb record)
  helm-M-x-execute-command(gdb)
  helm-execute-selection-action-1()
  helm-execute-selection-action()
  helm-internal((((name . "Emacs Commands history") ...
  apply(helm-internal ((((name . "Emacs Commands history") ...
  helm((((name . "Emacs Commands history") ...
  apply(helm ((((name . "Emacs Commands history") ...
  helm(:sources (((name . "Emacs Commands history") ...
  helm-M-x-read-extended-command([## ...
  helm-M-x(nil)
  funcall-interactively(helm-M-x nil)
  call-interactively(helm-M-x nil nil)
  command-execute(helm-M-x)
  (let ((completion-styles completion-styles)) (add-to-list 'completion-styles (if (version< emacs-version "27") 'helm-flex 'flex) t) (command-execute 'helm-M-x))
  spacemacs/helm-M-x-fuzzy-matching()
  funcall-interactively(spacemacs/helm-M-x-fuzzy-matching)
  call-interactively(spacemacs/helm-M-x-fuzzy-matching nil nil)
  command-execute(spacemacs/helm-M-x-fuzzy-matching)

I'm not sure what this purpose-mode does, but I thought just disabling it should be sufficient via something like this:

(defadvice gdb (before disable-purpose-window activate compile)
  "Disable purpose-window minor mode which cause gdb failure."
  (purpose-mode 0))

However this causes yet another stack trace:

Debugger entered--Lisp error: (wrong-type-argument listp display-buffer-in-previous-window)
  purpose-alist-combine((display-buffer-in-previous-window display-buffer-same-window display-buffer-pop-up-window) nil)
  purpose--action-function(#<buffer *gud-ls*> (display-buffer-in-previous-window display-buffer-same-window display-buffer-pop-up-window))
  display-buffer(#<buffer *gud-ls*> (display-buffer-reuse-window display-buffer-in-previous-window display-buffer-same-window display-buffer-pop-up-window))
  gud-common-init("gdb -i=mi ls" nil gud-gdbmi-marker-filter)
  #f(compiled-function (command-line) "Run gdb passing it COMMAND-LINE as arguments. ...
  ad-Advice-gdb(#f(compiled-function (command-line) "Run gdb passing it COMMAND-LINE as arguments. ...
  apply(ad-Advice-gdb #f(compiled-function (command-line) "Run gdb passing it COMMAND-LINE ...
  gdb("gdb -i=mi ls")
  funcall-interactively(gdb "gdb -i=mi ls")
  call-interactively(gdb record nil)
  command-execute(gdb record)
  helm-M-x-execute-command(gdb)
  helm-execute-selection-action-1()
  helm-execute-selection-action()
  helm-internal((((name . "Emacs Commands history") ...
  apply(helm-internal ((((name . "Emacs Commands history") ...
  helm((((name . "Emacs Commands history") (candidates . ...
  apply(helm ((((name . "Emacs Commands history") (candidates . ...
  helm(:sources (((name . "Emacs Commands history") (candidates . ...
  helm-M-x-read-extended-command([## helm-ff-avfs-directory diary-included-files ...
  helm-M-x(nil)
  funcall-interactively(helm-M-x nil)
  call-interactively(helm-M-x nil nil)
  command-execute(helm-M-x)
  (let ((completion-styles completion-styles)) (add-to-list 'completion-styles (if (version< emacs-version "27") 'helm-flex 'flex) t) (command-execute 'helm-M-x))
  spacemacs/helm-M-x-fuzzy-matching()
  funcall-interactively(spacemacs/helm-M-x-fuzzy-matching)
  call-interactively(spacemacs/helm-M-x-fuzzy-matching nil nil)
  command-execute(spacemacs/helm-M-x-fuzzy-matching)

So I tried the following tweak to restore the value of display-buffer-overriding-action to its original value. This change does allow M-x gdb to work with purpose-mode turned off.

diff --git a/window-purpose.el b/window-purpose.el
index 8faa5f6..0bf7bde 100644
--- a/window-purpose.el
+++ b/window-purpose.el
@@ -293,15 +293,16 @@ This function is called when `purpose-mode' is deactivated."
 (define-minor-mode purpose-mode nil
   :global t :lighter (:eval (purpose--modeline-string))
   (if purpose-mode
       (progn
         (purpose--add-advices)
         (setq display-buffer-overriding-action
               '(purpose--action-function . nil))
         (setq purpose--active-p t)
         (purpose-fix-install))
     (purpose--remove-advices)
+    (setq display-buffer-overriding-action nil)
     (setq purpose--active-p nil)))

 (push '(purpose-dedicated . writable) window-persistent-parameters)
 (provide 'window-purpose)
 ;;; window-purpose.el ends here

Following is quote of the part of the doc-string of display-buffer-overriding-action.

This variable is not intended for user customization. Lisp programs should never set this variable permanently but may bind it around calls of buffer display functions like ‘display-buffer’ or ‘pop-to-buffer’. Since such a binding will affect any nested buffer display requests, this variable should be used with utmost care.

Perhaps this warning should be heeded by not messing with the variable at all it that is possible.

emacs18 commented 4 years ago

I looked into this some more. It seems like the bug was not in purpose-mode, but in emacs. I sent the following patch to emacs as detailed at https://debbugs.gnu.org/cgi/bugreport.cgi?bug=41888 If I hear back from emacs developers, I'll also updated this PR.

diff --git a/lisp/progmodes/gud.el b/lisp/progmodes/gud.el
index d5fd1dce6f..540bc9ce7f 100644
--- a/lisp/progmodes/gud.el
+++ b/lisp/progmodes/gud.el
@@ -2621,9 +2621,9 @@ comint mode, which see."
     (select-window
      (display-buffer
       (get-buffer-create (concat "*gud" filepart "*"))
-      '(display-buffer-reuse-window
-        display-buffer-in-previous-window
-        display-buffer-same-window display-buffer-pop-up-window)))
+      '((display-buffer-reuse-window
+         display-buffer-in-previous-window
+         display-buffer-same-window display-buffer-pop-up-window))))
     (when (and existing-buffer (get-buffer-process existing-buffer))
       (error "This program is already being debugged"))
     ;; Set the dir, in case the buffer already existed with a different dir.
emacs18 commented 4 years ago

This has been fixed upstream within emacs via https://github.com/emacs-mirror/emacs/commit/cce00bef0313bc42beee8096d9312313889dc92d