Subject: bug#76261: [PATCH] Add a mentions buffer feature to rcirc

From: Tassilo Horn <[email-masked]>
Date: Fri, 14 Feb 2025 11:39:16 +0100

Tassilo Horn <[email-masked]> writes:

Hi again,

> The proposed feature (if enabled) collects such mentions in a separate
> buffer *rcirc mentions* as clickable text, so that you can navigate to
> the source of the mention as often as you like.

Attached is a revised version of the patch which uses markers instead of
point values now and formats the mentions nicely, see this screenshot:

Bye,
Tassilo
>From 233dc0ecd923e630126bbde9de24a25af0172b9a Mon Sep 17 00:00:00 2001
From: Tassilo Horn <[email-masked]>
Date: Thu, 13 Feb 2025 10:39:31 +0100
Subject: [PATCH] Add a mentions buffer feature to rcirc

If the feature is enabled by setting rcirc-mentions-buffer-enabled to a
non-nil value, a buffer rcirc-mentions-buffer-name is created and used
for logging mentions of your nick or a keyword in rcirc-keywords.  The
log lines link to the original position in the actual channel buffer
where the mention occured.

* lisp/net/rcirc.el (rcirc-mentions-buffer-name): New defcustom.
(rcirc-mentions-buffer-enabled): New defcustom.
(rcirc--get-last-message-bounds): New helper function.
(rcirc-update-mentions-buffer): New function.
(rcirc-record-activity): Call it if rcirc-mentions-buffer-enabled is
non-nil.
(rcirc-switch-to-mentions-buffer): New command.
(rcirc-mode-map): Bind it `to C-c C-.'.
---
 lisp/net/rcirc.el | 96 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 96 insertions(+)

diff --git a/lisp/net/rcirc.el b/lisp/net/rcirc.el
index 3f81672182b..15bf851bb1b 100644
--- a/lisp/net/rcirc.el
+++ b/lisp/net/rcirc.el
@@ -1448,6 +1448,7 @@ rcirc-mode-map
   "C-c C-x" #'rcirc-cmd-quit
   "C-c C-i" #'rcirc-toggle-ignore-buffer-activity
   "C-c C-s" #'rcirc-switch-to-server-buffer
+  "C-c C-." #'rcirc-switch-to-mentions-buffer
   "C-c C-a" #'rcirc-jump-to-first-unread-line)
 
 (defvar-local rcirc-short-buffer-name nil
@@ -2466,6 +2467,97 @@ rcirc-next-active-buffer
                  ""))))
   (rcirc-update-activity-string))
 
+(defcustom rcirc-mentions-buffer-name "*rcirc mentions*"
+  "The name of the mentions buffer.
+Also see `rcirc-mentions-buffer-enabled'."
+  :type 'string
+  :version "31.1")
+
+(defcustom rcirc-mentions-buffer-enabled nil
+  "If non-nil, create a mentions buffer.
+The name of this buffer is defined by `rcirc-mentions-buffer-name'.  In
+this buffer, mentions of your nick or a keyword in `rcirc-keywords' will
+be logged linking to the original message in the respective channel
+buffer."
+  :type 'boolean
+  :version "31.1")
+
+(defun rcirc--get-last-message-bounds ()
+  ;; Assumes being called while the current buffer is the buffer with
+  ;; the activity.
+  (save-excursion
+    (goto-char rcirc-prompt-start-marker)
+    (goto-char (1- (point)))
+    (let ((end (point)))
+      (when-let* ((beg (previous-single-property-change
+                        (point) 'rcirc-text)))
+        (goto-char beg)
+        (cons (line-beginning-position) end)))))
+
+(defun rcirc-update-mentions-buffer (type)
+  ;; Assumes being called while the current buffer is the buffer with
+  ;; the activity.
+  (when-let* ((bounds (rcirc--get-last-message-bounds)))
+    (font-lock-ensure (car bounds) (cdr bounds))
+    (let* ((msg (buffer-substring (car bounds) (cdr bounds)))
+           (activity-marker (let ((m (make-marker)))
+                              (set-marker m (car bounds))))
+           (action
+            (lambda (_button)
+              (let ((buf (marker-buffer activity-marker)))
+                (if (buffer-live-p buf)
+                    (if-let* ((pos (marker-position activity-marker)))
+                        (progn
+                          (switch-to-buffer-other-window buf)
+                          (goto-char activity-marker))
+                      (message "The buffer %s has been truncated." buf))
+                  (message "The originating buffer has disappeared."))))))
+      (with-current-buffer (get-buffer-create
+                            rcirc-mentions-buffer-name)
+        (setq-local buffer-read-only t)
+        ;; We want to see the original fontification of the channel
+        ;; buffer.
+        (font-lock-mode 1)
+        (let ((inhibit-read-only t)
+              ;; Stay at the current position in the buffer when we are
+              ;; not on the very last mention.
+              (orig-pos (when (save-excursion
+                                (text-property-search-forward
+                                 'face t
+                                 (lambda (_v pv)
+                                   (and (listp pv)
+                                        (eq 'separator-line
+                                            (plist-get pv :inherit))
+                                        (plist-get pv :extend)))))
+                          (point))))
+          (goto-char (point-max))
+          (unless (bobp)
+            (insert (make-separator-line)))
+          (insert (rcirc-facify
+                   (format-time-string
+                    (string-trim-right rcirc-time-format)
+                    (current-time))
+                   'rcirc-timestamp)
+                  ": ")
+          (let ((link-beg (point)))
+            (insert-button
+             (format "%s mentioned in %s."
+                     (if (eq type 'nick) "Nick" "Keyword")
+                     (buffer-name (marker-buffer activity-marker)))
+             'action action
+             'follow-link t)
+            (insert "\n" msg "\n")
+            (goto-char (or orig-pos link-beg))))))))
+
+(defun rcirc-switch-to-mentions-buffer ()
+  "Switch to the mentions buffer if it exists.
+See `rcirc-mentions-buffer-enabled' and `rcirc-mentions-buffer-name'."
+  (interactive)
+  (when-let* ((buf (get-buffer rcirc-mentions-buffer-name)))
+    (if-let* ((win (get-buffer-window buf)))
+        (select-window win)
+      (switch-to-buffer buf))))
+
 (defvar rcirc-activity-functions nil
   "Hook to be run when there is channel activity.
 
@@ -2478,6 +2570,10 @@ rcirc-record-activity
   (with-current-buffer buffer
     (let ((old-activity rcirc-activity)
           (old-types rcirc-activity-types))
+      (when (and rcirc-mentions-buffer-enabled
+                 (memq type '(nick keyword))
+                 (not (eq buffer rcirc-server-buffer)))
+        (rcirc-update-mentions-buffer type))
       (when (and (not (get-buffer-window (current-buffer) t))
                  (not (and rcirc-track-ignore-server-buffer-flag
                            (eq rcirc-server-buffer (current-buffer)))))
-- 
2.48.1