From bbb31bb246aa3dae4f211194a683ee86eae0acc5 Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Thu, 17 Oct 2019 17:35:48 -0400 Subject: [PATCH 1/3] Add with-selected-window-undedicated * lisp/window.el (with-selected-window-undedicated): new --- lisp/window.el | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lisp/window.el b/lisp/window.el index d93ec0add6..8e69351370 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.24.1 From 40be8173bfc7a5174136eadb9166c250aa348c8e Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Mon, 14 Oct 2019 21:11:43 -0400 Subject: [PATCH 2/3] Add window configuration save/restore feature for gdb-mi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now you can save a gdb window configuration to a file with ‘gdb-store-window-configuration’ and restore it from a file with ‘gdb-restore-window-configuration’. Set a default window configuration by setting gdb-default-window-configuration-file. Note that for the default window configuration to take effect, gdb-many-windows needs to be t. * lisp/progmodes/gdb-mi.el (require): add pcase, wrap inside ‘eval-when-compile’ (gdb-get-source-buffer): new, extracted out of gdb-restore-window (gdb-restore-window): extract out gdb-get-source-buffer (gdb-store-window-directory, gdb-buffer-p, gdb-function-buffer-p, gdb--buffer-type, gdb--inhibit-window-dedicated, gdb-store-window-configuration, gdb-restore-window-configuration): new (gdb-default-window-configuration-file): new (gdb-setup-windows): Add a condition branch that loads default window config when available (gdb-many-windows, gdb-get-source-file): add comments update --- lisp/progmodes/gdb-mi.el | 215 ++++++++++++++++++++++++++++++++------- 1 file changed, 181 insertions(+), 34 deletions(-) diff --git a/lisp/progmodes/gdb-mi.el b/lisp/progmodes/gdb-mi.el index 60852e4ad6..49aea1f236 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)) @@ -589,6 +590,20 @@ gdb-show-main :group 'gdb :version "22.1") +(defcustom gdb-store-window-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-configuration-file nil + "If non-nil, gdb loads this window configuration file on startup. +This should be an absolute file path." + :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*.") @@ -4573,41 +4588,169 @@ gdb-set-window-buffer (set-window-buffer window (get-buffer name)) (set-window-dedicated-p window t)) +(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-configuration-file + (gdb-restore-window-configuration + gdb-default-window-configuration-file) + ;; default layout + (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-store-window-configuration (file) + "Save current window configuration to FILE. + +You can later restore this configuration from that file by +`gdb-restore-window-configuration'." + (interactive (list (read-file-name + "Save window configuration to file: " + (or gdb-store-window-directory default-directory)))) + ;; we replace the buffer in each window with a placeholder, store + ;; the buffer type (register, breakpoint, etc) in window parameters, + ;; and store the window configuration + (save-window-excursion + (let ((placeholder (get-buffer-create " *gdb-placeholder*")) + (window-persistent-parameters + (cons '(gdb-buffer-type . writable) window-persistent-parameters)) + window-config) + (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 + (setq window-config (window-state-get nil t)) + (let ((buffer (find-file file))) + (unwind-protect + (with-current-buffer buffer + (if buffer-read-only + (user-error "Error: file read only") + (erase-buffer) + (prin1 window-config (current-buffer)) + (save-buffer))) + (kill-buffer buffer) + (kill-buffer placeholder)))))) + +(defun gdb-restore-window-configuration (file) + "Restore window configuration from FILE. + +FILE should be a window configuration file saved by +`gdb-store-window-configuration'." + (interactive (list (read-file-name + "Restore window configuration from file: " + (or gdb-store-window-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) + (goto-char (point-min)) + ;; we need to go to point-min even we + ;; are reading the whole buffer + (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' + (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. @@ -4626,6 +4769,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) @@ -4677,6 +4823,7 @@ gdb-reset (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)))) -- 2.24.1 From a10bea0436a7ca4bf6a7f6f07913a4471fefcf78 Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Thu, 16 Jan 2020 18:52:17 -0500 Subject: [PATCH 3/3] Restore window configuration when gdb quits Make gdb preserve the window configuration that the user had before starting gdb. * lisp/progmodes/gdb-mi.el (gdb--window-configuration-before): new (gdb): save configuration before start (gdb-reset): restore window configuration --- lisp/progmodes/gdb-mi.el | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lisp/progmodes/gdb-mi.el b/lisp/progmodes/gdb-mi.el index 49aea1f236..cb06cc6db0 100644 --- a/lisp/progmodes/gdb-mi.el +++ b/lisp/progmodes/gdb-mi.el @@ -243,6 +243,9 @@ 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-discard-unordered-replies t "Non-nil means discard any out-of-order GDB replies. This protects against lost GDB replies, assuming that GDB always @@ -762,6 +765,10 @@ 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 + (setq gdb--window-configuration-before (window-state-get)) ;; (gud-common-init command-line nil 'gud-gdbmi-marker-filter) @@ -4818,7 +4825,9 @@ 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 + (window-state-put gdb--window-configuration-before)) (defun gdb-get-source-file () "Find the source file where the program starts and display it with related -- 2.24.1