unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
blob dcdef7cfafcc2407f265fdb33e7b59366ee0bccc 26565 bytes (raw)
name: lisp/erc/erc-status-sidebar.el 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
 
;;; erc-status-sidebar.el --- HexChat-like activity overview for ERC  -*- lexical-binding: t; -*-

;; Copyright (C) 2017, 2020-2024 Free Software Foundation, Inc.

;; Author: Andrew Barbarello
;; Maintainer: Amin Bandali <bandali@gnu.org>, F. Jason Park <jp@neverwas.me>
;; URL: https://github.com/drewbarbs/erc-status-sidebar

;; 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 <https://www.gnu.org/licenses/>.

;;; Commentary:

;; This package provides a HexChat-like sidebar for joined channels in
;; ERC.  It relies on the `erc-track' module, and displays all of the
;; same information that `erc-track' does in the mode line, but in an
;; alternative format in form of a sidebar.

;; Shout out to sidebar.el <https://github.com/sebastiencs/sidebar.el>
;; and outline-toc.el <https://github.com/abingham/outline-toc.el> for
;; the sidebar window management ideas.

;; Usage:

;; Use M-x erc-status-sidebar-open RET to open the ERC status sidebar
;; in the current frame.  Make sure that the `erc-track' module is
;; active (this is the default).

;; Use M-x erc-status-sidebar-close RET to close the sidebar on the
;; current frame.  With a prefix argument, it closes the sidebar on
;; all frames.

;; Use M-x erc-status-sidebar-kill RET to kill the sidebar buffer and
;; close the sidebar on all frames.

;; In addition to the commands above, you can also try the all-in-one
;; entry point `erc-bufbar-mode'.  See its doc string for usage.

;; If you want the status sidebar enabled whenever you use ERC, add
;; `bufbar' to `erc-modules'.  Note that this library also has a major
;; mode, `erc-status-sidebar-mode', which is for internal use.

;;; Code:

(require 'erc)
(require 'erc-track)
(require 'fringe)
(require 'seq)

(defgroup erc-status-sidebar nil
  "A responsive side window listing all connected ERC buffers.
More commonly known as a window list or \"buflist\", this side
panel displays clickable buffer names for switching to with the
mouse.  By default, ERC highlights the name corresponding to the
selected window's buffer, if any.  In this context, \"connected\"
just means associated with the same IRC session, even one that
has ceased communicating with its server.  For information on how
the window itself works, see Info node `(elisp) Side Windows'."
  :group 'erc)

