From: Vincenzo Pupillo Date: Thu, 6 Mar 2025 12:39:01 +0000 (+0100) Subject: Add a new command `speedbar-window'. X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=d427d8effbaac6c1e42b64ab11704d022390410f;p=emacs.git Add a new command `speedbar-window'. Speedbar now can be opened in a window instead of a separate frame. The frame remains the default. * doc/emacs/frames.texi: Mention Speedbar window mode. * doc/misc/speedbar.texi: Document 'speedbar-window'. * lisp/speedbar.el (speedbar-prefer-window): New user option. If t, the command `speedbar' open the speedbar in a window. (speedbar-window-dedicated-window): New user option. If t the window is dedicated. (speedbar-window-side): New user option. The side of 'speedbar-window', defaults to left. (speedbar-window-default-width): New user option. The default size of the 'speedbar-window'. (speedbar-window-max-width): New user option. Limits the width of the 'speedbar-window'. The user can resize the window as desired, but this option will be the width of the window when restored. (speedbar--buffer-name): New variable, the buffer name used for both 'speedbar-frame-mode' and 'speedbar-window-mode'. (speedbar--window): New variable, the window displaying 'speedbar-window'. (speedbar--window-width): New variable, store the current width of 'speedbar-window'. (speedbar-easymenu-definition-trailer): Now it is a function that returns a different trailer for 'speedbar-frame' and 'speedbar-window'. (speedbar): Now it is a function that calls 'speedbar-frame-mode', the default or 'speedbar-window-mode' based on the value of 'speedbar-prefer-window'. (speedbar-frame-mode): Before opening a frame, close 'speedbar-window' if it is open. (speedbar-frame-or-window): New function, returns 'frame', 'window' or nil if speedbar is not open. (speedbar-window): New alias for 'speedbar-window-mode'. (speedbar-window-mode): Enable of disable 'speedbar-window'. (speedbar-window--window-live-p): New function, return non-nil if the 'speedbar-window' is live. (speedbar-window--buffer-live-p): New function, return non-nil if the 'speedbar-buffer' is live. (speedbar-window--live-p): New function, return t if 'speedbar-window' is open. (speedbar-window-current-window): New function, return t if the selected window is speedbar-window. (speedbar-window--close): New function, close the 'speedbar-window'. (speedbar-window--width): New function, return the current width of 'speedbar-window'. (speedbar-width): New function, return the 'speedbar' of 'speedbar-frame-mode' of 'speedbar-frame-mode'. (speedbar-set-mode-line-format): Use the new 'speedbar-width' function. (speedbar-directory-buttons): Use the new 'speedbar-width' function. (speedbar--speedbar-live-p): New function, returns t if 'speedbar-frame-mode' or 'speedbar-window-mode' are open. (speedbar-timer-fn): Now handle 'speedbar-frame-mode' and 'speedbar-window-mode'. (cherry picked from commit c1aab8c49b763abc482a6dbc402782bbe5370da0) --- diff --git a/doc/emacs/frames.texi b/doc/emacs/frames.texi index 9992c39dcc9..1fa6e0b5b33 100644 --- a/doc/emacs/frames.texi +++ b/doc/emacs/frames.texi @@ -964,6 +964,9 @@ select. For example, in Rmail mode, the speedbar shows a list of Rmail files, and lets you move the current message to another Rmail file by clicking on its @samp{} box. +The speedbar can optionally be displayed as a window rather than a +separate frame, in both terminal and graphics mode. + For more details on using and programming the speedbar, @xref{Top, Speedbar,,speedbar, Speedbar Manual}. diff --git a/doc/misc/speedbar.texi b/doc/misc/speedbar.texi index 69c54f5b552..716135dfc4a 100644 --- a/doc/misc/speedbar.texi +++ b/doc/misc/speedbar.texi @@ -46,11 +46,11 @@ information related to the current buffer. Its original inspiration is the ``explorer'' often used in modern development environments, office packages, and web browsers. -Speedbar displays a narrow frame in which a tree view is shown. This -tree view defaults to containing a list of files and directories. Files -can be ``expanded'' to list tags inside. Directories can be expanded to -list the files within them. Each file or tag can be jumped to -immediately. +Speedbar displays a narrow frame or a narrow window in which a tree view +is shown. This tree view defaults to containing a list of files and +directories. Files can be ``expanded'' to list tags inside. +Directories can be expanded to list the files within them. Each file or +tag can be jumped to immediately. Speedbar expands upon ``explorer'' windows by maintaining context with the user. For example, when using the file view, the current buffer's file @@ -114,6 +114,21 @@ The function to use when switching between frames using the keyboard is @code{speedbar-get-focus}. This function will toggle between frames, and it's useful to bind it to a key in terminal mode. @xref{Customizing}. +@cindex @code{speedbar-window} +Optionally, the speedbar can be displayed as a window, splitting the +windows of the selected frame, in both terminal and graphics modes. +Only one speedbar window can be open at a time. + +It is possible to switch from displaying the speedbar in a separate +frame to displaying it in a window and vice versa simply by using the +@kbd{M-x speedbar-window @key{RET}} or +@kbd{M-x speedbar-frame@key{RET}} command. + +@cindex @code{speedbar-prefer-window} +The user option @code{speedbar-prefer-window} changes the behavior of +the @code{speedbar} command, if set to t the @code{speedbar} command +will display the speedbar in a window instead of in a frame. + @node Basic Navigation @chapter Basic Navigation @@ -138,10 +153,11 @@ These key bindings are common across all modes: @table @kbd @item Q @cindex quitting speedbar -Quit speedbar, and kill the frame. +Quit speedbar, and kill the frame. This is only available for +@code{speed-frame}. @item q -Quit speedbar, and hide the frame. This makes it faster to restore the -speedbar frame, than if you press @kbd{Q}. +Quit speedbar, and hide the frame or close the window. This makes it +faster to restore the speedbar frame, than if you press @kbd{Q}. @item g @cindex refresh speedbar display Refresh whatever contents are in speedbar. @@ -658,10 +674,10 @@ Customize speedbar's many colors and fonts. @end table @menu -* Frames and Faces:: Visible behaviors. -* Tag Hierarchy Methods:: Customizing how tags are displayed. -* Version Control:: Adding new VC detection modes. -* Hooks:: The many hooks you can use. +* Frames, Windows and Faces:: Visible behaviors. +* Tag Hierarchy Methods:: Customizing how tags are displayed. +* Version Control:: Adding new VC detection modes. +* Hooks:: The many hooks you can use. @end menu @node Frames and Faces @@ -701,6 +717,25 @@ the alist @code{speedbar-frame-parameters}. This variable is used to set up initial details. Height is also automatically added when speedbar is created, though you can override it. +@cindex @code{speedbar-window-side} +If the speedbar is displayed in a window, you can also customize the +side on which the @code{speedbar-window} is displayed by changing the +@code{speedbar-window-side} option. + +@cindex @code{speedbar-window-default-width} +@cindex @code{speedbar-window-max-width} +The size of the @code{speedbar-window}, when opened on the left or right +side, can be defined by changing the +@code{speedbar-window-default-width} option, which defines the default +window size. The width of the speedbar window can be greater than +@code{speedbar-window-max-width}, but if it is closed and later +reopened, the width will be equal to @code{speedbar-window-max-width}. + +@cindex @code{speedbar-window-dedicated-window} +By default, @code{speedbar-window} is displayed in a dedicated window, +so @code{display-buffer} will never use this window. Setting +@code{speedbar-window-dedicated-window} to nil can change this behavior. + @node Tag Hierarchy Methods @section Tag Hierarchy Methods @cindex tag hierarchy diff --git a/lisp/speedbar.el b/lisp/speedbar.el index c9ce8e49ab9..5a4766c712e 100644 --- a/lisp/speedbar.el +++ b/lisp/speedbar.el @@ -22,9 +22,9 @@ ;;; Commentary: ;; -;; The speedbar provides a frame in which files, and locations in -;; files are displayed. These items can be clicked on with mouse-2 in -;; to display that file location. +;; The speedbar provides a frame or a window in which files, and +;; locations in files are displayed. These items can be clicked on with +;; mouse-2 in to display that file location. ;; ;;; Customizing and Developing for speedbar ;; @@ -139,8 +139,66 @@ :version "21.1" :type 'boolean) +(defcustom speedbar-select-frame-method 'attached + "Specify how to select a frame for displaying a file. +A number such as 1 or -1 means to pass that number to `other-frame' +while selecting a frame from speedbar. Any other value means to use +the attached frame (the frame that speedbar was started from)." + :group 'speedbar + :type '(choice integer (other :tag "attached" attached))) + +(defcustom speedbar-prefer-window nil + "If t, the command `speedbar' opens the speedbar in a window." + :type 'boolean + :group 'speedbar + :version "31.1") + +(defcustom speedbar-window-dedicated-window t + "Whether to make the `speedbar-window' dedicated." + :group 'speedbar + :type 'boolean + :version "31.1") + +(defcustom speedbar-window-side 'left + "Control the side of the frame on which to show the speedbar window. +The value can be `left', `right', `top' or `bottom'. +See `display-buffer-in-side-window' for more details." + :type '(radio (const :tag "Left" left) + (const :tag "Right" right) + (const :tag "Top" top) + (const :tag "Bottom" bottom)) + :group 'speedbar + :version "31.1") + +(defcustom speedbar-window-default-width 20 + "Initial width in characters of `speedbar-window'. +Specified the desired width when `speedbar-window' is opened on the left or +right side. The default value is the same width of `speedbar-frame-mode'." + :type 'integer + :group 'speedbar + :version "31.1") + +(defcustom speedbar-window-max-width 40 + "The maximum allowed width in characters of the `speedbar-window'. +The `speedbar-window' width can exceed the value defined here, however +if the window is closed and then reopened, this will be the width of the +`speedbar-window'." + :type 'integer + :group 'speedbar + :version "31.1") + ;;; Code: +(defconst speedbar--buffer-name " SPEEDBAR" + "Speedbar buffer name.") + +(defvar speedbar--window nil + "The window displaying `speedbar-window'.") + +(defvar speedbar--window-width speedbar-window-default-width +"Stores the current width of `speedbar-window'. +Subsequent calls to `speedbar-window' will open a window of this width.") + (defvar speedbar-initial-expansion-mode-alist '(("buffers" speedbar-buffer-easymenu-definition speedbar-buffers-key-map speedbar-buffer-buttons) @@ -845,11 +903,18 @@ This basically creates a sparse keymap, and makes its parent be ) "Additional menu items while in file-mode.") -(defvar speedbar-easymenu-definition-trailer - '(["Customize..." speedbar-customize t] - ["Close" dframe-close-frame t] - ["Quit" delete-frame t]) - "Menu items appearing at the end of the speedbar menu.") +(defun speedbar-easymenu-definition-trailer () + "Return menu items appearing at the end of the speedbar menu." + (let ((type (speedbar-frame-or-window))) + (cond ((eq type 'frame) + '(["Customize..." speedbar-customize t] + ["Close" dframe-close-frame t] + ["Quit" delete-frame t])) + ((eq type 'window) + '(["Customize..." speedbar-customize t] + ["Close" + (lambda () (interactive) (speedbar-window--close)) + :keys "q" :active t]))))) (defvar speedbar-desired-buffer nil "Non-nil when speedbar is showing buttons specific to a special mode. @@ -892,7 +957,19 @@ directories.") ;; ;;;###autoload -(defalias 'speedbar 'speedbar-frame-mode) +(defun speedbar (&optional arg) + "Open or close the `speedbar'. +Positive ARG means turn on, negative turn off. +A nil ARG means toggle. If `speedbar-prefer-window' is t, open the +speedbar in a window instead of in a separate frame." + (interactive "P") + (if speedbar-prefer-window + (speedbar-window-mode arg) + (speedbar-frame-mode arg))) + +;;;###autoload +(defalias 'speedbar-frame 'speedbar-frame-mode) + ;;;###autoload (defun speedbar-frame-mode (&optional arg) "Enable or disable speedbar. @@ -902,10 +979,12 @@ be displayed. Currently, only one speedbar is supported at a time. `speedbar-before-popup-hook' is called before popping up the speedbar frame. `speedbar-before-delete-hook' is called before the frame is deleted." (interactive "P") + (when (eq (speedbar-frame-or-window) 'window) + (speedbar-window--close)) ;; Get the buffer to play with (if (not (buffer-live-p speedbar-buffer)) (with-current-buffer - (setq speedbar-buffer (get-buffer-create " SPEEDBAR")) + (setq speedbar-buffer (get-buffer-create speedbar--buffer-name)) (speedbar-mode))) ;; Do the frame thing (dframe-frame-mode arg @@ -935,6 +1014,119 @@ be displayed. Currently, only one speedbar is supported at a time. (message (substitute-command-keys "Use \\[speedbar-get-focus] to see the speedbar window")))) +(defsubst speedbar-current-frame () + "Return the frame to use for speedbar based on current context." + (dframe-current-frame 'speedbar-frame 'speedbar-mode)) + +(defsubst speedbar-window--window-live-p () + "Return non-nil if `speedbar--window' is defined and live." + (when (and speedbar--window (window-live-p speedbar--window)) + speedbar--window)) + +(defsubst speedbar-window--buffer-live-p () + "Return non-nil if `speedbar-buffer' is live." + (when (and speedbar-buffer (buffer-live-p speedbar-buffer)) + speedbar-buffer)) + +(defun speedbar-window--live-p () + "Return t if `speedbar-window' is live." + (and (speedbar-window--buffer-live-p) (speedbar-window--window-live-p))) + +(defsubst speedbar-window-current-window () + "Return t if the selected windows is the `speedbar--window'." + (eq (selected-window) speedbar--window)) + +(defsubst speedbar-window--width () + "Return the width of `speedbar-window'." + (let ((edges (window-edges speedbar--window))) + (- (nth 2 edges) (nth 0 edges)))) + +(defun speedbar-frame-or-window () + "Return `frame' or `window' if one of each are open. +Return nil if both are closed." + (cond + ((speedbar-window--live-p) + 'window) + ((and (frame-live-p (speedbar-current-frame)) + speedbar-buffer + (not (speedbar-window--live-p))) + 'frame) + (t nil))) + +;;;###autoload +(defalias 'speedbar-window 'speedbar-window-mode) +;;;###autoload +(defun speedbar-window-mode (&optional arg) + "Enable or disable speedbar window mode. +Positive ARG means turn on, negative turn off. +A nil ARG means toggle. Once the speedbar window is activated, a buffer in +`speedbar-mode' will be displayed. Currently, only one speedbar is +supported at a time. +`speedbar-before-popup-hook' is called before popping up the speedbar frame. +`speedbar-before-delete-hook' is called before the frame is deleted." + (interactive "P") + (when (eq (speedbar-frame-or-window) 'frame) + (delete-frame (speedbar-current-frame))) + + (if (or (and (not arg) (speedbar-window--live-p)) + (and (numberp arg) (< arg 0))) + (speedbar-window--close) + (let ((current-window (selected-window))) + (unless (speedbar-window--buffer-live-p) + (setq speedbar-buffer (get-buffer-create speedbar--buffer-name))) + + (setq speedbar-frame (selected-frame) + dframe-attached-frame (selected-frame) + speedbar-select-frame-method 'attached + speedbar-last-selected-file nil) + + (set-buffer speedbar-buffer) + (speedbar-mode) + + ;; let's create the window + (setq speedbar--window + (display-buffer-in-side-window speedbar-buffer + `((side ,@speedbar-window-side) + (slot . 0) + (dedicated ,@speedbar-window-dedicated-window) + (window-width ,@speedbar--window-width)))) + ;; additional window parameters + (set-window-parameter speedbar--window 'no-other-window t) + (set-window-parameter speedbar--window 'no-delete-other-windows t) + + ;; `speedbar-reconfigure-keymaps' checks if the `speedbar-window' is open, so + ;; should stay after the buffer and window definition. + (speedbar-reconfigure-keymaps) + (speedbar-update-contents) + (speedbar-set-timer dframe-update-speed) + + ;; hscroll + (setq-local auto-hscroll-mode nil) + ;; reset the selection variable + (setq speedbar-last-selected-file nil) + (select-window current-window)))) + +(defun speedbar-window--close () + "Close `speedbar-window'." + (when (speedbar-window--live-p) + (let ((current-window (selected-window))) + ;; store the current window width + (setq speedbar--window-width + (let ((current-width (speedbar-window--width))) + (if (> current-width speedbar-window-max-width) + speedbar-window-max-width + current-width))) + + (delete-window speedbar--window) + (setq speedbar--window nil + speedbar-frame nil + dframe-attached-frame nil) + (speedbar-set-timer nil) + (kill-buffer speedbar-buffer) + (setq speedbar-buffer nil) + (when (and current-window (window-live-p current-window)) + (select-window current-window))))) + (defun speedbar-frame-reposition-smartly () "Reposition the speedbar frame to be next to the attached frame." (cond ((or (assoc 'left speedbar-frame-parameters) @@ -952,10 +1144,6 @@ be displayed. Currently, only one speedbar is supported at a time. (dframe-attached-frame speedbar-frame) speedbar-default-position)))) -(defsubst speedbar-current-frame () - "Return the frame to use for speedbar based on current context." - (dframe-current-frame 'speedbar-frame 'speedbar-mode)) - (defun speedbar-handle-delete-frame (e) "Handle a delete-frame event E. If the deleted frame is the frame speedbar is attached to, @@ -981,6 +1169,14 @@ selected. If the speedbar frame is active, then select the attached frame." Return nil if it doesn't exist." (frame-width speedbar-frame)) +(defun speedbar-width () + "Return the width of the `speedbar'. +if `speedbar-window-mode' is open, the width is `speedbar-window--width' +otherwise the width is `speedbar-frame-width'." + (if (speedbar-window--live-p) + (speedbar-window--width) + (speedbar-frame-width))) + (define-derived-mode speedbar-mode fundamental-mode "Speedbar" "Major mode for managing a display of directories and tags. \\ @@ -1067,7 +1263,7 @@ frame and window to be the currently active frame and window." (if (and (frame-live-p (speedbar-current-frame)) speedbar-buffer) (with-current-buffer speedbar-buffer - (let* ((w (or (speedbar-frame-width) 20)) + (let* ((w (or (speedbar-width) 20)) (p1 "<<") (p5 ">>") (p3 (if speedbar-update-flag "#" "!")) @@ -1129,7 +1325,7 @@ and the existence of packages." (setq alist (cdr alist))) displays))) ;; The trailer - speedbar-easymenu-definition-trailer)) + (speedbar-easymenu-definition-trailer))) (localmap (save-excursion (let ((cf (selected-frame))) (prog2 @@ -1816,7 +2012,7 @@ INDEX is not used, but is required by the caller." ;; Nuke the beginning of the directory if it's too long... (cond ((eq speedbar-directory-button-trim-method 'span) (beginning-of-line) - (let ((ww (or (speedbar-frame-width) 20))) + (let ((ww (or (speedbar-width) 20))) (move-to-column ww nil) (while (>= (current-column) ww) (re-search-backward "[/\\]" nil t) @@ -1832,7 +2028,7 @@ INDEX is not used, but is required by the caller." (move-to-column ww nil))))) ((eq speedbar-directory-button-trim-method 'trim) (end-of-line) - (let ((ww (or (speedbar-frame-width) 20)) + (let ((ww (or (speedbar-width) 20)) (tl (current-column))) (if (< ww tl) (progn @@ -2503,56 +2699,67 @@ Also resets scanner functions." ;; change this if it changed for some reason (speedbar-set-mode-line-format)) +(defun speedbar--speedbar-live-p () + "Return non-nil if `speedbar-window-mode' or `speedbar-frame-mode' are active." + (cond + ((and (speedbar-current-frame) + (frame-live-p (speedbar-current-frame))) + t) + ((speedbar-window--window-live-p) t) + (t nil))) + (defun speedbar-timer-fn () "Run whenever Emacs is idle to update the speedbar item." - (if (or (not (speedbar-current-frame)) - (not (frame-live-p (speedbar-current-frame)))) + (if (not (speedbar--speedbar-live-p)) (speedbar-set-timer nil) ;; Save all the match data so that we don't mess up executing fns (save-match-data ;; Only do stuff if the frame is visible, not an icon, and if ;; it is currently flagged to do something. (if (and speedbar-update-flag - (speedbar-current-frame) + (or (speedbar-window-current-window) + (speedbar-current-frame)) (frame-visible-p (speedbar-current-frame)) (not (eq (frame-visible-p (speedbar-current-frame)) 'icon))) (let ((af (selected-frame))) - (dframe-select-attached-frame speedbar-frame) - ;; make sure we at least choose a window to - ;; get a good directory from - (if (window-minibuffer-p) - nil - ;; Check for special modes - (speedbar-maybe-add-localized-support (current-buffer)) - ;; Update for special mode all the time! - (if (and speedbar-mode-specific-contents-flag - (consp speedbar-special-mode-expansion-list) - (local-variable-p - 'speedbar-special-mode-expansion-list - (current-buffer))) - ;;(eq (get major-mode 'mode-class 'special))) - (progn - (if (<= 2 speedbar-verbosity-level) + (dframe-select-attached-frame speedbar-frame) + ;; make sure we at least choose a window to + ;; get a good directory from + (if (window-minibuffer-p) + nil + ;; Check for special modes + (speedbar-maybe-add-localized-support (current-buffer)) + ;; Update for special mode all the time! + (if (and speedbar-mode-specific-contents-flag + (consp speedbar-special-mode-expansion-list) + (local-variable-p + 'speedbar-special-mode-expansion-list + (current-buffer))) + ;;(eq (get major-mode 'mode-class 'special))) + (progn + (if (<= 2 speedbar-verbosity-level) + (dframe-message + "Updating speedbar to special mode: %s..." + major-mode)) + (speedbar-update-special-contents) + (if (<= 2 speedbar-verbosity-level) + (progn (dframe-message - "Updating speedbar to special mode: %s..." - major-mode)) - (speedbar-update-special-contents) - (if (<= 2 speedbar-verbosity-level) - (progn - (dframe-message - "Updating speedbar to special mode: %s...done" - major-mode) - (dframe-message nil)))) - - ;; Update all the contents if directories change! - (unless (and (or (member major-mode speedbar-ignored-modes) - (eq af (speedbar-current-frame)) - (not (buffer-file-name))) - ;; Always update for GUD. - (not (string-equal "GUD" - speedbar-initial-expansion-list-name))) - (speedbar-update-localized-contents))) - (select-frame af)) + "Updating speedbar to special mode: %s...done" + major-mode) + (dframe-message nil)))) + + ;; Update all the contents if directories change! + (unless (and (or (member major-mode speedbar-ignored-modes) + (and + (eq af (speedbar-current-frame)) + (speedbar-window-current-window)) + (not (buffer-file-name))) + ;; Always update for GUD. + (not (string-equal "GUD" + speedbar-initial-expansion-list-name))) + (speedbar-update-localized-contents))) + (select-frame af)) ;; Now run stealthy updates of time-consuming items (speedbar-stealthy-updates))))) (run-hooks 'speedbar-timer-hook)) @@ -3342,13 +3549,6 @@ TOKEN will be the list, and INDENT is the current indentation level." ;;; Loading files into the attached frame. ;; -(defcustom speedbar-select-frame-method 'attached - "Specify how to select a frame for displaying a file. -A number such as 1 or -1 means to pass that number to `other-frame' -while selecting a frame from speedbar. Any other value means to use -the attached frame (the frame that speedbar was started from)." - :group 'speedbar - :type '(choice integer (other :tag "attached" attached))) (defun speedbar-find-file-in-frame (file) "Load FILE into the frame attached to speedbar.