aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-11 04:37:13 -0500
committerCraig Jennings <c@cjennings.net>2026-05-11 04:37:13 -0500
commita11f554fd533f2139cf6b9e592388a5385d4462b (patch)
tree155e50377d1fc6cf7069417ee154e0ff7a05d5e6
parent675fb681a2ed63d6630696755d40be895fd17743 (diff)
downloadchime-a11f554fd533f2139cf6b9e592388a5385d4462b.tar.gz
chime-a11f554fd533f2139cf6b9e592388a5385d4462b.zip
feat: make modeline status-message strings customizable
The validation banner, retry-attempt message, async-failure tooltip, and the initial loading tooltip were hardcoded English literals. I added defcustoms for them so the wording can be localized or adjusted. The format-string ones keep their %d/%s directives, with the order spelled out in each docstring. I also dropped the dead :info severity from chime-validate-configuration's docstring. The function only ever emitted :ok, :warning, and :error.
-rw-r--r--chime.el76
-rw-r--r--tests/test-chime-status-messages.el167
-rw-r--r--tests/test-chime-validate-configuration.el2
3 files changed, 235 insertions, 10 deletions
diff --git a/chime.el b/chime.el
index c9ac491..cfe7446 100644
--- a/chime.el
+++ b/chime.el
@@ -634,6 +634,64 @@ The single format argument is the option name to customize."
:group 'chime
:type 'string)
+(defcustom chime-validating-message "Chime: Validating configuration..."
+ "Banner printed to *Messages* when validation runs interactively."
+ :package-version '(chime . "0.8.0")
+ :group 'chime
+ :type 'string)
+
+(defcustom chime-validation-summary-format "Chime: %d error%s, %d warning%s."
+ "Format string for the validation summary line in *Messages*.
+Receives four arguments: error count, error plural suffix, warning count,
+warning plural suffix."
+ :package-version '(chime . "0.8.0")
+ :group 'chime
+ :type 'string)
+
+(defcustom chime-async-failure-tooltip
+ "Event check failed — check *Messages* buffer"
+ "Modeline tooltip shown when an async event check fails."
+ :package-version '(chime . "0.8.0")
+ :group 'chime
+ :type 'string)
+
+(defcustom chime-validation-errors-message
+ "Chime: Configuration errors detected (see *Messages* buffer for details)"
+ "Banner printed to *Messages* after validation has exhausted retries."
+ :package-version '(chime . "0.8.0")
+ :group 'chime
+ :type 'string)
+
+(defcustom chime-validation-error-tooltip
+ "Configuration error — check *Messages* buffer"
+ "Modeline tooltip shown after validation has exhausted retries."
+ :package-version '(chime . "0.8.0")
+ :group 'chime
+ :type 'string)
+
+(defcustom chime-validation-waiting-message-format
+ "Chime: Waiting for org-agenda-files to load... (attempt %d/%d)"
+ "Format string for the validation-retry banner in *Messages*.
+Receives two arguments: current attempt number and the configured maximum."
+ :package-version '(chime . "0.8.0")
+ :group 'chime
+ :type 'string)
+
+(defcustom chime-validation-waiting-tooltip-format
+ "Waiting for org-agenda-files... (attempt %d/%d)"
+ "Format string for the validation-retry modeline tooltip.
+Receives two arguments: current attempt number and the configured maximum."
+ :package-version '(chime . "0.8.0")
+ :group 'chime
+ :type 'string)
+
+(defcustom chime-modeline-initial-tooltip
+ "Chime: waiting for first event check..."
+ "Modeline tooltip shown before the first event check completes."
+ :package-version '(chime . "0.8.0")
+ :group 'chime
+ :type 'string)
+
(defcustom chime-sound-file
(expand-file-name "sounds/chime.wav"
(file-name-directory
@@ -2000,7 +2058,7 @@ MESSAGE is nil for passing checks and contains issue details otherwise."
"Display full validation RESULTS in the *Messages* buffer."
(let ((errors 0)
(warnings 0))
- (message "Chime: Validating configuration...")
+ (message "%s" chime-validating-message)
(dolist (result results)
(pcase-let ((`(,severity ,description ,detail) result))
(pcase severity
@@ -2014,7 +2072,7 @@ MESSAGE is nil for passing checks and contains issue details otherwise."
description)
(when detail
(message " %s" detail))))
- (message "Chime: %d error%s, %d warning%s."
+ (message chime-validation-summary-format
errors
(if (= errors 1) "" "s")
warnings
@@ -2024,7 +2082,7 @@ MESSAGE is nil for passing checks and contains issue details otherwise."
(defun chime-validate-configuration ()
"Validate chime's runtime environment and configuration.
Returns a list of (SEVERITY MESSAGE) pairs, or nil if all checks pass.
-SEVERITY is one of: :error :warning :info
+SEVERITY is one of: :error :warning
Checks performed:
- org-agenda-files is set and non-empty
@@ -2116,7 +2174,7 @@ persistent-failure warning, and switches the modeline to its error state."
(chime--debug-log-async-error err))
(chime--log-silently "Chime: %s: %s" prefix (error-message-string err))
(chime--maybe-warn-persistent-failures)
- (chime--set-modeline-error-state "Event check failed — check *Messages* buffer"))
+ (chime--set-modeline-error-state chime-async-failure-tooltip))
(defun chime--handle-async-success (callback events)
"Process a successful async fetch. Invoke CALLBACK with EVENTS.
@@ -2179,15 +2237,15 @@ Returns nil if validation failed and check should be skipped."
(dolist (err errors)
(chime--log-silently "")
(chime--log-silently "ERROR: %s" (cadr err))))
- (message "Chime: Configuration errors detected (see *Messages* buffer for details)")
+ (message "%s" chime-validation-errors-message)
;; Update modeline tooltip to show error state
- (chime--set-modeline-error-state "Configuration error — check *Messages* buffer"))
- (message "Chime: Waiting for org-agenda-files to load... (attempt %d/%d)"
+ (chime--set-modeline-error-state chime-validation-error-tooltip))
+ (message chime-validation-waiting-message-format
chime--validation-retry-count
chime--validation-max-retries)
;; Update modeline tooltip to show waiting state
(chime--set-modeline-error-state
- (format "Waiting for org-agenda-files... (attempt %d/%d)"
+ (format chime-validation-waiting-tooltip-format
chime--validation-retry-count
chime--validation-max-retries)))
nil)
@@ -2252,7 +2310,7 @@ Uses `chime-modeline-no-events-text' with a loading tooltip."
(let ((map (make-sparse-keymap)))
(define-key map [mode-line mouse-1] #'chime--open-calendar-url)
(propertize chime-modeline-no-events-text
- 'help-echo "Chime: waiting for first event check..."
+ 'help-echo chime-modeline-initial-tooltip
'mouse-face 'mode-line-highlight
'local-map map))))
diff --git a/tests/test-chime-status-messages.el b/tests/test-chime-status-messages.el
new file mode 100644
index 0000000..b15b6e4
--- /dev/null
+++ b/tests/test-chime-status-messages.el
@@ -0,0 +1,167 @@
+;;; test-chime-status-messages.el --- Tests for customizable status messages -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2026 Craig Jennings
+
+;; Author: Craig Jennings <c@cjennings.net>
+
+;; This program 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.
+
+;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Verify that the status-message defcustoms control the strings shown to
+;; the user in *Messages* and in the modeline help-echo. Existing tests
+;; in test-chime-validate-configuration.el and test-chime-validation-retry.el
+;; cover the default text; this file proves the wiring honors customization.
+;;
+;; Mocks are kept in a single cl-letf per test to avoid native-comp
+;; trampoline issues with nested redefinitions of subrs like `message'.
+
+;;; Code:
+
+(require 'test-bootstrap (expand-file-name "test-bootstrap.el"))
+(require 'cl-lib)
+
+(defmacro test-chime-status--with-validation-state (&rest body)
+ "Run BODY with isolated chime validation/modeline state."
+ (declare (indent 0) (debug t))
+ `(let ((chime--validation-done nil)
+ (chime--validation-retry-count 0)
+ (chime--validation-max-retries 3)
+ (chime--consecutive-async-failures 0)
+ (chime-modeline-string nil)
+ (chime-modeline-no-events-text " ⏰"))
+ ,@body))
+
+(defun test-chime-status--tooltip-string (modeline-string)
+ "Return the `help-echo' property from MODELINE-STRING, or nil."
+ (and modeline-string
+ (get-text-property 0 'help-echo modeline-string)))
+
+;;; Interactive validation banner and summary
+
+(ert-deftest test-chime-validating-message-uses-defcustom ()
+ "Normal: custom validating banner appears in *Messages* during interactive validate."
+ (let ((chime-validating-message "CUSTOM validating banner")
+ (org-agenda-files '("/tmp/exists.org"))
+ (chime-enable-modeline t)
+ (global-mode-string '(""))
+ (messages nil))
+ (cl-letf (((symbol-function 'file-exists-p) (lambda (_) t))
+ ((symbol-function 'require) (lambda (_ &optional _ _) t))
+ ((symbol-function 'called-interactively-p) (lambda (_) t))
+ ((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages))))
+ (chime-validate-configuration))
+ (should (member "CUSTOM validating banner" (nreverse messages)))))
+
+(ert-deftest test-chime-validation-summary-format-uses-defcustom ()
+ "Normal: custom summary format controls the count summary in *Messages*."
+ (let ((chime-validation-summary-format "summary errs=%d%s warns=%d%s")
+ (org-agenda-files '("/tmp/exists.org"))
+ (chime-enable-modeline t)
+ (global-mode-string '(""))
+ (messages nil))
+ (cl-letf (((symbol-function 'file-exists-p) (lambda (_) t))
+ ((symbol-function 'require) (lambda (_ &optional _ _) t))
+ ((symbol-function 'called-interactively-p) (lambda (_) t))
+ ((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages))))
+ (chime-validate-configuration))
+ (should (member "summary errs=0s warns=0s" (nreverse messages)))))
+
+;;; Async failure tooltip
+
+(ert-deftest test-chime-async-failure-tooltip-uses-defcustom ()
+ "Normal: async-failure tooltip uses the configured string."
+ (test-chime-status--with-validation-state
+ (let ((chime-async-failure-tooltip "CUSTOM async fail")
+ (chime-max-consecutive-failures 0))
+ (chime--record-async-failure '(error "boom") "Async error")
+ (should (string-match-p
+ "CUSTOM async fail"
+ (test-chime-status--tooltip-string chime-modeline-string))))))
+
+;;; Validation retry: errors-message + error tooltip (retries exhausted)
+
+(ert-deftest test-chime-validation-errors-message-uses-defcustom ()
+ "Normal: errors-exhausted banner uses the configured string."
+ (test-chime-status--with-validation-state
+ (let ((chime-validation-errors-message "CUSTOM errors detected")
+ (org-agenda-files nil)
+ (messages nil))
+ (setq chime--validation-retry-count 99)
+ (setq chime--validation-max-retries 0)
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages))))
+ (chime--maybe-validate))
+ (should (member "CUSTOM errors detected" (nreverse messages))))))
+
+(ert-deftest test-chime-validation-error-tooltip-uses-defcustom ()
+ "Normal: errors-exhausted tooltip uses the configured string."
+ (test-chime-status--with-validation-state
+ (let ((chime-validation-error-tooltip "CUSTOM error tip")
+ (org-agenda-files nil))
+ (setq chime--validation-retry-count 99)
+ (setq chime--validation-max-retries 0)
+ (cl-letf (((symbol-function 'message) (lambda (&rest _) nil)))
+ (chime--maybe-validate))
+ (should (string-match-p
+ "CUSTOM error tip"
+ (test-chime-status--tooltip-string chime-modeline-string))))))
+
+;;; Validation retry: waiting-message + waiting-tooltip (retry in progress)
+
+(ert-deftest test-chime-validation-waiting-message-format-uses-defcustom ()
+ "Normal: retry banner uses the configured format with attempt/max."
+ (test-chime-status--with-validation-state
+ (let ((chime-validation-waiting-message-format "waiting %d of %d")
+ (org-agenda-files nil)
+ (messages nil))
+ (setq chime--validation-retry-count 0)
+ (setq chime--validation-max-retries 3)
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages))))
+ (chime--maybe-validate))
+ (should (member "waiting 1 of 3" (nreverse messages))))))
+
+(ert-deftest test-chime-validation-waiting-tooltip-format-uses-defcustom ()
+ "Normal: retry tooltip uses the configured format with attempt/max."
+ (test-chime-status--with-validation-state
+ (let ((chime-validation-waiting-tooltip-format "wait %d %d")
+ (org-agenda-files nil))
+ (setq chime--validation-retry-count 0)
+ (setq chime--validation-max-retries 3)
+ (cl-letf (((symbol-function 'message) (lambda (&rest _) nil)))
+ (chime--maybe-validate))
+ (should (string-match-p
+ "wait 1 3"
+ (test-chime-status--tooltip-string chime-modeline-string))))))
+
+;;; Initial modeline tooltip (before first check)
+
+(ert-deftest test-chime-modeline-initial-tooltip-uses-defcustom ()
+ "Normal: initial modeline tooltip uses the configured string."
+ (let ((chime-modeline-initial-tooltip "CUSTOM initial tip")
+ (chime-modeline-no-events-text " ⏰"))
+ (should (string-match-p
+ "CUSTOM initial tip"
+ (test-chime-status--tooltip-string
+ (chime--make-initial-modeline-string))))))
+
+(provide 'test-chime-status-messages)
+;;; test-chime-status-messages.el ends here
diff --git a/tests/test-chime-validate-configuration.el b/tests/test-chime-validate-configuration.el
index c971632..680808c 100644
--- a/tests/test-chime-validate-configuration.el
+++ b/tests/test-chime-validate-configuration.el
@@ -49,7 +49,7 @@
(cl-some (lambda (issue) (eq (car issue) :warning)) issues))
(defun test-chime-validate-configuration--count-issues (issues severity)
- "Count number of ISSUES with given SEVERITY (:error, :warning, or :info)."
+ "Count number of ISSUES with given SEVERITY (:error or :warning)."
(length (cl-remove-if-not (lambda (i) (eq (car i) severity)) issues)))
;;; Normal Cases - Valid Configurations