From: Yuan Fu <casouri@gmail.com>
To: martin rudalics <rudalics@gmx.at>
Cc: 39181@debbugs.gnu.org
Subject: bug#39181: 27.0.50; [PATCH] Allow users to store & restore gdb-mi layout
Date: Thu, 5 Mar 2020 01:12:30 -0500 [thread overview]
Message-ID: <90254CFF-2FFD-4D54-8467-717FF10166A2@gmail.com> (raw)
In-Reply-To: <55235252-FD03-4801-804A-864EFC1DCA0A@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 26 bytes --]
Here is the patch.
Yuan
[-- Attachment #2: new-window.patch --]
[-- Type: application/octet-stream, Size: 18137 bytes --]
From c5226b26f117806572da3cc0acbe709c37880181 Mon Sep 17 00:00:00 2001
From: Yuan Fu <casouri@gmail.com>
Date: Tue, 3 Mar 2020 18:30:03 -0500
Subject: [PATCH] Add window streo/restore feature for gdb-mi
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add a feature that allows a user to save a gdb window layout to a file
with 'gdb-save-window-layout' and load it back it with
'gdb-load-window-layout'. Set a default window configuration by
setting 'gdb-default-window-layout-file'. Note that for the default
window configuration to take effect, 'gdb-many-windows' needs to be t.
Make gdb preserve the window configuration that the user had before
starting gdb. In window.el, add 'with-selected-window-undedicated'.
* lisp/progmodes/gdb-mi.el (require): add 'pcase', wrap 'pcase' and 'cl-lib'
inside ‘eval-when-compile’.
(gdb--window-configuration-before): New variable.
(gdb-restore-window-layout-after-quit): New custom variable.
(gdb): Save configuration on startup.
(gdb-reset): Restore window configuration after quit.
(gdb-window-layout-directory, gdb-default-window-layout-file): New
variables.
(gud-menu-map): Add "Load window layout" and "Save window layout" to
menu. Add "Restore window layout" button to menu.
(gdb-toggle-restore-window-layout): New function.
(gdb-get-source-buffer): New function, extracted out of
'gdb-restore-window'.
(gdb-setup-windows): Add a condition branch that loads default window
layout when available.
(gdb-buffer-p, gdb-function-buffer-p, gdb--buffer-type,
gdb-save-window-layout, gdb-load-window-layout): New functions.
(gdb-many-windows, gdb-get-source-file): Add comments.
* lisp/window.el (with-selected-window-undedicated): New function.
---
lisp/progmodes/gdb-mi.el | 267 ++++++++++++++++++++++++++++++++++-----
lisp/window.el | 13 ++
2 files changed, 245 insertions(+), 35 deletions(-)
diff --git a/lisp/progmodes/gdb-mi.el b/lisp/progmodes/gdb-mi.el
index da5a2a503a..c8d4c0ceab 100644
--- a/lisp/progmodes/gdb-mi.el
+++ b/lisp/progmodes/gdb-mi.el
@@ -91,7 +91,8 @@
(require 'gud)
(require 'json)
(require 'bindat)
-(require 'cl-lib)
+(eval-when-compile (require 'cl-lib))
+(eval-when-compile (require 'pcase))
(declare-function speedbar-change-initial-expansion-list
"speedbar" (new-default))
@@ -253,6 +254,25 @@ gdb-output-sink
disposition of output generated by commands that
gdb mode sends to gdb on its own behalf.")
+(defvar gdb--window-configuration-before nil
+ "Stores the window configuration before starting gdb.")
+
+(defcustom gdb-restore-window-layout-after-quit nil
+ "Specify whether to restore the window layout the user had before gdb starts.
+
+Possible values are:
+ t -- Always restore.
+ nil -- Don't restore.
+ 'if-show-main -- Restore only if `gdb-show-main' is non-nil
+ 'if-many-windows -- Restore only if variable `gdb-many-windows' is non-nil."
+ :type '(choice
+ (const :tag "Always restore" t)
+ (const :tag "Don't restore" nil)
+ (const :tag "Depends on `gdb-show-main'" 'if-gdb-show-main)
+ (const :tag "Depends on `gdb-many-windows'" 'if-gdb-many-windows))
+ :group 'gdb
+ :version "28.1")
+
(defcustom gdb-discard-unordered-replies t
"Non-nil means discard any out-of-order GDB replies.
This protects against lost GDB replies, assuming that GDB always
@@ -603,6 +623,20 @@ gdb-show-main
:group 'gdb
:version "22.1")
+(defcustom gdb-window-layout-directory user-emacs-directory
+ "The default directory where window configuration files are stored.
+If nil, use `default-directory'."
+ :type 'string
+ :group 'gdb
+ :version "28.1")
+
+(defcustom gdb-default-window-layout-file nil
+ "If non-nil, gdb loads this window layout file on startup.
+If not absolute, GDB will look under `gdb-window-layout-directory'."
+ :type 'string
+ :group 'gdb
+ :version "28.1")
+
(defvar gdbmi-debug-mode nil
"When non-nil, print the messages sent/received from GDB/MI in *Messages*.")
@@ -761,6 +795,12 @@ gdb
(gdb-restore-windows)
(error
"Multiple debugging requires restarting in text command mode"))
+
+ ;; Save window configuration before starting gdb so we can restore
+ ;; it after gdb quits. Save it regardless of the value of
+ ;; `gdb-restore-window-layout-after-quit'.
+ (setq gdb--window-configuration-before (window-state-get))
+
;;
(gud-common-init command-line nil 'gud-gdbmi-marker-filter)
@@ -4491,6 +4531,13 @@ gdb-preempt-existing-or-display-buffer
(define-key gud-menu-map [displays]
`(menu-item "GDB-Windows" ,menu
:visible (eq gud-minor-mode 'gdbmi)))
+ (define-key menu [load-layout] '("Load window layout" "Load GDB window layout from a file" . gdb-load-window-layout))
+ (define-key menu [save-layout] '("Save window layout" "Save current GDB window layout to a file" . gdb-save-window-layout))
+ (define-key menu [restore-layout-when-finish]
+ '(menu-item "Restore window layout"
+ gdb-toggle-restore-window-layout
+ . (:button (:toggle . gdb-restore-window-layout-after-quit)
+ :help "If on, GDB restores the window layout you had before starting GDB after it quits")))
(define-key menu [gdb] '("Gdb" . gdb-display-gdb-buffer))
(define-key menu [threads] '("Threads" . gdb-display-threads-buffer))
(define-key menu [memory] '("Memory" . gdb-display-memory-buffer))
@@ -4606,41 +4653,175 @@ gdb-set-window-buffer
(set-window-buffer window (get-buffer name))
(set-window-dedicated-p window t))
+(defun gdb-toggle-restore-window-layout ()
+ "Toggle whether to restore window layout when GDB quit."
+ (interactive)
+ (setq gdb-restore-window-layout-after-quit
+ (not gdb-restore-window-layout-after-quit)))
+
+(defun gdb-get-source-buffer ()
+ "Return a buffer displaying source file or nil.
+
+The source file would be the most relevant file or the main file."
+ (if gud-last-last-frame
+ (gud-find-file (car gud-last-last-frame))
+ (when gdb-main-file
+ (gud-find-file gdb-main-file))))
+
(defun gdb-setup-windows ()
"Layout the window pattern for option `gdb-many-windows'."
- (gdb-get-buffer-create 'gdb-locals-buffer)
- (gdb-get-buffer-create 'gdb-stack-buffer)
- (gdb-get-buffer-create 'gdb-breakpoints-buffer)
- (set-window-dedicated-p (selected-window) nil)
- (switch-to-buffer gud-comint-buffer)
- (delete-other-windows)
- (let ((win0 (selected-window))
- (win1 (split-window nil ( / ( * (window-height) 3) 4)))
- (win2 (split-window nil ( / (window-height) 3)))
- (win3 (split-window-right)))
- (gdb-set-window-buffer (gdb-locals-buffer-name) nil win3)
- (select-window win2)
- (set-window-buffer
- win2
- (if gud-last-last-frame
- (gud-find-file (car gud-last-last-frame))
- (if gdb-main-file
- (gud-find-file gdb-main-file)
- ;; Put buffer list in window if we
- ;; can't find a source file.
- (list-buffers-noselect))))
- (setq gdb-source-window (selected-window))
- (let ((win4 (split-window-right)))
- (gdb-set-window-buffer
- (gdb-get-buffer-create 'gdb-inferior-io) nil win4))
- (select-window win1)
- (gdb-set-window-buffer (gdb-stack-buffer-name))
- (let ((win5 (split-window-right)))
- (gdb-set-window-buffer (if gdb-show-threads-by-default
- (gdb-threads-buffer-name)
- (gdb-breakpoints-buffer-name))
- nil win5))
- (select-window win0)))
+ (if gdb-default-window-layout-file
+ (gdb-load-window-layout
+ (if (file-name-absolute-p gdb-default-window-layout-file)
+ gdb-default-window-layout-file
+ (expand-file-name gdb-default-window-layout-file
+ gdb-window-layout-directory)))
+ ;; Create default layout as before.
+ (gdb-get-buffer-create 'gdb-locals-buffer)
+ (gdb-get-buffer-create 'gdb-stack-buffer)
+ (gdb-get-buffer-create 'gdb-breakpoints-buffer)
+ (set-window-dedicated-p (selected-window) nil)
+ (switch-to-buffer gud-comint-buffer)
+ (delete-other-windows)
+ (let ((win0 (selected-window))
+ (win1 (split-window nil ( / ( * (window-height) 3) 4)))
+ (win2 (split-window nil ( / (window-height) 3)))
+ (win3 (split-window-right)))
+ (gdb-set-window-buffer (gdb-locals-buffer-name) nil win3)
+ (select-window win2)
+ (set-window-buffer
+ win2
+ (or (gdb-get-source-buffer)
+ (list-buffers-noselect)))
+ (setq gdb-source-window (selected-window))
+ (let ((win4 (split-window-right)))
+ (gdb-set-window-buffer
+ (gdb-get-buffer-create 'gdb-inferior-io) nil win4))
+ (select-window win1)
+ (gdb-set-window-buffer (gdb-stack-buffer-name))
+ (let ((win5 (split-window-right)))
+ (gdb-set-window-buffer (if gdb-show-threads-by-default
+ (gdb-threads-buffer-name)
+ (gdb-breakpoints-buffer-name))
+ nil win5))
+ (select-window win0))))
+
+(defun gdb-buffer-p (buffer)
+ "Return t if BUFFER is gdb-related."
+ (with-current-buffer buffer
+ (eq gud-minor-mode 'gdbmi)))
+
+(defun gdb-function-buffer-p (buffer)
+ "Return t if BUFFER is a gdb function buffer.
+
+E.g., locals buffer, registers buffer, but don't include the main
+command buffer (the one in where you type gdb commands) or source
+buffers."
+ (with-current-buffer buffer
+ (derived-mode-p 'gdb-parent-mode 'gdb-inferior-io-mode)))
+
+(defun gdb--buffer-type (buffer)
+ "Return the buffer type of BUFFER or nil.
+
+Buffer type is like `gdb-registers-type', `gdb-stack-buffer'.
+This symbol can be passed to `gdb-get-buffer-create'.
+
+Return nil if BUFFER isn't a gdb function buffer."
+ (with-current-buffer buffer
+ (cl-loop for rule in gdb-buffer-rules
+ for mode-name = (gdb-rules-buffer-mode rule)
+ for type = (car rule)
+ if (eq mode-name major-mode)
+ return type
+ finally return nil)))
+
+(defun gdb-save-window-layout (file)
+ "Save current window layout (window configuration) to FILE.
+
+You can later restore this layout from that file by
+`gdb-load-window-layout'."
+ (interactive (list (read-file-name
+ "Save window configuration to file: "
+ (or gdb-window-layout-directory default-directory))))
+ ;; We replace the buffer in each window with a placeholder, store
+ ;; the buffer type (register, breakpoint, etc) in window parameters,
+ ;; and write the window configuration to the file.
+ (save-window-excursion
+ (let ((placeholder (get-buffer-create " *gdb-placeholder*"))
+ (window-persistent-parameters
+ (cons '(gdb-buffer-type . writable) window-persistent-parameters))
+ window-config)
+ (unwind-protect
+ (dolist (win (window-list nil 'no-minibuffer))
+ (select-window win)
+ (when (gdb-buffer-p (current-buffer))
+ (set-window-parameter
+ nil 'gdb-buffer-type
+ (cond ((gdb-function-buffer-p (current-buffer))
+ ;; 1) If a user arranged the window
+ ;; configuration herself and saves it, windows
+ ;; are probably not dedicated. 2) We use the
+ ;; same dedication flag as in
+ ;; `gdb-display-buffer'.
+ (set-window-dedicated-p nil t)
+ ;; We save this gdb-buffer-type symbol so
+ ;; we can later pass it to `gdb-get-buffer-create';
+ ;; one example: `gdb-registers-buffer'.
+ (or (gdb--buffer-type (current-buffer))
+ (error "Unrecognized gdb buffer mode: %s" major-mode)))
+ ;; Command buffer.
+ ((derived-mode-p 'gud-mode) 'command)
+ ((equal (selected-window) gdb-source-window) 'source)))
+ (with-selected-window-undedicated
+ (set-window-buffer nil placeholder)
+ (set-window-prev-buffers (selected-window) nil)
+ (set-window-next-buffers (selected-window) nil))))
+ ;; Save the window configuration to FILE.
+ (let ((window-config (window-state-get nil t)))
+ (with-temp-buffer
+ (prin1 window-config (current-buffer))
+ (write-file file t)))
+ (kill-buffer placeholder)))))
+
+(defun gdb-load-window-layout (file)
+ "Restore window layout (window configuration) from FILE.
+
+FILE should be a window layout file saved by
+`gdb-save-window-layout'."
+ (interactive (list (read-file-name
+ "Restore window configuration from file: "
+ (or gdb-window-layout-directory default-directory))))
+ ;; Basically, we restore window configuration and go through each
+ ;; window and restore the function buffers.
+ (let* ((placeholder (get-buffer-create " *gdb-placeholder*")))
+ (unwind-protect ; Don't leak buffer.
+ (let ((window-config (with-temp-buffer
+ (insert-file-contents file)
+ ;; We need to go to point-min even we
+ ;; are reading the whole buffer.
+ (goto-char (point-min))
+ (read (current-buffer))))
+ (source-buffer (if gdb-source-window
+ (window-buffer gdb-source-window)
+ (or (gdb-get-source-buffer)
+ ;; Do the same thing as in
+ ;; `gdb-setup-windows' if no source
+ ;; buffer is found.
+ (list-buffers-noselect))))
+ buffer-type)
+ (window-state-put window-config (frame-root-window))
+ (dolist (window (window-list nil 'no-minibuffer))
+ (with-selected-window window
+ (setq buffer-type (window-parameter nil 'gdb-buffer-type))
+ (pcase buffer-type
+ ('source (when source-buffer
+ (set-window-buffer nil source-buffer)
+ (setq gdb-source-window (selected-window))))
+ ('command (switch-to-buffer gud-comint-buffer))
+ (_ (let ((buffer (gdb-get-buffer-create buffer-type)))
+ (with-selected-window-undedicated
+ (set-window-buffer nil buffer))))))))
+ (kill-buffer placeholder))))
(define-minor-mode gdb-many-windows
"If nil just pop up the GUD buffer unless `gdb-show-main' is t.
@@ -4659,6 +4840,9 @@ gdb-many-windows
(defun gdb-restore-windows ()
"Restore the basic arrangement of windows used by gdb.
This arrangement depends on the value of option `gdb-many-windows'."
+ ;; This function is used when the user messed up window
+ ;; configuration and want to "reset to default". The function that
+ ;; sets up window configuration on start up is `gdb-get-source-file'.
(interactive)
(switch-to-buffer gud-comint-buffer) ;Select the right window and frame.
(delete-other-windows)
@@ -4705,11 +4889,24 @@ gdb-reset
(if (boundp 'speedbar-frame) (speedbar-timer-fn))
(setq gud-running nil)
(setq gdb-active-process nil)
- (remove-hook 'after-save-hook 'gdb-create-define-alist t))
+ (remove-hook 'after-save-hook 'gdb-create-define-alist t)
+ ;; Recover window configuration.
+ (when (or (eq gdb-restore-window-layout-after-quit t)
+ (and (eq gdb-restore-window-layout-after-quit 'if-show-main)
+ gdb-show-main)
+ (and (eq gdb-restore-window-layout-after-quit 'if-many-windows)
+ gdb-many-windows))
+ (when gdb--window-configuration-before
+ (window-state-put gdb--window-configuration-before)
+ ;; This way we don't accidentally restore an outdated window
+ ;; configuration. Maybe the user changed the configuration
+ ;; after starting GDB, who knows.
+ (setq gdb--window-configuration-before nil))))
(defun gdb-get-source-file ()
"Find the source file where the program starts and display it with related
buffers, if required."
+ ;; This function is called only once on startup.
(goto-char (point-min))
(if (re-search-forward gdb-source-file-regexp nil t)
(setq gdb-main-file (read (match-string 1))))
diff --git a/lisp/window.el b/lisp/window.el
index bd825c09e1..229400966a 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -278,6 +278,19 @@ with-displayed-buffer-window
(funcall ,vquit-function ,window ,value)
,value)))))
+(defmacro with-selected-window-undedicated (&rest body)
+ "Run BODY in the selected window temporarily undedicated."
+ (let ((window-dedicated-sym (gensym)))
+ `(let ((,window-dedicated-sym (window-dedicated-p)))
+ (when ,window-dedicated-sym
+ (set-window-dedicated-p nil nil))
+ ,@body
+ (when ,window-dedicated-sym
+ ;; `window-dedicated-p' returns the value set by
+ ;; `set-window-dedicated-p', which differentiates
+ ;; non-nil and t, so we cannot simply set to t.
+ (set-window-dedicated-p nil ,window-dedicated-sym)))))
+
;; The following two functions are like `window-next-sibling' and
;; `window-prev-sibling' but the WINDOW argument is _not_ optional (so
;; they don't substitute the selected window for nil), and they return
--
2.25.1
[-- Attachment #3: Type: text/plain, Size: 2 bytes --]
next prev parent reply other threads:[~2020-03-05 6:12 UTC|newest]
Thread overview: 48+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <5e950f28.1c69fb81.61726.5731.GMR@mx.google.com>
2020-01-18 20:57 ` bug#39181: 27.0.50; [PATCH] Allow users to store & restore gdb-mi layout Yuan Fu
2020-01-31 10:13 ` Eli Zaretskii
2020-02-02 14:22 ` Yuan Fu
2020-02-07 22:28 ` Yuan Fu
2020-02-10 4:56 ` Yuan Fu
2020-02-15 8:08 ` Eli Zaretskii
2020-02-15 9:55 ` martin rudalics
2020-02-15 10:19 ` Eli Zaretskii
2020-02-15 20:37 ` Yuan Fu
2020-02-16 10:00 ` martin rudalics
2020-03-03 23:41 ` Yuan Fu
2020-03-04 13:28 ` Fu Yuan
2020-03-05 6:12 ` Yuan Fu [this message]
2020-03-05 9:14 ` martin rudalics
2020-03-07 18:09 ` Yuan Fu
2020-03-07 19:07 ` Štěpán Němec
2020-03-07 19:17 ` Yuan Fu
2020-03-09 9:01 ` martin rudalics
2020-03-09 17:59 ` Yuan Fu
2020-03-09 19:18 ` Štěpán Němec
2020-03-09 20:17 ` Yuan Fu
2020-03-09 20:54 ` Štěpán Němec
2020-03-10 8:49 ` martin rudalics
2020-03-10 18:05 ` Fu Yuan
2020-03-11 8:52 ` martin rudalics
2020-03-11 9:26 ` Štěpán Němec
2020-03-12 8:22 ` martin rudalics
2020-03-12 8:49 ` Štěpán Němec
2020-03-12 19:21 ` Yuan Fu
2020-03-13 20:09 ` Yuan Fu
[not found] ` <87lfo4netg.fsf@gmail.com>
2020-03-13 21:13 ` Štěpán Němec
2020-03-13 21:40 ` Yuan Fu
2020-03-13 22:12 ` Štěpán Němec
2020-03-15 15:55 ` martin rudalics
2020-03-16 0:13 ` Yuan Fu
2020-03-16 9:24 ` martin rudalics
2020-03-20 20:03 ` Yuan Fu
2020-03-20 20:58 ` Štěpán Němec
2020-03-21 18:00 ` Yuan Fu
2020-03-21 18:39 ` Štěpán Němec
2020-03-21 21:03 ` Yuan Fu
2020-03-21 21:49 ` Štěpán Němec
2020-03-24 16:14 ` Yuan Fu
2020-03-27 9:01 ` martin rudalics
2020-03-27 16:28 ` Yuan Fu
2020-04-14 8:05 ` martin rudalics
2020-03-10 8:48 ` martin rudalics
2020-04-14 1:17 ` bug#39181: Fwd: Delivery Status Notification (Failure) Yuan Fu
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=90254CFF-2FFD-4D54-8467-717FF10166A2@gmail.com \
--to=casouri@gmail.com \
--cc=39181@debbugs.gnu.org \
--cc=rudalics@gmx.at \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.