Skip to content

Commit 9dccaf8

Browse files
casouriMartin Rudalics
authored andcommitted
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.
1 parent ff862f5 commit 9dccaf8

File tree

2 files changed

+272
-39
lines changed

2 files changed

+272
-39
lines changed

lisp/progmodes/gdb-mi.el

Lines changed: 254 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@
9292
(require 'json)
9393
(require 'bindat)
9494
(require 'cl-lib)
95+
(require 'cl-seq)
96+
(eval-when-compile (require 'pcase))
9597

9698
(declare-function speedbar-change-initial-expansion-list
9799
"speedbar" (new-default))
@@ -253,6 +255,27 @@ Possible values are these symbols:
253255
disposition of output generated by commands that
254256
gdb mode sends to gdb on its own behalf.")
255257

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+
264+
Possible 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+
256279
(defcustom gdb-discard-unordered-replies t
257280
"Non-nil means discard any out-of-order GDB replies.
258281
This protects against lost GDB replies, assuming that GDB always
@@ -603,6 +626,25 @@ Also display the main routine in the disassembly buffer if present."
603626
:group 'gdb
604627
:version "22.1")
605628

629+
(defcustom gdb-window-configuration-directory user-emacs-directory
630+
"Directory where GDB window configuration files are stored.
631+
If 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.
638+
This should be the full name of the window configuration file.
639+
If this is not an absolute path, GDB treats it as a relative path
640+
and looks under `gdb-window-configuration-directory'.
641+
642+
Note 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+
606648
(defvar gdbmi-debug-mode nil
607649
"When non-nil, print the messages sent/received from GDB/MI in *Messages*.")
608650

@@ -761,6 +803,12 @@ detailed description of this mode.
761803
(gdb-restore-windows)
762804
(error
763805
"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+
764812
;;
765813
(gud-common-init command-line nil 'gud-gdbmi-marker-filter)
766814

@@ -4494,6 +4542,26 @@ SPLIT-HORIZONTAL and show BUF in the new window."
44944542
(define-key gud-menu-map [displays]
44954543
`(menu-item "GDB-Windows" ,menu
44964544
: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."))
44974565
(define-key menu [gdb] '("Gdb" . gdb-display-gdb-buffer))
44984566
(define-key menu [threads] '("Threads" . gdb-display-threads-buffer))
44994567
(define-key menu [memory] '("Memory" . gdb-display-memory-buffer))
@@ -4532,9 +4600,6 @@ SPLIT-HORIZONTAL and show BUF in the new window."
45324600
'(menu-item "Display Other Windows" gdb-many-windows
45334601
:help "Toggle display of locals, stack and breakpoint information"
45344602
: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."))
45384603
(define-key menu [sep1]
45394604
'(menu-item "--"))
45404605
(define-key menu [all-threads]
@@ -4609,41 +4674,172 @@ window is dedicated."
46094674
(set-window-buffer window (get-buffer name))
46104675
(set-window-dedicated-p window t))
46114676

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.
4685+
The source file is the file that contains the source location
4686+
where GDB stops. There could be multiple source files during a
4687+
debugging session, we get the most recently showed one. If
4688+
program hasn't started running yet, the source file is the \"main
4689+
file\" 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+
46124695
(defun gdb-setup-windows ()
4613-
"Layout the window pattern for option `gdb-many-windows'."
4614-
(gdb-get-buffer-create 'gdb-locals-buffer)
4615-
(gdb-get-buffer-create 'gdb-stack-buffer)
4616-
(gdb-get-buffer-create 'gdb-breakpoints-buffer)
4617-
(set-window-dedicated-p (selected-window) nil)
4618-
(switch-to-buffer gud-comint-buffer)
4619-
(delete-other-windows)
4620-
(let ((win0 (selected-window))
4621-
(win1 (split-window nil ( / ( * (window-height) 3) 4)))
4622-
(win2 (split-window nil ( / (window-height) 3)))
4623-
(win3 (split-window-right)))
4624-
(gdb-set-window-buffer (gdb-locals-buffer-name) nil win3)
4625-
(select-window win2)
4626-
(set-window-buffer
4627-
win2
4628-
(if gud-last-last-frame
4629-
(gud-find-file (car gud-last-last-frame))
4630-
(if gdb-main-file
4631-
(gud-find-file gdb-main-file)
4632-
;; Put buffer list in window if we
4633-
;; can't find a source file.
4634-
(list-buffers-noselect))))
4635-
(setq gdb-source-window (selected-window))
4636-
(let ((win4 (split-window-right)))
4637-
(gdb-set-window-buffer
4638-
(gdb-get-buffer-create 'gdb-inferior-io) nil win4))
4639-
(select-window win1)
4640-
(gdb-set-window-buffer (gdb-stack-buffer-name))
4641-
(let ((win5 (split-window-right)))
4642-
(gdb-set-window-buffer (if gdb-show-threads-by-default
4643-
(gdb-threads-buffer-name)
4644-
(gdb-breakpoints-buffer-name))
4645-
nil win5))
4646-
(select-window win0)))
4696+
"Lay out the window pattern for option `gdb-many-windows'."
4697+
(if gdb-default-window-configuration-file
4698+
(gdb-load-window-configuration
4699+
(if (file-name-absolute-p gdb-default-window-configuration-file)
4700+
gdb-default-window-configuration-file
4701+
(expand-file-name gdb-default-window-configuration-file
4702+
gdb-window-configuration-directory)))
4703+
;; Create default layout as before.
4704+
(gdb-get-buffer-create 'gdb-locals-buffer)
4705+
(gdb-get-buffer-create 'gdb-stack-buffer)
4706+
(gdb-get-buffer-create 'gdb-breakpoints-buffer)
4707+
(set-window-dedicated-p (selected-window) nil)
4708+
(switch-to-buffer gud-comint-buffer)
4709+
(delete-other-windows)
4710+
(let ((win0 (selected-window))
4711+
(win1 (split-window nil ( / ( * (window-height) 3) 4)))
4712+
(win2 (split-window nil ( / (window-height) 3)))
4713+
(win3 (split-window-right)))
4714+
(gdb-set-window-buffer (gdb-locals-buffer-name) nil win3)
4715+
(select-window win2)
4716+
(set-window-buffer win2 (or (gdb-get-source-buffer)
4717+
(list-buffers-noselect)))
4718+
(setq gdb-source-window (selected-window))
4719+
(let ((win4 (split-window-right)))
4720+
(gdb-set-window-buffer
4721+
(gdb-get-buffer-create 'gdb-inferior-io) nil win4))
4722+
(select-window win1)
4723+
(gdb-set-window-buffer (gdb-stack-buffer-name))
4724+
(let ((win5 (split-window-right)))
4725+
(gdb-set-window-buffer (if gdb-show-threads-by-default
4726+
(gdb-threads-buffer-name)
4727+
(gdb-breakpoints-buffer-name))
4728+
nil win5))
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+
4739+
Function buffers are locals buffer, registers buffer, etc, but
4740+
not including main command buffer (the one where you type GDB
4741+
commands) 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.
4747+
Buffer type is like `gdb-registers-type', `gdb-stack-buffer'.
4748+
These symbols are used by `gdb-get-buffer-create'.
4749+
4750+
Return 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.
4761+
You 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.
4808+
FILE 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))))
46474843

46484844
(define-minor-mode gdb-many-windows
46494845
"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
46614857

46624858
(defun gdb-restore-windows ()
46634859
"Restore the basic arrangement of windows used by gdb.
4664-
This arrangement depends on the value of option `gdb-many-windows'."
4860+
This 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'.
46654866
(interactive)
46664867
(switch-to-buffer gud-comint-buffer) ;Select the right window and frame.
46674868
(delete-other-windows)
@@ -4708,11 +4909,25 @@ Kills the gdb buffers, and resets variables and the source buffers."
47084909
(if (boundp 'speedbar-frame) (speedbar-timer-fn))
47094910
(setq gud-running nil)
47104911
(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))))
47124926

47134927
(defun gdb-get-source-file ()
47144928
"Find the source file where the program starts and display it with related
47154929
buffers, if required."
4930+
;; This function is called only once on startup.
47164931
(goto-char (point-min))
47174932
(if (re-search-forward gdb-source-file-regexp nil t)
47184933
(setq gdb-main-file (read (match-string 1))))

lisp/window.el

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,24 @@ displays the buffer specified by BUFFER-OR-NAME before running BODY."
278278
(funcall ,vquit-function ,window ,value)
279279
,value)))))
280280

281+
(defmacro with-window-non-dedicated (window &rest body)
282+
"Evaluate BODY with WINDOW temporarily made non-dedicated.
283+
If WINDOW is nil, use the selected window. Return the value of
284+
the 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+
281299
;; The following two functions are like `window-next-sibling' and
282300
;; `window-prev-sibling' but the WINDOW argument is _not_ optional (so
283301
;; they don't substitute the selected window for nil), and they return

0 commit comments

Comments
 (0)