aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYuan Fu2020-03-15 16:47:43 +0100
committerMartin Rudalics2020-03-15 16:47:43 +0100
commit9dccaf8a5cdb10dae597345ec3741475477a7d97 (patch)
tree1c6c16db369257b4c0da7e3137dc383c8c202527
parentff862f55f4a51c96254a198c17910f90e45b8986 (diff)
downloademacs-9dccaf8a5cdb10dae597345ec3741475477a7d97.tar.gz
emacs-9dccaf8a5cdb10dae597345ec3741475477a7d97.zip
Add store/restore window configuration feature for gdb-mi
Add a feature that allows a user to save a gdb window configuration (window layout) to a file with 'gdb-save-window-configuration' and load it back with 'gdb-load-window-configuration'. Set a default window configuration by setting 'gdb-default-window-configuration-file'. Add an option to make gdb preserve the window configuration that the user had before starting gdb. In window.el, add 'with-window-non-dedicated'. * lisp/progmodes/gdb-mi.el (top/level): Require 'pcase' and 'cl-seq'. (gdb--window-configuration-before): New variable. (gdb-restore-window-configuration-after-quit): New option. (gdb-window-configuration-directory, gdb-default-window-configuration-file): New variables. (gdb): Save configuration on startup. (gud-menu-map): Add "Load Layout" and "Save Layout" to menu. Add "Restore Layout After Quit" button to menu. Rename "Restore Window Layout" to "Restore Default Layout", add some help echo, and move it from "GDB-MI" menu to "GDB-WINDOWs" menu. (gdb-toggle-restore-window-configuration): 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 configuration when available. Fix docstring. (gdb-buffer-p, gdb-function-buffer-p, gdb--buffer-type, gdb-save-window-configuration, gdb-load-window-configuration): New functions. (gdb-restore-windows): Edit docstring to mention 'gdb-default-window-configuration-file'. (gdb-reset): Restore window configuration after quit. * lisp/window.el (with-window-non-dedicated): New macro.
-rw-r--r--lisp/progmodes/gdb-mi.el293
-rw-r--r--lisp/window.el18
2 files changed, 272 insertions, 39 deletions
diff --git a/lisp/progmodes/gdb-mi.el b/lisp/progmodes/gdb-mi.el
index c2622327967..ea3b1b816a8 100644
--- a/lisp/progmodes/gdb-mi.el
+++ b/lisp/progmodes/gdb-mi.el
@@ -92,6 +92,8 @@
92(require 'json) 92(require 'json)
93(require 'bindat) 93(require 'bindat)
94(require 'cl-lib) 94(require 'cl-lib)
95(require 'cl-seq)
96(eval-when-compile (require 'pcase))
95 97
96(declare-function speedbar-change-initial-expansion-list 98(declare-function speedbar-change-initial-expansion-list
97 "speedbar" (new-default)) 99 "speedbar" (new-default))
@@ -253,6 +255,27 @@ Possible values are these symbols:
253 disposition of output generated by commands that 255 disposition of output generated by commands that
254 gdb mode sends to gdb on its own behalf.") 256 gdb mode sends to gdb on its own behalf.")
255 257
258(defvar gdb--window-configuration-before nil
259 "Stores the window configuration before starting GDB.")
260
261(defcustom gdb-restore-window-configuration-after-quit nil
262 "If non-nil, restore window configuration as of before GDB started.
263
264Possible values are:
265 t -- Always restore.
266 nil -- Don't restore.
267 `if-gdb-show-main' -- Restore only if variable `gdb-show-main'
268 is non-nil
269 `if-gdb-many-windows' -- Restore only if variable `gdb-many-windows'
270 is non-nil."
271 :type '(choice
272 (const :tag "Always restore" t)
273 (const :tag "Don't restore" nil)
274 (const :tag "Depends on `gdb-show-main'" 'if-gdb-show-main)
275 (const :tag "Depends on `gdb-many-windows'" 'if-gdb-many-windows))
276 :group 'gdb
277 :version "28.1")
278
256(defcustom gdb-discard-unordered-replies t 279(defcustom gdb-discard-unordered-replies t
257 "Non-nil means discard any out-of-order GDB replies. 280 "Non-nil means discard any out-of-order GDB replies.
258This protects against lost GDB replies, assuming that GDB always 281This protects against lost GDB replies, assuming that GDB always
@@ -603,6 +626,25 @@ Also display the main routine in the disassembly buffer if present."
603 :group 'gdb 626 :group 'gdb
604 :version "22.1") 627 :version "22.1")
605 628
629(defcustom gdb-window-configuration-directory user-emacs-directory
630 "Directory where GDB window configuration files are stored.
631If nil, use `default-directory'."
632 :type 'string
633 :group 'gdb
634 :version "28.1")
635
636(defcustom gdb-default-window-configuration-file nil
637 "If non-nil, load this window configuration (layout) on startup.
638This should be the full name of the window configuration file.
639If this is not an absolute path, GDB treats it as a relative path
640and looks under `gdb-window-configuration-directory'.
641
642Note that this variable only takes effect when variable
643`gdb-many-windows' is t."
644 :type 'string
645 :group 'gdb
646 :version "28.1")
647
606(defvar gdbmi-debug-mode nil 648(defvar gdbmi-debug-mode nil
607 "When non-nil, print the messages sent/received from GDB/MI in *Messages*.") 649 "When non-nil, print the messages sent/received from GDB/MI in *Messages*.")
608 650
@@ -761,6 +803,12 @@ detailed description of this mode.
761 (gdb-restore-windows) 803 (gdb-restore-windows)
762 (error 804 (error
763 "Multiple debugging requires restarting in text command mode")) 805 "Multiple debugging requires restarting in text command mode"))
806
807 ;; Save window configuration before starting gdb so we can restore
808 ;; it after gdb quits. Save it regardless of the value of
809 ;; `gdb-restore-window-configuration-after-quit'.
810 (setq gdb--window-configuration-before (window-state-get))
811
764 ;; 812 ;;
765 (gud-common-init command-line nil 'gud-gdbmi-marker-filter) 813 (gud-common-init command-line nil 'gud-gdbmi-marker-filter)
766 814
@@ -4494,6 +4542,26 @@ SPLIT-HORIZONTAL and show BUF in the new window."
4494 (define-key gud-menu-map [displays] 4542 (define-key gud-menu-map [displays]
4495 `(menu-item "GDB-Windows" ,menu 4543 `(menu-item "GDB-Windows" ,menu
4496 :visible (eq gud-minor-mode 'gdbmi))) 4544 :visible (eq gud-minor-mode 'gdbmi)))
4545 (define-key menu [gdb-restore-windows]
4546 '(menu-item "Restore Initial Layout" gdb-restore-windows
4547 :help "Restore the initial GDB window layout."))
4548 ;; Window layout vs window configuration: We use "window layout" in
4549 ;; GDB UI. Internally we refer to "window configuration" because
4550 ;; that's the data structure used to store window layouts. Though
4551 ;; bare in mind that there is a small difference between what we
4552 ;; store and what normal window configuration functions
4553 ;; output. Because GDB buffers (source, local, breakpoint, etc) are
4554 ;; different between each debugging sessions, simply save/load
4555 ;; window configurations doesn't
4556 ;; work. `gdb-save-window-configuration' and
4557 ;; `gdb-load-window-configuration' do some tricks to store and
4558 ;; recreate each buffer in the layout.
4559 (define-key menu [load-layout] '("Load Layout" "Load GDB window configuration (layout) from a file" . gdb-load-window-configuration))
4560 (define-key menu [save-layout] '("Save Layout" "Save current GDB window configuration (layout) to a file" . gdb-save-window-configuration))
4561 (define-key menu [restore-layout-after-quit]
4562 '(menu-item "Restore Layout After Quit" gdb-toggle-restore-window-configuration
4563 :button (:toggle . gdb-restore-window-configuration-after-quit)
4564 :help "Toggle between always restore the window configuration (layout) after GDB quits and never restore.\n You can also change this setting in Customize to conditionally restore."))
4497 (define-key menu [gdb] '("Gdb" . gdb-display-gdb-buffer)) 4565 (define-key menu [gdb] '("Gdb" . gdb-display-gdb-buffer))
4498 (define-key menu [threads] '("Threads" . gdb-display-threads-buffer)) 4566 (define-key menu [threads] '("Threads" . gdb-display-threads-buffer))
4499 (define-key menu [memory] '("Memory" . gdb-display-memory-buffer)) 4567 (define-key menu [memory] '("Memory" . gdb-display-memory-buffer))
@@ -4532,9 +4600,6 @@ SPLIT-HORIZONTAL and show BUF in the new window."
4532 '(menu-item "Display Other Windows" gdb-many-windows 4600 '(menu-item "Display Other Windows" gdb-many-windows
4533 :help "Toggle display of locals, stack and breakpoint information" 4601 :help "Toggle display of locals, stack and breakpoint information"
4534 :button (:toggle . gdb-many-windows))) 4602 :button (:toggle . gdb-many-windows)))
4535 (define-key menu [gdb-restore-windows]
4536 '(menu-item "Restore Window Layout" gdb-restore-windows
4537 :help "Restore standard layout for debug session."))
4538 (define-key menu [sep1] 4603 (define-key menu [sep1]
4539 '(menu-item "--")) 4604 '(menu-item "--"))
4540 (define-key menu [all-threads] 4605 (define-key menu [all-threads]
@@ -4609,41 +4674,172 @@ window is dedicated."
4609 (set-window-buffer window (get-buffer name)) 4674 (set-window-buffer window (get-buffer name))
4610 (set-window-dedicated-p window t)) 4675 (set-window-dedicated-p window t))
4611 4676
4677(defun gdb-toggle-restore-window-configuration ()
4678 "Toggle whether to restore window configuration when GDB quits."
4679 (interactive)
4680 (setq gdb-restore-window-configuration-after-quit
4681 (not gdb-restore-window-configuration-after-quit)))
4682
4683(defun gdb-get-source-buffer ()
4684 "Return a buffer displaying source file or nil if we can't find one.
4685The source file is the file that contains the source location
4686where GDB stops. There could be multiple source files during a
4687debugging session, we get the most recently showed one. If
4688program hasn't started running yet, the source file is the \"main
4689file\" where the GDB session starts (see `gdb-main-file')."
4690 (if gud-last-last-frame
4691 (gud-find-file (car gud-last-last-frame))
4692 (when gdb-main-file
4693 (gud-find-file gdb-main-file))))
4694
4612(defun gdb-setup-windows () 4695(defun gdb-setup-windows ()
4613 "Layout the window pattern for option `gdb-many-windows'." 4696 "Lay out the window pattern for option `gdb-many-windows'."
4614 (gdb-get-buffer-create 'gdb-locals-buffer) 4697 (if gdb-default-window-configuration-file
4615 (gdb-get-buffer-create 'gdb-stack-buffer) 4698 (gdb-load-window-configuration
4616 (gdb-get-buffer-create 'gdb-breakpoints-buffer) 4699 (if (file-name-absolute-p gdb-default-window-configuration-file)
4617 (set-window-dedicated-p (selected-window) nil) 4700 gdb-default-window-configuration-file
4618 (switch-to-buffer gud-comint-buffer) 4701 (expand-file-name gdb-default-window-configuration-file
4619 (delete-other-windows) 4702 gdb-window-configuration-directory)))
4620 (let ((win0 (selected-window)) 4703 ;; Create default layout as before.
4621 (win1 (split-window nil ( / ( * (window-height) 3) 4))) 4704 (gdb-get-buffer-create 'gdb-locals-buffer)
4622 (win2 (split-window nil ( / (window-height) 3))) 4705 (gdb-get-buffer-create 'gdb-stack-buffer)
4623 (win3 (split-window-right))) 4706 (gdb-get-buffer-create 'gdb-breakpoints-buffer)
4624 (gdb-set-window-buffer (gdb-locals-buffer-name) nil win3) 4707 (set-window-dedicated-p (selected-window) nil)
4625 (select-window win2) 4708 (switch-to-buffer gud-comint-buffer)
4626 (set-window-buffer 4709 (delete-other-windows)
4627 win2 4710 (let ((win0 (selected-window))
4628 (if gud-last-last-frame 4711 (win1 (split-window nil ( / ( * (window-height) 3) 4)))
4629 (gud-find-file (car gud-last-last-frame)) 4712 (win2 (split-window nil ( / (window-height) 3)))
4630 (if gdb-main-file 4713 (win3 (split-window-right)))
4631 (gud-find-file gdb-main-file) 4714 (gdb-set-window-buffer (gdb-locals-buffer-name) nil win3)
4632 ;; Put buffer list in window if we 4715 (select-window win2)
4633 ;; can't find a source file. 4716 (set-window-buffer win2 (or (gdb-get-source-buffer)
4634 (list-buffers-noselect)))) 4717 (list-buffers-noselect)))
4635 (setq gdb-source-window (selected-window)) 4718 (setq gdb-source-window (selected-window))
4636 (let ((win4 (split-window-right))) 4719 (let ((win4 (split-window-right)))
4637 (gdb-set-window-buffer 4720 (gdb-set-window-buffer
4638 (gdb-get-buffer-create 'gdb-inferior-io) nil win4)) 4721 (gdb-get-buffer-create 'gdb-inferior-io) nil win4))
4639 (select-window win1) 4722 (select-window win1)
4640 (gdb-set-window-buffer (gdb-stack-buffer-name)) 4723 (gdb-set-window-buffer (gdb-stack-buffer-name))
4641 (let ((win5 (split-window-right))) 4724 (let ((win5 (split-window-right)))
4642 (gdb-set-window-buffer (if gdb-show-threads-by-default 4725 (gdb-set-window-buffer (if gdb-show-threads-by-default
4643 (gdb-threads-buffer-name) 4726 (gdb-threads-buffer-name)
4644 (gdb-breakpoints-buffer-name)) 4727 (gdb-breakpoints-buffer-name))
4645 nil win5)) 4728 nil win5))
4646 (select-window win0))) 4729 (select-window win0))))
4730
4731(defun gdb-buffer-p (buffer)
4732 "Return t if BUFFER is GDB-related."
4733 (with-current-buffer buffer
4734 (eq gud-minor-mode 'gdbmi)))
4735
4736(defun gdb-function-buffer-p (buffer)
4737 "Return t if BUFFER is a GDB function buffer.
4738
4739Function buffers are locals buffer, registers buffer, etc, but
4740not including main command buffer (the one where you type GDB
4741commands) or source buffers (that display program source code)."
4742 (with-current-buffer buffer
4743 (derived-mode-p 'gdb-parent-mode 'gdb-inferior-io-mode)))
4744
4745(defun gdb--buffer-type (buffer)
4746 "Return the type of BUFFER if it is a function buffer.
4747Buffer type is like `gdb-registers-type', `gdb-stack-buffer'.
4748These symbols are used by `gdb-get-buffer-create'.
4749
4750Return nil if BUFFER is not a GDB function buffer."
4751 (with-current-buffer buffer
4752 (cl-loop for rule in gdb-buffer-rules
4753 for mode-name = (gdb-rules-buffer-mode rule)
4754 for type = (car rule)
4755 if (eq mode-name major-mode)
4756 return type
4757 finally return nil)))
4758
4759(defun gdb-save-window-configuration (file)
4760 "Save current window configuration (layout) to FILE.
4761You can later restore this configuration from that file by
4762`gdb-load-window-configuration'."
4763 (interactive (list (read-file-name
4764 "Save window configuration to file: "
4765 (or gdb-window-configuration-directory
4766 default-directory))))
4767 ;; We replace the buffer in each window with a placeholder, store
4768 ;; the buffer type (register, breakpoint, etc) in window parameters,
4769 ;; and write the window configuration to the file.
4770 (save-window-excursion
4771 (let ((placeholder (get-buffer-create " *gdb-placeholder*"))
4772 (window-persistent-parameters
4773 (cons '(gdb-buffer-type . writable) window-persistent-parameters)))
4774 (unwind-protect
4775 (dolist (win (window-list nil 'no-minibuffer))
4776 (select-window win)
4777 (when (gdb-buffer-p (current-buffer))
4778 (set-window-parameter
4779 nil 'gdb-buffer-type
4780 (cond ((gdb-function-buffer-p (current-buffer))
4781 ;; 1) If a user arranged the window
4782 ;; configuration herself and saves it, windows
4783 ;; are probably not dedicated. 2) We use the
4784 ;; same dedication flag as in
4785 ;; `gdb-display-buffer'.
4786 (set-window-dedicated-p nil t)
4787 ;; We save this gdb-buffer-type symbol so
4788 ;; we can later pass it to `gdb-get-buffer-create';
4789 ;; one example: `gdb-registers-buffer'.
4790 (or (gdb--buffer-type (current-buffer))
4791 (error "Unrecognized gdb buffer mode: %s" major-mode)))
4792 ;; Command buffer.
4793 ((derived-mode-p 'gud-mode) 'command)
4794 ((equal (selected-window) gdb-source-window) 'source)))
4795 (with-window-non-dedicated nil
4796 (set-window-buffer nil placeholder)
4797 (set-window-prev-buffers (selected-window) nil)
4798 (set-window-next-buffers (selected-window) nil))))
4799 ;; Save the window configuration to FILE.
4800 (let ((window-config (window-state-get nil t)))
4801 (with-temp-buffer
4802 (prin1 window-config (current-buffer))
4803 (write-file file t)))
4804 (kill-buffer placeholder)))))
4805
4806(defun gdb-load-window-configuration (file)
4807 "Restore window configuration (layout) from FILE.
4808FILE should be a window configuration file saved by
4809`gdb-save-window-configuration'."
4810 (interactive (list (read-file-name
4811 "Restore window configuration from file: "
4812 (or gdb-window-configuration-directory
4813 default-directory))))
4814 ;; Basically, we restore window configuration and go through each
4815 ;; window and restore the function buffers.
4816 (let* ((placeholder (get-buffer-create " *gdb-placeholder*")))
4817 (unwind-protect ; Don't leak buffer.
4818 (let ((window-config (with-temp-buffer
4819 (insert-file-contents file)
4820 ;; We need to go to point-min because
4821 ;; `read' reads from point
4822 (goto-char (point-min))
4823 (read (current-buffer))))
4824 (source-buffer (or (gdb-get-source-buffer)
4825 ;; Do the same thing as in
4826 ;; `gdb-setup-windows' if no source
4827 ;; buffer is found.
4828 (list-buffers-noselect)))
4829 buffer-type)
4830 (window-state-put window-config (frame-root-window))
4831 (dolist (window (window-list nil 'no-minibuffer))
4832 (with-selected-window window
4833 (setq buffer-type (window-parameter nil 'gdb-buffer-type))
4834 (pcase buffer-type
4835 ('source (when source-buffer
4836 (set-window-buffer nil source-buffer)
4837 (setq gdb-source-window (selected-window))))
4838 ('command (switch-to-buffer gud-comint-buffer))
4839 (_ (let ((buffer (gdb-get-buffer-create buffer-type)))
4840 (with-window-non-dedicated nil
4841 (set-window-buffer nil buffer))))))))
4842 (kill-buffer placeholder))))
4647 4843
4648(define-minor-mode gdb-many-windows 4844(define-minor-mode gdb-many-windows
4649 "If nil just pop up the GUD buffer unless `gdb-show-main' is t. 4845 "If nil just pop up the GUD buffer unless `gdb-show-main' is t.
@@ -4661,7 +4857,12 @@ of the debugged program. Non-nil means display the layout shown for
4661 4857
4662(defun gdb-restore-windows () 4858(defun gdb-restore-windows ()
4663 "Restore the basic arrangement of windows used by gdb. 4859 "Restore the basic arrangement of windows used by gdb.
4664This arrangement depends on the value of option `gdb-many-windows'." 4860This arrangement depends on the values of variable
4861`gdb-many-windows' and `gdb-default-window-configuration-file'."
4862 ;; This function is used when the user messed up window
4863 ;; configuration and wants to "reset to default". The function that
4864 ;; sets up window configuration on start up is
4865 ;; `gdb-get-source-file'.
4665 (interactive) 4866 (interactive)
4666 (switch-to-buffer gud-comint-buffer) ;Select the right window and frame. 4867 (switch-to-buffer gud-comint-buffer) ;Select the right window and frame.
4667 (delete-other-windows) 4868 (delete-other-windows)
@@ -4708,11 +4909,25 @@ Kills the gdb buffers, and resets variables and the source buffers."
4708 (if (boundp 'speedbar-frame) (speedbar-timer-fn)) 4909 (if (boundp 'speedbar-frame) (speedbar-timer-fn))
4709 (setq gud-running nil) 4910 (setq gud-running nil)
4710 (setq gdb-active-process nil) 4911 (setq gdb-active-process nil)
4711 (remove-hook 'after-save-hook 'gdb-create-define-alist t)) 4912 (remove-hook 'after-save-hook 'gdb-create-define-alist t)
4913 ;; Recover window configuration.
4914 (when (or (eq gdb-restore-window-configuration-after-quit t)
4915 (and (eq gdb-restore-window-configuration-after-quit
4916 'if-gdb-show-main)
4917 gdb-show-main)
4918 (and (eq gdb-restore-window-configuration-after-quit
4919 'if-gdb-many-windows)
4920 gdb-many-windows))
4921 (when gdb--window-configuration-before
4922 (window-state-put gdb--window-configuration-before)
4923 ;; This way we don't accidentally restore an outdated window
4924 ;; configuration.
4925 (setq gdb--window-configuration-before nil))))
4712 4926
4713(defun gdb-get-source-file () 4927(defun gdb-get-source-file ()
4714 "Find the source file where the program starts and display it with related 4928 "Find the source file where the program starts and display it with related
4715buffers, if required." 4929buffers, if required."
4930 ;; This function is called only once on startup.
4716 (goto-char (point-min)) 4931 (goto-char (point-min))
4717 (if (re-search-forward gdb-source-file-regexp nil t) 4932 (if (re-search-forward gdb-source-file-regexp nil t)
4718 (setq gdb-main-file (read (match-string 1)))) 4933 (setq gdb-main-file (read (match-string 1))))
diff --git a/lisp/window.el b/lisp/window.el
index fc1e7d4a76c..b54f1633f5e 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -278,6 +278,24 @@ displays the buffer specified by BUFFER-OR-NAME before running BODY."
278 (funcall ,vquit-function ,window ,value) 278 (funcall ,vquit-function ,window ,value)
279 ,value))))) 279 ,value)))))
280 280
281(defmacro with-window-non-dedicated (window &rest body)
282 "Evaluate BODY with WINDOW temporarily made non-dedicated.
283If WINDOW is nil, use the selected window. Return the value of
284the last form in BODY."
285 (declare (indent 1) (debug t))
286 (let ((window-dedicated-sym (gensym))
287 (window-sym (gensym)))
288 `(let* ((,window-sym (window-normalize-window ,window t))
289 (,window-dedicated-sym (window-dedicated-p ,window-sym)))
290 (set-window-dedicated-p ,window-sym nil)
291 (unwind-protect
292 (progn ,@body)
293 ;; `window-dedicated-p' returns the value set by
294 ;; `set-window-dedicated-p', which differentiates non-nil and
295 ;; t, so we cannot simply use t here. That's why we use
296 ;; `window-dedicated-sym'.
297 (set-window-dedicated-p ,window-sym ,window-dedicated-sym)))))
298
281;; The following two functions are like `window-next-sibling' and 299;; The following two functions are like `window-next-sibling' and
282;; `window-prev-sibling' but the WINDOW argument is _not_ optional (so 300;; `window-prev-sibling' but the WINDOW argument is _not_ optional (so
283;; they don't substitute the selected window for nil), and they return 301;; they don't substitute the selected window for nil), and they return