;;; erc-speedbar.el --- Speedbar support for ERC -*- lexical-binding: t; -*- ;; Copyright (C) 2001-2004, 2006-2025 Free Software Foundation, Inc. ;; Author: Mario Lang ;; Contributor: Eric M. Ludlam ;; Maintainer: Amin Bandali , F. Jason Park ;; URL: https://www.emacswiki.org/emacs/ErcSpeedbar ;; This file is part of GNU Emacs. ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;;; Commentary: ;; This module provides integration of ERC into the Speedbar. ;;; TODO / ideas: ;; * Write intelligent update function: ;; update-channel, update-nick, remove-nick-from-channel, ... ;; * Use indicator-strings for op/voice ;; * Extract/convert face notes field from bbdb if available ;; * Write tests that run in a term-mode subprocess ;; ;;; Code: (require 'erc) (require 'erc-goodies) (require 'erc-button) (require 'speedbar) ;;; Customization: (defgroup erc-speedbar nil "Speedbar integration for ERC. To open an ERC-flavored speedbar in a separate frame, run the command `erc-speedbar-browser'. To use a window-based proxy instead, run \\[erc-nickbar-mode] in a connected ERC buffer or put `nickbar' in `erc-modules' before connecting. See Info node `(speedbar) Top' for more about the underlying integration." :group 'erc) (defcustom erc-speedbar-nicknames-window-width 18 "Default width of the nicknames sidebar (in columns)." :package-version '(ERC . "5.6") :type 'integer) (defcustom erc-speedbar-sort-users-type 'activity "How channel nicknames are sorted. `activity' - Sort users by channel activity `alphabetical' - Sort users alphabetically nil - Do not sort users" :type '(choice (const :tag "Sort users by channel activity" activity) (const :tag "Sort users alphabetically" alphabetical) (const :tag "Do not sort users" nil))) (defcustom erc-speedbar-hide-mode-topic 'headerline "Hide mode and topic lines." :package-version '(ERC . "5.6") :type '(choice (const :tag "Always show" nil) (const :tag "Always hide" t) (const :tag "Omit when headerline visible" headerline))) (defcustom erc-speedbar-my-nick-face t "A face to use for your nickname. When the value is t, ERC uses `erc-current-nick-face' if `erc-match' has been loaded and `erc-my-nick-face' otherwise. When using the `nicks' module, you can see your nick as it appears to others by coordinating with the option `erc-nicks-skip-faces'." :package-version '(ERC . "5.6") :type '(choice face (const :tag "Current nick or own speaker face" t))) (defvar erc-speedbar-key-map nil "Keymap used when in erc display mode.") (defun erc-install-speedbar-variables () "Install those variables used by speedbar to enhance ERC." (if erc-speedbar-key-map nil (setq erc-speedbar-key-map (speedbar-make-specialized-keymap)) ;; Basic tree features (define-key erc-speedbar-key-map "e" #'speedbar-edit-line) (define-key erc-speedbar-key-map "\C-m" #'speedbar-edit-line) (define-key erc-speedbar-key-map "+" #'speedbar-expand-line) (define-key erc-speedbar-key-map "=" #'speedbar-expand-line) (define-key erc-speedbar-key-map "-" #'speedbar-contract-line)) (speedbar-add-expansion-list '("ERC" erc-speedbar-menu-items erc-speedbar-key-map erc-speedbar-server-buttons)) (speedbar-add-mode-functions-list '("ERC" (speedbar-item-info . erc-speedbar-item-info)))) (defvar erc-speedbar-menu-items '(["Goto buffer" speedbar-edit-line t] ["Expand Node" speedbar-expand-line (save-excursion (beginning-of-line) (looking-at "[0-9]+: *.\\+. "))] ["Contract Node" speedbar-contract-line (save-excursion (beginning-of-line) (looking-at "[0-9]+: *.-. "))]) "Additional menu-items to add to speedbar frame.") ;;; ERC hierarchy display method ;;;###autoload (defun erc-speedbar-browser () "Initialize speedbar to display an ERC browser. This will add a speedbar major display mode." (interactive) (require 'speedbar) (erc-install-speedbar-variables) ;; Make sure that speedbar is active (speedbar-frame-mode 1) ;; Now, throw us into Info mode on speedbar. (speedbar-change-initial-expansion-list "ERC") (speedbar-get-focus)) (defun erc-speedbar-buttons (buffer) "Create buttons for speedbar in BUFFER." (erase-buffer) (let (serverp chanp queryp queries-current-p) (with-current-buffer buffer ;; The function `dframe-help-echo' checks the default value of ;; `dframe-help-echo-function' when deciding whether to visit ;; the buffer and fire the callback. This works in normal ;; speedbar frames because the event handler runs in the ;; `window-buffer' of the active frame. But in our hacked ;; version, where the frame is hidden, `speedbar-item-info' ;; never runs without this workaround. (setq-local dframe-help-echo-function #'ignore) (setq serverp (erc--server-buffer-p)) (setq chanp (erc-channel-p (erc-default-target))) (setq queryp (erc-query-buffer-p) queries-current-p (erc--queries-current-p))) (defvar erc-nickbar-mode) (cond ((and erc-nickbar-mode (null (get-buffer-window speedbar-buffer))) (run-at-time 0 nil #'erc-nickbar-mode -1)) (serverp (erc-speedbar-channel-buttons nil 0 buffer)) ((or chanp (and queryp queries-current-p)) (erc-speedbar-insert-target buffer 0) (forward-line -1) (erc-speedbar-expand-channel "+" buffer 0)) (queryp (erc-speedbar-insert-target buffer 0)) (t (ignore))))) (defun erc-speedbar-server-buttons (_directory depth) "Insert the initial list of servers you are connected to." (let ((servers (erc-buffer-list (lambda () (eq (current-buffer) (process-buffer erc-server-process)))))) (when servers (speedbar-with-writable (dolist (server servers) (speedbar-make-tag-line 'bracket ?+ 'erc-speedbar-expand-server server (buffer-name server) 'erc-speedbar-goto-buffer server nil depth)) t)))) (defun erc-speedbar-expand-server (text server indent) (cond ((string-search "+" text) (speedbar-change-expand-button-char ?-) (if (speedbar-with-writable (save-excursion (end-of-line) (forward-char 1) (erc-speedbar-channel-buttons nil (1+ indent) server))) (speedbar-change-expand-button-char ?-) (speedbar-change-expand-button-char ??))) (;; we have to contract this node (string-search "-" text) (speedbar-change-expand-button-char ?+) (speedbar-delete-subblock indent)) (t (error "Ooops... not sure what to do"))) (speedbar-center-buffer-smartly)) (defun erc-speedbar-channel-buttons (_directory depth server-buffer) (when (get-buffer server-buffer) (let* ((proc (with-current-buffer server-buffer erc-server-process)) (targets (erc-buffer-list (lambda () (not (eq (process-buffer erc-server-process) (current-buffer)))) proc))) (when targets (speedbar-with-writable (dolist (target targets) (erc-speedbar-insert-target target depth)) t))))) (defun erc-speedbar-insert-target (buffer depth) (if (with-current-buffer buffer (or (erc--target-channel-p erc--target) (erc--queries-current-p))) (progn (speedbar-make-tag-line 'bracket ?+ 'erc-speedbar-expand-channel buffer (erc--target-string (buffer-local-value 'erc--target buffer)) 'erc-speedbar-goto-buffer buffer nil depth) (save-excursion (forward-line -1) (let ((table (buffer-local-value 'erc-channel-users buffer))) (speedbar-add-indicator (format "(%d)" (hash-table-count table))) (rx "(" (+ (any "0-9")) ")")))) ;; Query target (cl-assert (erc-query-buffer-p buffer)) (speedbar-make-tag-line 'bracket ?? nil nil (buffer-name buffer) 'erc-speedbar-goto-buffer buffer nil depth))) (defconst erc-speedbar--fmt-sentinel (gensym "erc-speedbar-") "Symbol for identifying a nonstandard `speedbar-token' text property. When encountered, ERC assumes the value's tail contains `format'-compatible args.") (defun erc-speedbar-expand-channel (text channel indent) "For the line matching TEXT, in CHANNEL, expand or contract a line. INDENT is the current indentation level." (cond ((string-search "+" text) (speedbar-change-expand-button-char ?-) (speedbar-with-writable (save-excursion (end-of-line) (forward-char 1) (let ((modes (buffer-local-value 'erc--mode-line-mode-string channel)) (topic (erc-controls-interpret (with-current-buffer channel erc-channel-topic)))) (when modes (speedbar-make-tag-line 'angle ?m nil (list erc-speedbar--fmt-sentinel "Mode: %s" modes) modes nil nil 'erc-notice-face (1+ indent))) (unless (string= topic "") (speedbar-make-tag-line 'angle ?t nil (list erc-speedbar--fmt-sentinel "Topic: %s" topic) topic nil nil 'erc-notice-face (1+ indent))) (unless (pcase erc-speedbar-hide-mode-topic ('nil 'show) ('headerline (null erc-header-line-format))) (save-excursion (goto-char (point-max)) (forward-line (if (string= topic "") -1 -2)) (put-text-property (pos-bol) (point-max) 'invisible t))) (let ((names (cond ((eq erc-speedbar-sort-users-type 'alphabetical) (erc-sort-channel-users-alphabetically (with-current-buffer channel (erc-get-channel-user-list)))) ((eq erc-speedbar-sort-users-type 'activity) (erc-sort-channel-users-by-activity (with-current-buffer channel (erc-get-channel-user-list)))) (t (with-current-buffer channel (erc-get-channel-user-list)))))) (when names (speedbar-with-writable (dolist (entry names) (erc-speedbar-insert-user entry ?+ (1+ indent) channel))))))))) ((string-search "-" text) (speedbar-change-expand-button-char ?+) (speedbar-delete-subblock indent)) (t (error "Ooops... not sure what to do"))) (speedbar-center-buffer-smartly)) (defvar erc-speedbar--nick-face-function #'erc-speedbar--highlight-self-and-ops "Function called when finding a face for fontifying nicks. Called with the proposed nick, the `erc-server-user', and the `erc-channel-user'. Should return any valid face, possibly composed or anonymous, or nil.") (defun erc-speedbar--highlight-self-and-ops (buffer user cuser) "Highlight own nick and op'd users in the speedbar." (with-current-buffer buffer (if (erc-current-nick-p (erc-server-user-nickname user)) (pcase erc-speedbar-my-nick-face ('t (if (facep 'erc-current-nick-face) 'erc-current-nick-face 'erc-my-nick-face)) (v v)) (or (and cuser (not (zerop (erc-channel-user-status cuser))) erc-button-nickname-face) 'erc-default-face)))) (defun erc-speedbar--on-click (nick sbtoken _indent) ;; 0: finger, 1: name, 2: info, 3: buffer-name (with-current-buffer (nth 3 sbtoken) (erc-nick-popup (string-trim-left nick "[~&@%+]+")))) (defun erc-speedbar-insert-user (entry exp-char indent &optional buffer) "Insert one user based on the channel member list ENTRY. Expect EXP-CHAR to be the expansion character to use, INDENT the current indentation level, and BUFFER the associated channel or query buffer. Set the `speedbar-function' text property to `erc-speedbar--on-click', which is called with the formatted nick, a so-called \"token\", and the indent level. The token is a list of four items: the userhost, the GECOS, the current `erc-server-user' info slot, and the associated buffer." (let* ((user (car entry)) (cuser (cdr entry)) (nick (erc-server-user-nickname user)) (host (erc-server-user-host user)) (info (erc-server-user-info user)) (login (erc-server-user-login user)) (name (erc-server-user-full-name user)) (nick-str (concat (with-current-buffer (or buffer (current-buffer)) (erc-get-channel-membership-prefix cuser)) nick)) (finger (concat login (when (or login host) "@") host)) (sbtoken (list finger name info (buffer-name buffer)))) (if (or login host name info) ; we want to be expandable (speedbar-make-tag-line 'bracket ?+ 'erc-speedbar-expand-user sbtoken nick-str #'erc-speedbar--on-click sbtoken (funcall erc-speedbar--nick-face-function buffer user cuser) indent) (when (equal exp-char ?-) (forward-line -1) (erc-speedbar-expand-user "+" (list finger name info) indent)) (speedbar-make-tag-line 'statictag ?? nil nil nick-str nil nil nil indent)))) (defun erc-speedbar-update-channel (buffer) "Update the speedbar information about a ERC buffer. The update is only done when the channel is actually expanded already." ;; This is only a rude hack and doesn't care about multiserver usage ;; yet, consider this a brain storming, better ideas? (with-current-buffer speedbar-buffer (save-excursion (goto-char (point-min)) (when (re-search-forward (concat "^1: *.+. *" (regexp-quote (buffer-name buffer))) nil t) (beginning-of-line) (speedbar-delete-subblock 1) (erc-speedbar-expand-channel "+" buffer 1))))) (defun erc-speedbar-expand-user (text token indent) (cond ((string-search "+" text) (speedbar-change-expand-button-char ?-) (speedbar-with-writable (save-excursion (end-of-line) (forward-char 1) (let ((finger (nth 0 token)) (name (nth 1 token)) (info (nth 2 token))) (when finger (speedbar-make-tag-line nil nil nil nil finger nil nil nil (1+ indent))) (when name (speedbar-make-tag-line nil nil nil nil name nil nil nil (1+ indent))) (when info (speedbar-make-tag-line nil nil nil nil info nil nil nil (1+ indent))))))) ((string-search "-" text) (speedbar-change-expand-button-char ?+) (speedbar-delete-subblock indent)) (t (error "Ooops... not sure what to do"))) (speedbar-center-buffer-smartly)) (defun erc-speedbar-goto-buffer (_text buffer _indent) "When user clicks on TEXT, goto an ERC buffer. The INDENT level is ignored." (if (featurep 'dframe) (progn (dframe-select-attached-frame speedbar-frame) (let ((bwin (get-buffer-window buffer 0))) (if bwin (progn (select-window bwin) (raise-frame (window-frame bwin))) (if dframe-power-click (let ((pop-up-frames t)) (select-window (display-buffer buffer))) (dframe-select-attached-frame speedbar-frame) (switch-to-buffer buffer))))) (let ((bwin (get-buffer-window buffer 0))) (if bwin (progn (select-window bwin) (raise-frame (window-frame bwin))) (if speedbar-power-click (let ((pop-up-frames t)) (select-window (display-buffer buffer))) (dframe-select-attached-frame speedbar-frame) (switch-to-buffer buffer)))))) (defun erc-speedbar-line-text () "Return the text for the item on the current line." (beginning-of-line) (when (re-search-forward "[]>] " nil t) (buffer-substring-no-properties (point) (line-end-position)))) (defun erc-speedbar-item-info () "Display information about the current buffer on the current line." (let ((data (speedbar-line-token)) (txt (erc-speedbar-line-text))) (cond ((and data (listp data)) (message "%s: %s" txt (car data))) ((bufferp data) (message "Channel: %s" txt)) ;; Print help if line has a non-standard ([-+?=]) button ;; char and a `speedbar-token' property with a known CAR. ((and-let* ((p (text-property-not-all (pos-bol) (pos-eol) 'speedbar-token nil)) (v (get-text-property p 'speedbar-token)) ((eq erc-speedbar--fmt-sentinel (car v)))) (apply #'message (cdr v)))) (t (message "%s" txt))))) ;;;; Status-sidebar integration (defvar erc-track-mode) (defvar erc-track--switch-fallback-blockers) (defvar erc-status-sidebar-buffer-name) (declare-function erc-status-sidebar-set-window-preserve-size "erc-status-sidebar" nil) (defvar erc-speedbar--buffer-options '((speedbar-update-flag . t) (speedbar-use-images . nil) (speedbar-hide-button-brackets-flag . t))) (defvar erc-speedbar--hidden-speedbar-frame nil "The original `speedbar-frame', which `erc-nickbar-mode' deletes. It keeps a reference to it in order to run upstream teardown procedures without having to create a dummy frame for that purpose.") (defun erc-speedbar--emulate-sidebar-set-window-preserve-size () (let ((erc-status-sidebar-buffer-name (buffer-name speedbar-buffer)) (display-buffer-overriding-action `(display-buffer-in-side-window . ((side . right) (window-width . ,erc-speedbar-nicknames-window-width))))) (erc-status-sidebar-set-window-preserve-size))) (defun erc-speedbar--status-sidebar-mode--unhook () "Remove hooks installed by `erc-status-sidebar-mode'." (remove-hook 'window-configuration-change-hook #'erc-speedbar--emulate-sidebar-set-window-preserve-size)) (defun erc-speedbar--emulate-sidebar () "Perform local setup for `erc-nickbar-mode' in a new `speedbar-buffer'." (require 'erc-status-sidebar) (cl-assert speedbar-frame) (cl-assert (eq speedbar-buffer (current-buffer))) (cl-assert (eq speedbar-frame (selected-frame))) (setq erc-speedbar--hidden-speedbar-frame speedbar-frame ;; In Emacs 27, this is not `local-variable-if-set-p'. dframe-controlled #'erc-speedbar--dframe-controlled) (add-hook 'window-configuration-change-hook #'erc-speedbar--emulate-sidebar-set-window-preserve-size nil t) (add-hook 'kill-buffer-hook #'erc-speedbar--status-sidebar-mode--unhook nil t) (with-current-buffer speedbar-buffer (pcase-dolist (`(,var . ,val) erc-speedbar--buffer-options) (set (make-local-variable var) val))) (when (memq 'nicks erc-modules) (with-current-buffer speedbar-buffer (add-function :around (local 'erc-speedbar--nick-face-function) #'erc-speedbar--compose-nicks-face)))) (defun erc-speedbar--handle-delete-frame (event) "Disable the nickbar if EVENT is deleting the proxy frame." (when (and speedbar-frame (cdr (frame-list)) (pcase event (`(delete-frame (,frame)) (eq frame speedbar-frame)))) (erc-nickbar-mode -1))) (defun erc-speedbar--ensure (&optional forcep) "Perform common setup for `erc-nickbar-mode'. Without FORCEP, return early when the calling context isn't associated with an ERC session." (save-excursion (when (or (erc-server-buffer) forcep) (when erc-track-mode (cl-pushnew '(derived-mode . speedbar-mode) erc-track--switch-fallback-blockers :test #'equal)) (unless speedbar-update-flag (erc-button--display-error-notice-with-keys (erc-server-buffer) "Module `nickbar' needs `speedbar-update-flag' to be non-nil" (and (not (display-graphic-p)) " in text terminals") ". Setting to t for the current Emacs session." " Customize it permanently to avoid this message.") (setq speedbar-update-flag t)) (when-let* (((null speedbar-buffer)) (speedbar-frame-parameters (backquote-list* '(visibility . nil) '(no-other-frame . t) speedbar-frame-parameters)) (speedbar-after-create-hook #'erc-speedbar--emulate-sidebar) (original-frame (selected-frame))) (erc-install-speedbar-variables) ;; Run before toggling mode to prevent timer from being ;; created twice. (speedbar-change-initial-expansion-list "ERC") (speedbar-frame-mode 1) ;; The setup steps below can't go in the "create hook" because ;; the frame with `window-main-window' will be raised and ;; steal focus if you switch away from Emacs in the meantime. (let ((frame speedbar-frame)) (cl-assert (not (eq speedbar-frame original-frame))) (select-frame (setq speedbar-frame original-frame)) (delete-frame frame)) ;; Allow deleting (our) `speedbar-frame' with the mouse. (with-current-buffer speedbar-buffer (kill-local-variable 'dframe-delete-frame-function) (setq dframe-delete-frame-function #'erc-speedbar--handle-delete-frame))) (with-selected-frame speedbar-frame (erc-speedbar--emulate-sidebar-set-window-preserve-size) (erc-speedbar-toggle-nicknames-window-lock -1)) (cl-assert (null (cdr (erc-speedbar--get-timers)))) (with-current-buffer speedbar-buffer (setq speedbar-update-flag t) (speedbar-set-mode-line-format))))) (defvar erc-speedbar--force-update-interval-secs 5 "Speedbar update period.") (defvar-local erc-speedbar--last-ran nil "When non-nil, a Lisp timestamp updated when the speedbar timer runs.") (defun erc-speedbar--prod-dframe-timer (&rest _) "Refresh speedbar if dormant for `erc-speedbar--force-update-interval-secs'." (when (buffer-live-p speedbar-buffer) (with-current-buffer speedbar-buffer (when (and dframe-timer (or (null erc-speedbar--last-ran) (time-less-p erc-speedbar--last-ran (time-subtract (current-time) erc-speedbar--force-update-interval-secs)))) (run-at-time 0 nil #'dframe-timer-fn)))) nil) (defun erc-speedbar--reset-last-ran-on-timer () "Reset `erc-speedbar--last-ran'." (when speedbar-buffer (with-current-buffer speedbar-buffer (setq erc-speedbar--last-ran (current-time))))) ;;;###autoload(autoload 'erc-nickbar-mode "erc-speedbar" nil t) (define-erc-module nickbar nil "Show nicknames for current target buffer in a side window. When enabling, create a speedbar session if one doesn't exist and show its buffer in an `erc-status-sidebar' window instead of a separate frame. If ERC doesn't yet have any live connections, defer activation until such time. This means the variable `erc-nickbar-mode' may be t even though no actual speedbar yet exists. When disabling, destroy the speedbar session. For controlling whether the speedbar window is selectable with `other-window', see `erc-nickbar-toggle-nicknames-window-lock'." ((add-hook 'erc--setup-buffer-hook #'erc-speedbar--ensure) (add-hook 'speedbar-timer-hook #'erc-speedbar--reset-last-ran-on-timer) (add-hook 'erc-insert-post-hook #'erc-speedbar--prod-dframe-timer) (add-hook 'erc-server-PONG-functions #'erc-speedbar--prod-dframe-timer) (erc-speedbar--ensure) (unless (or erc--updating-modules-p (and speedbar-buffer (eq speedbar-frame (window-frame (get-buffer-window speedbar-buffer t))))) (when-let* ((buf (or (and (derived-mode-p 'erc-mode) (current-buffer)) (car (erc-buffer-filter #'erc--server-buffer-p))))) (with-current-buffer buf (erc-speedbar--ensure 'forcep))))) ((remove-hook 'erc--setup-buffer-hook #'erc-speedbar--ensure) (remove-hook 'speedbar-timer-hook #'erc-speedbar--reset-last-ran-on-timer) (remove-hook 'erc-insert-post-hook #'erc-speedbar--prod-dframe-timer) (remove-hook 'erc-server-PONG-functions #'erc-speedbar--prod-dframe-timer) (when erc-track-mode (setq erc-track--switch-fallback-blockers (remove '(derived-mode . speedbar-mode) erc-track--switch-fallback-blockers))) ;; `speedbar-buffer' may be nil if the mode was never enabled, such ;; as when disabling the module through Customize after startup. (when speedbar-buffer ;; Close associated windows and stop updating but leave timer. (dolist (window (get-buffer-window-list speedbar-buffer nil t)) (unless (frame-root-window-p window) (when erc-speedbar--hidden-speedbar-frame (cl-assert (not (eq (window-frame window) erc-speedbar--hidden-speedbar-frame)))) (delete-window window))) (with-current-buffer speedbar-buffer (setq speedbar-update-flag nil) (speedbar-set-mode-line-format) (unless (eq erc--module-toggle-prefix-arg most-negative-fixnum) (dframe-close-frame)))))) (defun erc-speedbar--get-timers () (cl-remove #'dframe-timer-fn timer-idle-list :key #'timer--function :test-not #'eq)) (defun erc-speedbar--dframe-controlled (arg) (when speedbar-buffer (cl-assert (eq speedbar-buffer (current-buffer)))) (when (and erc-speedbar--hidden-speedbar-frame (numberp arg) (< arg 0)) (when erc-nickbar-mode (erc-nickbar-mode most-negative-fixnum)) (setq speedbar-frame erc-speedbar--hidden-speedbar-frame erc-speedbar--hidden-speedbar-frame nil) (speedbar-frame-mode arg) ; -1 ;; As of Emacs 29, `dframe-set-timer' can't remove `dframe-timer'. (cl-assert (= 1 (length (erc-speedbar--get-timers))) t) (cancel-function-timers #'dframe-timer-fn) ;; `dframe-close-frame' kills the buffer but no function in ;; erc-speedbar.el resets this to nil. (setq erc-speedbar--hidden-speedbar-frame nil speedbar-buffer nil speedbar-frame nil))) (defun erc-speedbar-toggle-nicknames-window-lock (arg) "Toggle whether nicknames window is selectable with \\[other-window]. When ARG is a number, lock the window if non-negative. Otherwise, unlock the window." (interactive "P") (unless erc-nickbar-mode (user-error "`erc-nickbar-mode' inactive")) (when-let* ((window (get-buffer-window speedbar-buffer))) (let ((val (cond ((natnump arg) t) ((integerp arg) nil) (t (not (erc-compat--window-no-other-p window)))))) (with-current-buffer speedbar-buffer (setq cursor-type (not val))) (set-window-parameter window 'no-other-window val) (unless (numberp arg) (message "nick-window: %s" (if val "protected" "selectable")))))) (defalias 'erc-nickbar-toggle-nicknames-window-lock #'erc-speedbar-toggle-nicknames-window-lock) ;;;; Nicks integration (declare-function erc-nicks--highlight "erc-nicks" (nickname &optional face)) (defun erc-speedbar--compose-nicks-face (orig buffer user cuser) (require 'erc-nicks) (let ((rv (funcall orig buffer user cuser))) (if-let* ((nick (erc-server-user-nickname user)) (face (with-current-buffer buffer (erc-nicks--highlight nick rv))) ((not (eq face erc-button-nickname-face)))) (cons face (ensure-list rv)) rv))) (provide 'erc-speedbar) ;;; erc-speedbar.el ends here ;; ;; Local Variables: ;; generated-autoload-file: "erc-loaddefs.el" ;; End: