aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.org4
-rw-r--r--tests/test-wttrin--add-buffer-instructions.el12
-rw-r--r--tests/test-wttrin-location-history.el8
-rw-r--r--tests/test-wttrin-make-default.el130
-rw-r--r--wttrin.el48
5 files changed, 186 insertions, 16 deletions
diff --git a/README.org b/README.org
index 82b36b4..909bd52 100644
--- a/README.org
+++ b/README.org
@@ -106,7 +106,9 @@ Simply use the keybinding you assigned, or run `M-x wttrin` to display the weath
[[assets/location-menu.png]]
-Choose one, or for a quick one-time weather check, type a new location and ⏎ . After the weather is displayed, you can press `a` to check another location, `g` to refresh, or `q` to quit.
+Choose one, or for a quick one-time weather check, type a new location and ⏎ . After the weather is displayed, you can press `a` to check another location, `g` to refresh, `d` to make the shown location your default, or `q` to quit.
+
+Pressing `d` sets =wttrin-favorite-location= to the location on screen and remembers it across restarts (via savehist), so the mode-line and future sessions follow it. Your default is also offered in the location list the next time you run =M-x wttrin=. Enable =savehist-mode= for the persistence to stick.
If you're looking at cached data, a line below the weather art tells you how old it is (e.g., "Last updated: 2:30 PM (5 minutes ago)").
diff --git a/tests/test-wttrin--add-buffer-instructions.el b/tests/test-wttrin--add-buffer-instructions.el
index 0f1c382..425832d 100644
--- a/tests/test-wttrin--add-buffer-instructions.el
+++ b/tests/test-wttrin--add-buffer-instructions.el
@@ -28,7 +28,7 @@
"Test adding instructions to empty buffer."
(with-temp-buffer
(wttrin--add-buffer-instructions)
- (should (string= "\n\nPress: [a] for another location [g] to refresh [q] to quit"
+ (should (string= "\n\nPress: [a] for another location [g] to refresh [d] to make default [q] to quit"
(buffer-string)))))
(ert-deftest test-wttrin--add-buffer-instructions-normal-with-existing-content-appends-instructions ()
@@ -36,7 +36,7 @@
(with-temp-buffer
(insert "Weather: Sunny\nTemperature: 20°C")
(wttrin--add-buffer-instructions)
- (should (string= "Weather: Sunny\nTemperature: 20°C\n\nPress: [a] for another location [g] to refresh [q] to quit"
+ (should (string= "Weather: Sunny\nTemperature: 20°C\n\nPress: [a] for another location [g] to refresh [d] to make default [q] to quit"
(buffer-string)))))
(ert-deftest test-wttrin--add-buffer-instructions-normal-preserves-point-moves-to-end ()
@@ -99,7 +99,7 @@
(insert "Weather data here")
(goto-char (point-min))
(wttrin--add-buffer-instructions)
- (should (string-suffix-p "Press: [a] for another location [g] to refresh [q] to quit"
+ (should (string-suffix-p "Press: [a] for another location [g] to refresh [d] to make default [q] to quit"
(buffer-string)))))
(ert-deftest test-wttrin--add-buffer-instructions-boundary-point-in-middle-appends-at-end ()
@@ -109,7 +109,7 @@
(goto-char (point-min))
(forward-line 1)
(wttrin--add-buffer-instructions)
- (should (string-suffix-p "Press: [a] for another location [g] to refresh [q] to quit"
+ (should (string-suffix-p "Press: [a] for another location [g] to refresh [d] to make default [q] to quit"
(buffer-string)))))
(ert-deftest test-wttrin--add-buffer-instructions-boundary-trailing-newlines-preserves-newlines ()
@@ -117,7 +117,7 @@
(with-temp-buffer
(insert "Weather\n\n\n")
(wttrin--add-buffer-instructions)
- (should (string= "Weather\n\n\n\n\nPress: [a] for another location [g] to refresh [q] to quit"
+ (should (string= "Weather\n\n\n\n\nPress: [a] for another location [g] to refresh [d] to make default [q] to quit"
(buffer-string)))))
(ert-deftest test-wttrin--add-buffer-instructions-boundary-very-large-buffer-appends-at-end ()
@@ -126,7 +126,7 @@
(insert (make-string 10000 ?x))
(wttrin--add-buffer-instructions)
(goto-char (point-max))
- (should (looking-back "Press: \\[a\\] for another location \\[g\\] to refresh \\[q\\] to quit" nil))))
+ (should (looking-back "Press: \\[a\\] for another location \\[g\\] to refresh \\[d\\] to make default \\[q\\] to quit" nil))))
;;; Error Cases
diff --git a/tests/test-wttrin-location-history.el b/tests/test-wttrin-location-history.el
index d23bdcd..61b495c 100644
--- a/tests/test-wttrin-location-history.el
+++ b/tests/test-wttrin-location-history.el
@@ -211,11 +211,13 @@
(should (memq 'wttrin--location-history savehist-additional-variables))))
(ert-deftest test-wttrin-location-history-boundary-savehist-register-idempotent ()
- "Registering when already present does not duplicate the entry."
+ "Registering an already-present variable does not duplicate it."
(require 'savehist)
- (let ((savehist-additional-variables '(wttrin--location-history)))
+ (require 'cl-lib)
+ (let ((savehist-additional-variables '(wttrin--location-history wttrin-favorite-location)))
(wttrin--savehist-register)
- (should (equal '(wttrin--location-history) savehist-additional-variables))))
+ (should (= 1 (cl-count 'wttrin--location-history savehist-additional-variables)))
+ (should (= 1 (cl-count 'wttrin-favorite-location savehist-additional-variables)))))
(ert-deftest test-wttrin-location-history-integration-savehist-register-on-save-hook ()
"The registration runs on `savehist-save-hook' so it survives a clobber."
diff --git a/tests/test-wttrin-make-default.el b/tests/test-wttrin-make-default.el
new file mode 100644
index 0000000..c6f845b
--- /dev/null
+++ b/tests/test-wttrin-make-default.el
@@ -0,0 +1,130 @@
+;;; test-wttrin-make-default.el --- Tests for promote-to-default command -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2026 Craig Jennings
+
+;;; Commentary:
+
+;; Unit tests for wttrin--set-favorite-location and wttrin-make-default,
+;; the weather-buffer command (bound to "d") that promotes the displayed
+;; location to the persisted favorite.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(require 'wttrin)
+
+;;; --------------------------------------------------------------------------
+;;; wttrin--set-favorite-location
+;;; --------------------------------------------------------------------------
+
+;;; Normal Cases
+
+(ert-deftest test-wttrin--set-favorite-location-normal-sets-variable ()
+ "Normal: sets `wttrin-favorite-location' to the given location."
+ (let ((wttrin-favorite-location nil)
+ (savehist-additional-variables nil))
+ (wttrin--set-favorite-location "Paris, FR")
+ (should (equal wttrin-favorite-location "Paris, FR"))))
+
+(ert-deftest test-wttrin--set-favorite-location-error-no-savehist-loaded ()
+ "Error: setting the favorite works even when savehist is not loaded.
+The setter must not touch `savehist-additional-variables' directly (it may be
+unbound); persistence is left to `wttrin--savehist-register'."
+ (let ((wttrin-favorite-location nil))
+ ;; Simulate savehist absent: the variable is unbound.
+ (cl-letf (((symbol-function 'wttrin--savehist-register)
+ (lambda () (error "Should not be called from the setter"))))
+ (wttrin--set-favorite-location "Oslo, NO")
+ (should (equal wttrin-favorite-location "Oslo, NO")))))
+
+(ert-deftest test-wttrin--set-favorite-location-normal-drops-from-history ()
+ "Normal: promoting a location removes it from the search history."
+ (let ((wttrin-favorite-location nil)
+ (wttrin--location-history '("Reykjavik" "Oslo, NO")))
+ (wttrin--set-favorite-location "Reykjavik")
+ (should-not (member "Reykjavik" wttrin--location-history))
+ (should (equal wttrin--location-history '("Oslo, NO")))))
+
+(ert-deftest test-wttrin--set-favorite-location-boundary-not-in-history-is-noop ()
+ "Boundary: promoting a location absent from history leaves history intact."
+ (let ((wttrin-favorite-location nil)
+ (wttrin--location-history '("Oslo, NO")))
+ (wttrin--set-favorite-location "Berkeley, CA")
+ (should (equal wttrin--location-history '("Oslo, NO")))))
+
+(ert-deftest test-wttrin-favorite-savehist-register-includes-favorite ()
+ "Normal: `wttrin--savehist-register' registers the favorite for persistence."
+ (require 'savehist)
+ (let ((savehist-additional-variables '(kill-ring)))
+ (wttrin--savehist-register)
+ (should (memq 'wttrin-favorite-location savehist-additional-variables))))
+
+;;; --------------------------------------------------------------------------
+;;; wttrin-make-default
+;;; --------------------------------------------------------------------------
+
+;;; Normal Cases
+
+(ert-deftest test-wttrin-make-default-normal-sets-favorite-from-current ()
+ "Normal: promotes the buffer's current location to the favorite."
+ (let ((wttrin-favorite-location nil)
+ (savehist-additional-variables nil))
+ (with-temp-buffer
+ (setq-local wttrin--current-location "Tokyo, JP")
+ (wttrin-make-default)
+ (should (equal wttrin-favorite-location "Tokyo, JP")))))
+
+;;; Boundary Cases
+
+(ert-deftest test-wttrin-make-default-boundary-nil-current-leaves-favorite ()
+ "Boundary: no current location is a no-op that leaves the favorite intact."
+ (let ((wttrin-favorite-location "Berkeley, CA")
+ (savehist-additional-variables nil))
+ (with-temp-buffer
+ (setq-local wttrin--current-location nil)
+ (wttrin-make-default)
+ (should (equal wttrin-favorite-location "Berkeley, CA")))))
+
+;;; --------------------------------------------------------------------------
+;;; favorite in completion candidates
+;;; --------------------------------------------------------------------------
+
+;;; Normal Cases
+
+(ert-deftest test-wttrin-make-default-normal-favorite-prepended-to-candidates ()
+ "Normal: a typed-in favorite is offered in the picker, at the front."
+ (let ((wttrin-default-locations '("Honolulu, HI" "Berkeley, CA"))
+ (wttrin--location-history nil)
+ (wttrin-favorite-location "Reykjavik"))
+ (should (equal (wttrin--completion-candidates)
+ '("Reykjavik" "Honolulu, HI" "Berkeley, CA")))))
+
+;;; Boundary Cases
+
+(ert-deftest test-wttrin-make-default-boundary-favorite-default-not-duplicated ()
+ "Boundary: a favorite that is already a default appears exactly once."
+ (require 'cl-lib)
+ (let ((wttrin-default-locations '("Honolulu, HI" "Berkeley, CA"))
+ (wttrin--location-history nil)
+ (wttrin-favorite-location "Berkeley, CA"))
+ (should (= 1 (cl-count "Berkeley, CA" (wttrin--completion-candidates) :test #'equal)))))
+
+(ert-deftest test-wttrin-make-default-boundary-nil-favorite-candidates-unchanged ()
+ "Boundary: nil favorite leaves the candidate list as defaults plus history."
+ (let ((wttrin-default-locations '("Honolulu, HI"))
+ (wttrin--location-history '("Oslo, NO"))
+ (wttrin-favorite-location nil))
+ (should (equal (wttrin--completion-candidates)
+ '("Honolulu, HI" "Oslo, NO")))))
+
+;;; --------------------------------------------------------------------------
+;;; keymap binding
+;;; --------------------------------------------------------------------------
+
+(ert-deftest test-wttrin-make-default-normal-d-bound-in-mode-map ()
+ "Normal: the weather-buffer keymap binds \"d\" to the command."
+ (should (eq (lookup-key wttrin-mode-map (kbd "d")) 'wttrin-make-default)))
+
+(provide 'test-wttrin-make-default)
+;;; test-wttrin-make-default.el ends here
diff --git a/wttrin.el b/wttrin.el
index b1c6562..c4b31bc 100644
--- a/wttrin.el
+++ b/wttrin.el
@@ -536,11 +536,14 @@ Persisted across sessions via `savehist-mode'.")
(defvar savehist-additional-variables)
(defun wttrin--savehist-register ()
- "Ensure `wttrin--location-history' is persisted by savehist.
+ "Ensure wttrin's persisted variables are saved by savehist.
+Registers `wttrin--location-history' and `wttrin-favorite-location' so both
+survive across restarts without the Emacs custom-variable mechanism.
Run both at load and on `savehist-save-hook', so the registration survives a
user `setq' of `savehist-additional-variables' (a common config pattern) that
-would otherwise drop the entry before it could be saved."
- (add-to-list 'savehist-additional-variables 'wttrin--location-history))
+would otherwise drop the entries before they could be saved."
+ (add-to-list 'savehist-additional-variables 'wttrin--location-history)
+ (add-to-list 'savehist-additional-variables 'wttrin-favorite-location))
(with-eval-after-load 'savehist
(wttrin--savehist-register)
@@ -563,9 +566,16 @@ entry is promoted to most-recent, and the list is trimmed to
(- (length wttrin--location-history) max)))))))
(defun wttrin--completion-candidates ()
- "Return default locations followed by search-history entries.
-History already excludes defaults (see `wttrin--add-to-location-history')."
- (append wttrin-default-locations wttrin--location-history))
+ "Return the favorite, default locations, then search-history entries.
+History already excludes defaults (see `wttrin--add-to-location-history'), and
+`wttrin--set-favorite-location' drops the favorite from history. The favorite
+\(`wttrin-favorite-location', when a string) is prepended unless it is already a
+default, so it always appears exactly once."
+ (let ((candidates (append wttrin-default-locations wttrin--location-history)))
+ (if (and (stringp wttrin-favorite-location)
+ (not (member wttrin-favorite-location candidates)))
+ (cons wttrin-favorite-location candidates)
+ candidates)))
(defun wttrin-remove-location-history (location)
"Remove LOCATION from the search history.
@@ -602,6 +612,7 @@ Prompts with completion over the current history entries."
(let ((map (make-sparse-keymap)))
(define-key map (kbd "a") 'wttrin-requery)
(define-key map (kbd "g") 'wttrin-requery-force)
+ (define-key map (kbd "d") 'wttrin-make-default)
;; Note: 'q' is bound to quit-window by special-mode
map)
"Keymap for wttrin-mode.")
@@ -664,6 +675,8 @@ Bracketed key chords use `wttrin-key'; the surrounding prose uses
(" for another location " . wttrin-instructions)
("[g]" . wttrin-key)
(" to refresh " . wttrin-instructions)
+ ("[d]" . wttrin-key)
+ (" to make default " . wttrin-instructions)
("[q]" . wttrin-key)
(" to quit" . wttrin-instructions)))
(insert (propertize (car segment) 'face (cdr segment)))))
@@ -836,6 +849,29 @@ reject inaccurate results."
(wttrin-query wttrin--current-location))
(message "No location to refresh")))
+(defun wttrin--set-favorite-location (location)
+ "Set `wttrin-favorite-location' to LOCATION and drop it from search history.
+LOCATION becomes a permanent default, so it no longer needs a history entry,
+mirroring how `wttrin-default-locations' entries are kept out of history.
+Persistence is handled by `wttrin--savehist-register', which registers the
+variable when savehist loads and again on `savehist-save-hook', so the value
+survives restarts without the Emacs custom-variable mechanism, and setting it
+here works whether or not savehist is loaded."
+ (setq wttrin-favorite-location location)
+ (setq wttrin--location-history (delete location wttrin--location-history)))
+
+(defun wttrin-make-default ()
+ "Make the location shown in this buffer the favorite (persisted) default.
+Sets `wttrin-favorite-location' to the displayed location so it drives the
+mode-line and survives restarts. No-op with a message when the buffer has
+no current location."
+ (interactive)
+ (if wttrin--current-location
+ (progn
+ (wttrin--set-favorite-location wttrin--current-location)
+ (message "wttrin: %s is now the default location" wttrin--current-location))
+ (message "wttrin: no location to make default")))
+
;;; Mode-line weather display
(defun wttrin--replace-response-location (response location)