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'.
This commit is contained in:
Vincenzo Pupillo 2025-03-06 13:39:01 +01:00 committed by Eli Zaretskii
parent c8471129dd
commit c1aab8c49b
4 changed files with 340 additions and 74 deletions

View file

@ -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{<M>} 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}.

View file

@ -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

View file

@ -1501,6 +1501,34 @@ Meant to be given a global binding convenient to the user. Example:
(keymap-global-set "C-c M-r" 'remember-prefix-map)
** Speedbar
+++
*** The new command 'speedbar-window-mode' opens Speedbar in a window
instead of a frame.
+++
*** New command 'speedbar-window' is an alias for 'speedbar-window-mode'.
+++
*** The new user option 'speedbar-prefer-window', tells 'speedbar' to
open a side window instead of a frame.
+++
*** The new user option speedbar-dedicated-window defines whether the
speedbar is displayed in a dedicated window.
+++
*** The new user option 'speedbar-window-default-width' defines the
initial width of the 'speedbar-window'
+++
*** The new user option 'speedbar-window-max-width' defines the maximum
width of the 'speedbar-window' when it is closed and then restored.
---
*** 'speedbar-easymenu-definition-trailer' is now a function.
** Miscellaneous
---

View file

@ -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.
\\<speedbar-mode-map>
@ -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
@ -1840,7 +2036,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)
@ -1856,7 +2052,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
@ -2527,56 +2723,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))))
"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))
;; 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))
@ -3366,13 +3573,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.