(defcustom erc-status-sidebar-buffer-name "*ERC Status*"
  "Name of the sidebar buffer."
  :type 'string)

(defcustom erc-status-sidebar-mode-line-format "ERC Status"
  "Mode line format for the status sidebar."
  :type 'string)

(defcustom erc-status-sidebar-header-line-format nil
  "Header line format for the status sidebar."
  :type '(choice (const :tag "No header line" nil)
                 string))

(defcustom erc-status-sidebar-width 15
  "Default width of the sidebar (in columns)."
  :type 'number)

(defcustom erc-status-sidebar-channel-sort
  'erc-status-sidebar-default-chansort
  "Sorting function used to determine order of channels in the sidebar."
  :type 'function)

(defcustom erc-status-sidebar-channel-format
  'erc-status-sidebar-default-chan-format
  "Function used to format channel names for display in the sidebar.
Only consulted for certain values of `erc-status-sidebar-style'."
  :type 'function)

(defcustom erc-status-sidebar-highlight-active-buffer t
  "Whether to highlight the selected window's buffer in the sidebar.
ERC uses the same instance across all frames.  May not be
compatible with all values of `erc-status-sidebar-style'."
  :package-version '(ERC . "5.6")
  :type 'boolean)

(defcustom erc-status-sidebar-style 'all-queries-first
  "Preset style for rendering the sidebar.

When set to `channels-only', ERC limits the items in the
status bar to uniquified channels.  It uses the options
and functions

  `erc-channel-list',
  `erc-status-sidebar-channel-sort',
  `erc-status-sidebar-get-channame',
  `erc-status-sidebar-channel-format'
  `erc-status-sidebar-default-insert'

for selecting, formatting, naming, and inserting entries.  When
set to one of the various `all-*' values, such as `all-mixed',
ERC shows channels and queries under their respective server
buffers, using the functions

  `erc-status-sidebar-all-target-buffers',
  `erc-status-sidebar-default-allsort',
  `erc-status-sidebar-prefer-target-as-name',
  `erc-status-sidebar-default-chan-format',
  `erc-status-sidebar-pad-hierarchy'

for the above-mentioned purposes.  ERC also accepts a list of
functions to perform these roles a la carte.  Since the members
of the above sets aren't really interoperable, we don't offer
them here as customization choices, but you can still specify
them manually.  See doc strings for a description of their
expected arguments and return values."
  :package-version '(ERC . "5.6")
  :type '(choice (const channels-only)
                 (const all-mixed)
                 (const all-queries-first)
                 (const all-channels-first)
                 (list (function :tag "Buffer lister")
                       (function :tag "Buffer sorter")
                       (function :tag "Name extractor")
                       (function :tag "Name formatter")
                       (function :tag "Name inserter"))))

(defcustom erc-status-sidebar-click-display-action t
  "How to display a buffer when clicked.
Values can be anything recognized by `display-buffer' for its
ACTION parameter."
  :package-version '(ERC . "5.6")
  :type '(choice (const :tag "Always use/create other window" t)
                 (const :tag "Let `display-buffer' decide" nil)
                 (const :tag "Same window" (display-buffer-same-window
                                            (inhibit-same-window . nil)))
                 (cons :tag "Action"
                       (choice function (repeat function))
                       (alist :tag "Action arguments"
                              :key-type symbol
                              :value-type (sexp :tag "Value")))))

(defvar erc-status-sidebar--singular-p t
  "Whether to restrict the sidebar to a single frame.
This variable only affects `erc-bufbar-mode'.  Disabling it does
not arrange for automatically showing the sidebar in all frames.
Rather, disabling it allows for displaying the sidebar in the
selected frame even if it's already showing in some other frame.")

(defvar hl-line-mode)
(declare-function hl-line-highlight "hl-line" nil)

(defun erc-status-sidebar-display-window ()
  "Display the status buffer in a side window.  Return the new window."
  (display-buffer
   (erc-status-sidebar-get-buffer)
   `(display-buffer-in-side-window . ((side . left)
                                      (window-width . ,erc-status-sidebar-width)))))

(defun erc-status-sidebar-get-window (&optional no-creation)
  "Return the created/existing window displaying the status buffer.

If NO-CREATION is non-nil, the window is not created."
  (let ((sidebar-window (get-buffer-window erc-status-sidebar-buffer-name
                                           erc-status-sidebar--singular-p)))
    (unless (or sidebar-window no-creation)
      (with-current-buffer (erc-status-sidebar-get-buffer)
        (setq-local vertical-scroll-bar nil))
      (setq sidebar-window (erc-status-sidebar-display-window))
      (set-window-dedicated-p sidebar-window t)
      (set-window-parameter sidebar-window 'no-delete-other-windows t)
      ;; Don't cycle to this window with `other-window'.
      (set-window-parameter sidebar-window 'no-other-window t)
      (setq cursor-type nil)
      (set-window-fringes sidebar-window 0 0)
      ;; Set a custom display table so the window doesn't show a
      ;; truncation symbol when a channel name is too big.
      (let ((dt (make-display-table)))
        (set-window-display-table sidebar-window dt)
        (set-display-table-slot dt 'truncation ?\ )))
    sidebar-window))

(defun erc-status-sidebar-buffer-exists-p ()
  "Check if the sidebar buffer exists."
  (get-buffer erc-status-sidebar-buffer-name))

(defun erc-status-sidebar-get-buffer ()
  "Return the sidebar buffer, creating it if it doesn't exist."
  (get-buffer-create erc-status-sidebar-buffer-name))

(defun erc-status-sidebar-close (&optional all-frames)
  "Close the sidebar.

If called with prefix argument (ALL-FRAMES non-nil), the sidebar
will be closed on all frames.

The erc-status-sidebar buffer is left alone, but the window
containing it on the current frame is closed.  See
`erc-status-sidebar-kill'."
  (interactive "P")
  (mapcar #'delete-window ; FIXME use `mapc'.
          (get-buffer-window-list (erc-status-sidebar-get-buffer)
                                  nil (if all-frames t))))

(defmacro erc-status-sidebar-writable (&rest body)
  "Make the status buffer writable while executing BODY."
  `(let ((buffer-read-only nil))
     ,@body))

(defun erc-status-sidebar--open ()
  "Maybe open the sidebar, respecting `erc-status-sidebar--singular-p'."
  (save-excursion
    (if (erc-status-sidebar-buffer-exists-p)
        (erc-status-sidebar-get-window)
      (with-current-buffer (erc-status-sidebar-get-buffer)
        (erc-status-sidebar-mode)
        (erc-status-sidebar-refresh)))))

;;;###autoload(autoload 'erc-bufbar-mode "erc-status-sidebar" nil t)
(define-erc-module bufbar nil
  "Show `erc-track'-like activity in a side window.
When enabling, show the sidebar immediately in the current frame
if called from a connected ERC buffer.  Otherwise, arrange for
doing so on connect or whenever next displaying a new ERC buffer.
When disabling, hide the status window in all frames.  With a
negative prefix arg, also shutdown the session.  Normally, this
module only allows one sidebar window in an Emacs session.  To
override this, use `erc-status-sidebar-open' to force creation
and `erc-status-sidebar-close' to hide a single instance on the
current frame only."
  ((unless erc-track-mode
     (unless (memq 'track erc-modules)
       (erc--warn-once-before-connect 'erc-bufbar-mode
         "Module `bufbar' needs global module `track'. Enabling now."
         " This will affect \C-]all\C-] ERC sessions."
         " Add `track' to `erc-modules' to silence this message."))
     (erc-track-mode +1))
   (add-hook 'erc--setup-buffer-hook #'erc-status-sidebar--open)
   ;; Preserve side-window dimensions after `custom-buffer-done'.
   (when-let (((not erc--updating-modules-p))
              (buf (or (and (derived-mode-p 'erc-mode) (current-buffer))
                       (car (erc-buffer-filter
                             (lambda () erc-server-connected))))))
     (with-current-buffer buf
       (erc-status-sidebar--open))))
  ((remove-hook 'erc--setup-buffer-hook #'erc-status-sidebar--open)
   (erc-status-sidebar-close 'all-frames)
   (when-let ((arg erc--module-toggle-prefix-arg)
              ((numberp arg))
              ((< arg 0)))
     (erc-status-sidebar-kill))))

;;;###autoload
(defun erc-status-sidebar-open ()
  "Open or create a sidebar window in the current frame.
When `erc-bufbar-mode' is active, do this even if one already
exists in another frame."
  (interactive)
  (let ((erc-status-sidebar--singular-p (not erc-bufbar-mode)))
    (erc-status-sidebar--open)))

;;;###autoload
(defun erc-status-sidebar-toggle ()
  "Toggle the sidebar open/closed on the current frame.
When opening, and `erc-bufbar-mode' is active, create a sidebar
even if one already exists in another frame."
  (interactive)
  (if (get-buffer-window erc-status-sidebar-buffer-name nil)
      (erc-status-sidebar-close)
    (erc-status-sidebar-open)))

(defun erc-status-sidebar-get-channame (buffer)
  "Return name of BUFFER with all leading \"#\" characters removed."
  (let ((s (buffer-name buffer)))
    (if (string-match "^#\\{1,2\\}" s)
        (setq s (replace-match "" t t s)))
    (downcase s)))

(defun erc-status-sidebar-default-chansort (chanlist)
  "Sort CHANLIST case-insensitively for display in the sidebar."
  (sort chanlist (lambda (x y)
                   (string< (erc-status-sidebar-get-channame x)
                            (erc-status-sidebar-get-channame y)))))

(defvar erc-status-sidebar--trimpat nil)
(defvar erc-status-sidebar--prechan nil)

(defun erc-status-sidebar-prefer-target-as-name (buffer)
  "Return some name to represent buffer in the sidebar."
  (if-let ((target (buffer-local-value 'erc--target buffer)))
      (cond ((and erc-status-sidebar--trimpat (erc--target-channel-p target))
             (string-trim-left (erc--target-string target)
                               erc-status-sidebar--trimpat))
            ((and erc-status-sidebar--prechan (erc--target-channel-p target))
             (concat erc-status-sidebar--prechan
                     (erc--target-string target)))
            (t (erc--target-string target)))
    (buffer-name buffer)))

;; This could be converted into an option if people want.
(defvar erc-status-sidebar--show-disconnected t)

(defun erc-status-sidebar-all-target-buffers (process)
  (erc-buffer-filter (lambda ()
                       (and erc--target
                            (or erc-status-sidebar--show-disconnected
                                (erc-server-process-alive))))
                     process))

;; FIXME profile this.  Rebuilding the graph every time track updates
;; seems wasteful for occasions where server messages are processed
;; unthrottled, such as during history playback.  If it's a problem,
;; we should look into rewriting this using `ewoc' or some other
;; solution that maintains a persistent model.
(defun erc-status-sidebar-default-allsort (target-buffers)
  "Return a list of servers interspersed with their targets."
  (mapcan (pcase-lambda (`(,proc . ,chans))
            (cons (process-buffer proc)
                  (let ((erc-status-sidebar--trimpat
                         (and (eq erc-status-sidebar-style 'all-mixed)
                              (with-current-buffer (process-buffer proc)
                                (when-let ((ch-pfxs (erc--get-isupport-entry
                                                     'CHANTYPES 'single)))
                                  (regexp-quote ch-pfxs)))))
                        (erc-status-sidebar--prechan
                         (and (eq erc-status-sidebar-style
                                  'all-queries-first)
                              "\C-?")))
                    (sort chans
                          (lambda (x y)
                            (string<
                             (erc-status-sidebar-prefer-target-as-name x)
                             (erc-status-sidebar-prefer-target-as-name y)))))))
          (sort (seq-group-by (lambda (b)
                                (buffer-local-value 'erc-server-process b))
                              target-buffers)
                (lambda (a b)
                  (string< (buffer-name (process-buffer (car a)))
                           (buffer-name (process-buffer (car b))))))))

(defvar-local erc-status-sidebar--active-marker nil
  "Marker indicating currently active buffer.")

(defun erc-status-sidebar--set-active-line (erc-buffer)
  (when (and erc-status-sidebar-highlight-active-buffer
             (eq (window-buffer (and (minibuffer-window-active-p
                                      (selected-window))
                                     (minibuffer-selected-window)))
                 erc-buffer))
    (set-marker erc-status-sidebar--active-marker (point))))

(defun erc-status-sidebar-default-insert (channame chanbuf _chanlist)
  "Insert CHANNAME followed by a newline.
Maybe arrange to highlight line if CHANBUF is showing in the
focused window."
  (erc-status-sidebar--set-active-line chanbuf)
  (insert channame "\n"))

(defun erc-status-sidebar-pad-hierarchy (bufname buffer buflist)
  "Prefix BUFNAME to emphasize BUFFER's role in BUFLIST."
  (if (and (buffer-live-p buffer) (buffer-local-value 'erc--target buffer))
      (insert " ")
    (unless (eq buffer (car buflist))
      (insert "\n"))) ;  ^L
  (when bufname
    (erc-status-sidebar--set-active-line buffer))
  (insert (or bufname
              (and-let* (((not (buffer-live-p buffer)))
                         (next (cadr (member buffer buflist)))
                         ((buffer-live-p next))
                         (proc (buffer-local-value 'erc-server-process next))
                         (id (process-get proc 'erc-networks--id)))
                (symbol-name (erc-networks--id-symbol id)))
              "???")
          "\n"))

(defun erc-status-sidebar-default-chan-format (channame
                                               &optional num-messages erc-face)
  "Format CHANNAME for display in the sidebar.

If NUM-MESSAGES is non-nil, append it to the channel name.  If
ERC-FACE is non-nil, apply it to channel name.  If it is equal to
`erc-default-face', also apply bold property to make the channel
name stand out."
  (when num-messages
    (setq channame (format "%s [%d]" channame num-messages)))
  (when erc-face
    (put-text-property 0 (length channame) 'face erc-face channame)
    (when (eq erc-face 'erc-default-face)
      (add-face-text-property 0 (length channame) 'bold t channame)))
  channame)

(defun erc-status-sidebar-refresh ()
  "Update the content of the sidebar."
  (interactive)
  (pcase-let* ((`(,list-fn ,sort-fn ,name-fn ,fmt-fn ,insert-fn)
                (pcase erc-status-sidebar-style
                  ('channels-only (list #'erc-channel-list
                                        erc-status-sidebar-channel-sort
                                        #'erc-status-sidebar-get-channame
                                        erc-status-sidebar-channel-format
                                        #'erc-status-sidebar-default-insert))
                  ((or 'all-mixed 'all-queries-first 'all-channels-first)
                   '(erc-status-sidebar-all-target-buffers
                     erc-status-sidebar-default-allsort
                     erc-status-sidebar-prefer-target-as-name
                     erc-status-sidebar-default-chan-format
                     erc-status-sidebar-pad-hierarchy))
                  (v v)))
               (chanlist (apply sort-fn (funcall list-fn nil) nil))
               (windows nil))
    (with-current-buffer (erc-status-sidebar-get-buffer)
      (dolist (window (get-buffer-window-list nil nil t))
        (push (cons window (window-start window)) windows))
      (erc-status-sidebar-writable
       (delete-region (point-min) (point-max))
       (goto-char (point-min))
       (if erc-status-sidebar--active-marker
           (set-marker erc-status-sidebar--active-marker nil)
         (setq erc-status-sidebar--active-marker (make-marker)))
       (dolist (chanbuf chanlist)
         (let* ((tup (seq-find (lambda (tup) (eq (car tup) chanbuf))
                               erc-modified-channels-alist))
                (count (if tup (cadr tup)))
                (face (if tup (cddr tup)))
                (face (if (or (not (buffer-live-p chanbuf))
                              (not (erc-server-process-alive chanbuf)))
                          `(shadow ,face)
                        face))
                (channame (apply fmt-fn
                                 (copy-sequence (funcall name-fn chanbuf))
                                 count face nil))
                (cnlen (length channame)))
           (put-text-property 0 cnlen 'erc-buf chanbuf channame)
           (put-text-property 0 cnlen 'mouse-face 'highlight channame)
           (put-text-property
            0 cnlen 'help-echo
            "mouse-1: switch to buffer in other window" channame)
           (funcall insert-fn channame chanbuf chanlist)))
       (when windows
         (map-apply #'set-window-start windows))
       (when (and erc-status-sidebar-highlight-active-buffer
                  (marker-buffer erc-status-sidebar--active-marker))
         (goto-char erc-status-sidebar--active-marker)
         (require 'hl-line)
         (unless hl-line-mode (hl-line-mode +1))
         (hl-line-highlight))))))

(defun erc-status-sidebar-kill ()
  "Close the ERC status sidebar and its buffer."
  (interactive)
  (when (and erc-bufbar-mode (not erc--module-toggle-prefix-arg))
    (erc-bufbar-mode -1))
  (ignore-errors (kill-buffer erc-status-sidebar-buffer-name)))

(defun erc-status-sidebar-click (event)
  "Handle click EVENT in `erc-status-sidebar-mode-map'."
  (interactive "e")
  (save-excursion
    (let ((window (posn-window (event-start event)))
          (pos (posn-point (event-end event))))
      ;; Current buffer is "ERC Status" and its window is selected
      (cl-assert (eq major-mode 'erc-status-sidebar-mode))
      (cl-assert (eq (selected-window) window))
      (cl-assert (eq (window-buffer window) (current-buffer)))
      (when-let ((buf (get-text-property pos 'erc-buf)))
        ;; Option operates relative to last selected window
        (select-window (get-mru-window nil nil 'not-selected))
        (pop-to-buffer buf erc-status-sidebar-click-display-action)))))

(defun erc-status-sidebar-scroll-up (lines)
  "Scroll sidebar buffer's content LINES linse upward.
If LINES is nil, scroll up a full screen's worth."
  (interactive "P")
  (let ((other-window-scroll-buffer (erc-status-sidebar-get-buffer)))
    (scroll-other-window lines)))

(defun erc-status-sidebar-scroll-down (lines)
  "Scroll sidebar buffer's content LINES lines downward.
If LINES is nil, scroll down a full screen's worth."
  (interactive "P")
  (let ((other-window-scroll-buffer (erc-status-sidebar-get-buffer)))
    (scroll-other-window-down lines)))

(defun erc-status-sidebar-recenter (arg)
  "Recenter the status sidebar.
Expect `erc-status-sidebar-highlight-active-buffer' to be non-nil
and to be invoked in a buffer matching the line currently
highlighted."
  (interactive "P")
  (let* ((buf (erc-status-sidebar-get-buffer))
         (win (get-buffer-window buf)))
    (with-current-buffer buf
      (when (and erc-status-sidebar--active-marker
                 (marker-position erc-status-sidebar--active-marker))
        (with-selected-window win
          (goto-char erc-status-sidebar--active-marker)
          (recenter arg t))))))

(defvar erc-status-sidebar-mode-map
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map special-mode-map)
    (define-key map [mouse-1] #'erc-status-sidebar-click)
    map))

(defvar erc-status-sidebar-refresh-triggers
  '(erc-track-list-changed-hook
    erc-join-hook
    erc-part-hook
    erc-kill-buffer-hook
    erc-kill-channel-hook
    erc-kill-server-hook
    erc-kick-hook
    erc-disconnected-hook
    erc-quit-hook)
  "Hooks to refresh the sidebar on.
This may be set locally in the status-sidebar buffer under
various conditions, like when the option
`erc-status-sidebar-highlight-active-buffer' is non-nil.")

(defvar erc-status-sidebar--highlight-refresh-triggers
  '(window-selection-change-functions)
  "Triggers enabled with `erc-status-sidebar-highlight-active-buffer'.")

(defun erc-status-sidebar--refresh-unless-input ()
  "Run `erc-status-sidebar-refresh' unless there are unread commands.
Also abstain when the user is interacting with the minibuffer."
  (unless (or (input-pending-p) (minibuffer-window-active-p (selected-window)))
    (erc-status-sidebar-refresh)))

(defun erc-status-sidebar--post-refresh (&rest _ignore)
  "Schedule sidebar refresh for execution after command stack is cleared.

Ignore arguments in IGNORE, allowing this function to be added to
hooks that invoke it with arguments."
  (run-at-time 0 nil #'erc-status-sidebar--refresh-unless-input))

(defun erc-status-sidebar-mode--unhook ()
  "Remove hooks installed by `erc-status-sidebar-mode'."
  (dolist (hk erc-status-sidebar-refresh-triggers)
    (remove-hook hk #'erc-status-sidebar--post-refresh))
  (remove-hook 'window-configuration-change-hook
               #'erc-status-sidebar-set-window-preserve-size))

(defun erc-status-sidebar-set-window-preserve-size ()
  "Tell Emacs to preserve the current height/width of the ERC sidebar window.

Note that preserve status needs to be reset when the window is
manually resized, so `erc-status-sidebar-mode' adds this function
to the `window-configuration-change-hook'."
  (when (and (eq (selected-window) (let (erc-status-sidebar--singular-p)
                                     (erc-status-sidebar-get-window)))
             (fboundp 'window-preserve-size))
    (unless (eq (window-total-width) (window-min-size nil t))
      (apply #'window-preserve-size (selected-window) t t nil))))

(define-derived-mode erc-status-sidebar-mode special-mode "ERC Sidebar"
  "Major mode for ERC status sidebar."
  ;; Users invoking M-x erc-status-sidebar-mode most likely expect to
  ;; summon the module's minor-mode, `erc-bufbar-mode'.
  :interactive nil
  ;; Don't scroll the buffer horizontally, if a channel name is
  ;; obscured then the window can be resized.
  (setq-local auto-hscroll-mode nil)
  (setq cursor-type nil
        buffer-read-only t
        mode-line-format erc-status-sidebar-mode-line-format
        header-line-format erc-status-sidebar-header-line-format)
  (erc-status-sidebar-set-window-preserve-size)

  (add-hook 'window-configuration-change-hook
            #'erc-status-sidebar-set-window-preserve-size nil t)
  (when erc-status-sidebar-highlight-active-buffer
    (setq-local erc-status-sidebar-refresh-triggers
                `(,@erc-status-sidebar--highlight-refresh-triggers
                  ,@erc-status-sidebar-refresh-triggers)))
  (dolist (hk erc-status-sidebar-refresh-triggers)
    (add-hook hk #'erc-status-sidebar--post-refresh))

  ;; `change-major-mode-hook' is run *before* the
  ;; erc-status-sidebar-mode initialization code, so it won't undo the
  ;; add-hook's we did in the previous expressions.
  (add-hook 'change-major-mode-hook #'erc-status-sidebar-mode--unhook nil t)
  (add-hook 'kill-buffer-hook #'erc-status-sidebar-mode--unhook nil t))

(provide 'erc-status-sidebar)
;;; erc-status-sidebar.el ends here

;; Local Variables:
;; generated-autoload-file: "erc-loaddefs.el"
;; End:

debug log:

solving dcdef7cfafc ...
found dcdef7cfafc in https://git.savannah.gnu.org/cgit/emacs.git

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).