summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-11-04 17:32:08 -0600
committerCraig Jennings <c@cjennings.net>2025-11-04 17:32:08 -0600
commit541c61a403e01922a7569f5e23c25eba08fce427 (patch)
tree4e923a8d58026658bb37d2eecf6855922a531cfe
parent18b89fbdf3b5b13ccfd0d3a09f6bb2925addbab2 (diff)
refactor:debug: Rename and enhance wttrin debug functions
- Reorganized debugging utilities by renaming `debug-wttrin.el` to `wttrin-debug.el` and adding new functions. - The updated module now supports `debug-wttrin-mode-line` for detailed mode-line diagnostics and introduces customizable mode-line weather display with new configuration options. - Additionally, extended debugging capabilities ensure concise emoji-based weather info and tooltip data management, incorporated with auto-loaded conditional debug logic.
-rw-r--r--debug-wttrin.el52
-rw-r--r--wttrin-debug.el107
-rw-r--r--wttrin.el229
3 files changed, 331 insertions, 57 deletions
diff --git a/debug-wttrin.el b/debug-wttrin.el
deleted file mode 100644
index 8b8798b..0000000
--- a/debug-wttrin.el
+++ /dev/null
@@ -1,52 +0,0 @@
-;;; debug-wttrin.el --- Debug helper for wttrin -*- lexical-binding: t -*-
-;;
-;; This file provides utilities for debugging wttrin display issues.
-;;
-;;; Commentary:
-;;
-;; To enable debug mode:
-;; (setq wttrin-debug t)
-;;
-;; This will save raw weather responses to timestamped files in your
-;; temp directory for bug reports.
-;;
-;; To view raw data with line numbers for development:
-;; M-x debug-wttrin-show-raw RET <location> RET
-;;
-;;; Code:
-
-(require 'wttrin)
-
-(defun debug-wttrin-show-raw (location)
- "Fetch and display raw wttr.in data for LOCATION with line numbers.
-This is useful for debugging header parsing issues."
- (interactive "sLocation: ")
- (let ((raw-string (wttrin--get-cached-or-fetch location)))
- (with-current-buffer (get-buffer-create "*wttrin-debug*")
- (erase-buffer)
- (insert raw-string)
- (goto-char (point-min))
- (let ((line-num 1))
- (while (not (eobp))
- (beginning-of-line)
- (insert (format "%2d: " line-num))
- (setq line-num (1+ line-num))
- (forward-line 1)))
- (goto-char (point-min))
- (switch-to-buffer (current-buffer)))))
-
-(defun debug-wttrin-enable ()
- "Enable wttrin debug mode.
-Raw weather data will be saved to timestamped files for bug reports."
- (interactive)
- (setq wttrin-debug t)
- (message "Wttrin debug mode enabled. Raw data will be saved to: %s" temporary-file-directory))
-
-(defun debug-wttrin-disable ()
- "Disable wttrin debug mode."
- (interactive)
- (setq wttrin-debug nil)
- (message "Wttrin debug mode disabled"))
-
-(provide 'debug-wttrin)
-;;; debug-wttrin.el ends here
diff --git a/wttrin-debug.el b/wttrin-debug.el
new file mode 100644
index 0000000..55546d4
--- /dev/null
+++ b/wttrin-debug.el
@@ -0,0 +1,107 @@
+;;; wttrin-debug.el --- Debug functions for wttrin.el -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2025 Craig Jennings
+;; Author: Craig Jennings <c@cjennings.net>
+;; Keywords: debug weather wttrin
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;;; Commentary:
+
+;; This file contains debug functions for troubleshooting wttrin.el behavior.
+;; It is only loaded when `wttrin-debug' is non-nil.
+;;
+;; Enable with:
+;; (setq wttrin-debug t)
+;; (require 'wttrin)
+;;
+;; Available debug functions:
+;; - `debug-wttrin-show-raw' - View raw weather data with line numbers
+;; - `debug-wttrin-mode-line' - Diagnose mode-line lighter issues
+;; - `wttrin--debug-mode-line-info' - Auto-called when wttrin runs (if debug enabled)
+;;
+;; Interactive commands:
+;; - M-x debug-wttrin-enable - Enable debug mode
+;; - M-x debug-wttrin-disable - Disable debug mode
+;;
+;; When debug mode is enabled, raw weather data is automatically saved to
+;; timestamped files in `temporary-file-directory' for bug reports.
+
+;;; Code:
+
+;; wttrin-debug.el is loaded by wttrin.el, so wttrin is already loaded
+;; No need for (require 'wttrin) here
+
+;;;###autoload
+(defun debug-wttrin-show-raw (location)
+ "Fetch and display raw wttr.in data for LOCATION with line numbers.
+This is useful for debugging header parsing issues."
+ (interactive "sLocation: ")
+ (let ((raw-string (wttrin--get-cached-or-fetch location)))
+ (with-current-buffer (get-buffer-create "*wttrin-debug*")
+ (erase-buffer)
+ (insert raw-string)
+ (goto-char (point-min))
+ (let ((line-num 1))
+ (while (not (eobp))
+ (beginning-of-line)
+ (insert (format "%2d: " line-num))
+ (setq line-num (1+ line-num))
+ (forward-line 1)))
+ (goto-char (point-min))
+ (switch-to-buffer (current-buffer)))))
+
+;;;###autoload
+(defun debug-wttrin-enable ()
+ "Enable wttrin debug mode.
+Raw weather data will be saved to timestamped files for bug reports."
+ (interactive)
+ (setq wttrin-debug t)
+ (message "Wttrin debug mode enabled. Raw data will be saved to: %s" temporary-file-directory))
+
+;;;###autoload
+(defun debug-wttrin-disable ()
+ "Disable wttrin debug mode."
+ (interactive)
+ (setq wttrin-debug nil)
+ (message "Wttrin debug mode disabled"))
+
+;;;###autoload
+(defun debug-wttrin-mode-line ()
+ "Display detailed mode-line information for the wttrin buffer.
+This is useful for diagnosing why the mode-line lighter isn't appearing."
+ (interactive)
+ (if-let ((buf (get-buffer "*wttr.in*")))
+ (with-current-buffer buf
+ (let* ((has-custom-modeline (boundp 'cj/modeline-major-mode))
+ (formatted-mode (when has-custom-modeline
+ (format-mode-line mode-name))))
+ (with-output-to-temp-buffer "*wttrin-mode-debug*"
+ (princ (format "=== Wttrin Mode-Line Debug Info ===\n\n"))
+ (princ (format "Buffer: %s\n" (buffer-name)))
+ (princ (format "Major mode: %s\n" major-mode))
+ (princ (format "mode-name variable: %S\n" mode-name))
+ (princ (format "mode-name type: %s\n" (type-of mode-name)))
+ (princ (format "\nCustom modeline detected: %s\n" has-custom-modeline))
+ (when has-custom-modeline
+ (princ (format "format-mode-line result: %S\n" formatted-mode)))
+ (princ (format "\nmode-line-format first 5 elements:\n"))
+ (let ((i 0))
+ (dolist (elem mode-line-format)
+ (when (< i 5)
+ (princ (format " [%d] %S\n" i elem))
+ (setq i (1+ i)))))
+ (princ (format "\nSpecial-mode parent: %s\n"
+ (get 'wttrin-mode 'derived-mode-parent)))
+ (princ (format "Is special-mode active: %s\n"
+ (derived-mode-p 'special-mode))))))
+ (message "No *wttr.in* buffer exists. Run M-x wttrin first.")))
+
+(defun wttrin--debug-mode-line-info ()
+ "Auto-generate mode-line diagnostic information.
+This function is called automatically when wttrin runs if debug mode is enabled.
+It creates the *wttrin-mode-debug* buffer with diagnostic information."
+ (debug-wttrin-mode-line))
+
+(provide 'wttrin-debug)
+;;; wttrin-debug.el ends here
diff --git a/wttrin.el b/wttrin.el
index d4e58fe..8bce49d 100644
--- a/wttrin.el
+++ b/wttrin.el
@@ -37,6 +37,9 @@
(require 'url)
(require 'xterm-color) ;; https://github.com/atomontage/xterm-color
+;; Declare functions from wttrin-debug.el (loaded conditionally)
+(declare-function wttrin--debug-mode-line-info "wttrin-debug")
+
(defgroup wttrin nil
"Emacs frontend for the weather web service wttr.in."
:prefix "wttrin-"
@@ -92,19 +95,70 @@ units (default)."
:group 'wttrin
:type 'integer)
+(defcustom wttrin-mode-line-favorite-location nil
+ "Favorite location to display weather for in the mode-line.
+When nil, mode-line weather display is disabled.
+Set to a location string (e.g., \"New Orleans, LA\") to enable.
+The weather icon and tooltip will update automatically in the background."
+ :group 'wttrin
+ :type '(choice (const :tag "Disabled" nil)
+ (string :tag "Location")))
+
+(defcustom wttrin-mode-line-refresh-interval 900
+ "Interval in seconds to refresh mode-line weather data.
+Default is 900 seconds (15 minutes)."
+ :group 'wttrin
+ :type 'integer)
+
+(defcustom wttrin-mode-line-emoji-font "Noto Color Emoji"
+ "Font family to use for mode-line weather emoji.
+Common color emoji fonts include:
+- \"Noto Color Emoji\" (Linux)
+- \"Apple Color Emoji\" (macOS)
+- \"Segoe UI Emoji\" (Windows)
+- \"Twitter Color Emoji\"
+Set to nil to use default font (may render as monochrome)."
+ :group 'wttrin
+ :type '(choice (const :tag "Use default font" nil)
+ (string :tag "Font family name")))
+
(defcustom wttrin-debug nil
- "If non-nil, save raw weather data to timestamped files for debugging.
-Raw data files are saved to `temporary-file-directory' with names like
-wttrin-debug-YYYYMMDD-HHMMSS.txt for bug reports."
+ "Enable debug functions for troubleshooting wttrin behavior.
+When non-nil, loads wttrin-debug.el which provides:
+- Automatic mode-line diagnostic logging when wttrin runs
+- Raw weather data saved to timestamped files in `temporary-file-directory'
+- Interactive debug commands for troubleshooting
+
+Set this to t BEFORE loading wttrin, typically in your init file:
+ (setq wttrin-debug t)
+ (require \\='wttrin)"
:group 'wttrin
:type 'boolean)
+;; Load debug functions if enabled
+(when wttrin-debug
+ (require 'wttrin-debug
+ (expand-file-name "wttrin-debug.el"
+ (file-name-directory (or load-file-name buffer-file-name)))
+ t))
+
(defvar wttrin--cache (make-hash-table :test 'equal)
"Cache for weather data: cache-key -> (timestamp . data).")
(defvar wttrin--force-refresh nil
"When non-nil, bypass cache on next fetch.")
+(defvar wttrin-mode-line-string nil
+ "Mode-line string showing weather for favorite location.")
+;;;###autoload(put 'wttrin-mode-line-string 'risky-local-variable t)
+(put 'wttrin-mode-line-string 'risky-local-variable t)
+
+(defvar wttrin--mode-line-timer nil
+ "Timer object for mode-line weather refresh.")
+
+(defvar wttrin--mode-line-tooltip-data nil
+ "Cached full weather data for tooltip display.")
+
(defun wttrin-additional-url-params ()
"Concatenates extra information into the URL."
(if wttrin-unit-system
@@ -174,7 +228,7 @@ CALLBACK is called with the weather data string when ready, or nil on error."
map)
"Keymap for wttrin-mode.")
-(define-derived-mode wttrin-mode special-mode "⛅"
+(define-derived-mode wttrin-mode special-mode "Wttrin"
"Major mode for displaying wttr.in weather information.
Weather data is displayed in a read-only buffer with the following keybindings:
@@ -233,7 +287,14 @@ Returns the path to the saved file."
(wttrin-mode)
;; Set location after mode initialization (mode calls kill-all-local-variables)
- (setq-local wttrin--current-location location-name))))
+ (setq-local wttrin--current-location location-name)
+
+ ;; Auto-generate debug diagnostics if debug mode is enabled
+ (when (featurep 'wttrin-debug)
+ (wttrin--debug-mode-line-info))
+
+ ;; Clear any lingering messages from url-retrieve
+ (message nil))))
(defun wttrin-query (location-name)
"Asynchronously query weather of LOCATION-NAME, display result when ready."
@@ -315,6 +376,164 @@ CALLBACK is called with the weather data string when ready, or nil on error."
(wttrin-query wttrin--current-location))
(message "No location to refresh")))
+;;; Mode-line weather display
+
+(defun wttrin--mode-line-fetch-weather ()
+ "Fetch weather for favorite location and update mode-line display.
+Uses wttr.in custom format for concise weather with emoji."
+ (when (featurep 'wttrin-debug)
+ (message "wttrin mode-line: Fetching weather for %s" wttrin-mode-line-favorite-location))
+ (when wttrin-mode-line-favorite-location
+ (let* ((location wttrin-mode-line-favorite-location)
+ ;; Custom format: location + emoji + temp + conditions
+ ;; %l=location, %c=weather emoji, %t=temp, %C=conditions
+ ;; Note: unit system must come BEFORE format in query string
+ (format-params (if wttrin-unit-system
+ (concat "?" wttrin-unit-system "&format=%l:+%c+%t+%C")
+ "?format=%l:+%c+%t+%C"))
+ (url (concat "https://wttr.in/"
+ (url-hexify-string location)
+ format-params))
+ (url-request-extra-headers (list wttrin-default-languages))
+ (url-user-agent "curl"))
+ (when (featurep 'wttrin-debug)
+ (message "wttrin mode-line: URL = %s" url))
+ (url-retrieve
+ url
+ (lambda (status)
+ (let ((data nil))
+ (condition-case err
+ (if (plist-get status :error)
+ (progn
+ (message "wttrin mode-line: Network error - %s"
+ (cdr (plist-get status :error)))
+ (setq data nil))
+ (unwind-protect
+ (progn
+ ;; Skip HTTP headers
+ (goto-char (point-min))
+ (re-search-forward "\r?\n\r?\n" nil t)
+ (setq data (string-trim
+ (decode-coding-string
+ (buffer-substring-no-properties (point) (point-max))
+ 'utf-8)))
+ (when (featurep 'wttrin-debug)
+ (message "wttrin mode-line: Received data = %S" data)))
+ (kill-buffer (current-buffer))))
+ (error
+ (message "wttrin mode-line: Error - %s" (error-message-string err))
+ (setq data nil)))
+ (when data
+ (wttrin--mode-line-update-display data))))))))
+
+(defun wttrin--mode-line-update-display (weather-string)
+ "Update mode-line display with WEATHER-STRING.
+Extracts emoji for mode-line, stores full info for tooltip.
+WEATHER-STRING format: \"Location: emoji temp conditions\" (e.g., \"Paris: ☀️ +61°F Clear\")."
+ (when (featurep 'wttrin-debug)
+ (message "wttrin mode-line: Updating display with: %S" weather-string))
+ ;; Store full weather info for tooltip
+ (setq wttrin--mode-line-tooltip-data weather-string)
+ ;; Extract just the emoji for mode-line display
+ ;; Format is "Location: emoji +temp conditions"
+ ;; We want just the emoji (first character after ": ")
+ (let* ((emoji (if (string-match ":\\s-*\\(.\\)" weather-string)
+ (match-string 1 weather-string)
+ "?")) ; Fallback if parsing fails
+ ;; Force color emoji rendering by setting font family
+ (emoji-with-font (if wttrin-mode-line-emoji-font
+ (propertize emoji
+ 'face (list :family wttrin-mode-line-emoji-font
+ :height 1.0))
+ emoji)))
+ (setq wttrin-mode-line-string
+ (propertize (concat " " emoji-with-font)
+ 'help-echo (lambda (_window _object _pos)
+ (or wttrin--mode-line-tooltip-data
+ (format "Weather for %s\nClick to refresh"
+ wttrin-mode-line-favorite-location)))
+ 'mouse-face 'mode-line-highlight
+ 'local-map (let ((map (make-sparse-keymap)))
+ (define-key map [mode-line mouse-1]
+ 'wttrin-mode-line-click)
+ (define-key map [mode-line mouse-3]
+ 'wttrin-mode-line-force-refresh)
+ map))))
+ (force-mode-line-update t)
+ (when (featurep 'wttrin-debug)
+ (message "wttrin mode-line: Display updated, mode-line-string = %S, tooltip = %S"
+ wttrin-mode-line-string wttrin--mode-line-tooltip-data)))
+
+(defun wttrin-mode-line-click ()
+ "Handle left-click on mode-line weather widget.
+Check cache, refresh if needed, then open weather buffer."
+ (interactive)
+ (when wttrin-mode-line-favorite-location
+ (wttrin wttrin-mode-line-favorite-location)))
+
+(defun wttrin-mode-line-force-refresh ()
+ "Handle right-click on mode-line weather widget.
+Force-refresh cache and update tooltip without opening buffer."
+ (interactive)
+ (when wttrin-mode-line-favorite-location
+ (let ((wttrin--force-refresh t))
+ (wttrin--mode-line-fetch-weather))))
+
+(defun wttrin--mode-line-start ()
+ "Start mode-line weather display and refresh timer."
+ (when (featurep 'wttrin-debug)
+ (message "wttrin mode-line: Starting mode-line display (location=%s, interval=%s)"
+ wttrin-mode-line-favorite-location
+ wttrin-mode-line-refresh-interval))
+ (when wttrin-mode-line-favorite-location
+ ;; Delay initial fetch by 3 seconds to allow network to initialize during startup
+ (run-at-time 3 nil #'wttrin--mode-line-fetch-weather)
+ ;; Set up refresh timer (starts after the interval from now)
+ (when wttrin--mode-line-timer
+ (cancel-timer wttrin--mode-line-timer))
+ (setq wttrin--mode-line-timer
+ (run-at-time wttrin-mode-line-refresh-interval
+ wttrin-mode-line-refresh-interval
+ #'wttrin--mode-line-fetch-weather))
+ (when (featurep 'wttrin-debug)
+ (message "wttrin mode-line: Initial fetch scheduled in 3 seconds, then every %s seconds"
+ wttrin-mode-line-refresh-interval))))
+
+(defun wttrin--mode-line-stop ()
+ "Stop mode-line weather display and cancel timer."
+ (when (featurep 'wttrin-debug)
+ (message "wttrin mode-line: Stopping mode-line display"))
+ (when wttrin--mode-line-timer
+ (cancel-timer wttrin--mode-line-timer)
+ (setq wttrin--mode-line-timer nil))
+ (setq wttrin-mode-line-string nil)
+ (setq wttrin--mode-line-tooltip-data nil)
+ (force-mode-line-update t))
+
+;;;###autoload
+(define-minor-mode wttrin-mode-line-mode
+ "Toggle weather display in mode-line.
+When enabled, shows weather for `wttrin-mode-line-favorite-location'."
+ :global t
+ :lighter (:eval wttrin-mode-line-string)
+ (if wttrin-mode-line-mode
+ (progn
+ (when (featurep 'wttrin-debug)
+ (message "wttrin mode-line: Mode enabled"))
+ (wttrin--mode-line-start)
+ ;; Add modeline string to global-mode-string for custom modelines
+ (if global-mode-string
+ (add-to-list 'global-mode-string 'wttrin-mode-line-string 'append)
+ (setq global-mode-string '("" wttrin-mode-line-string)))
+ (when (featurep 'wttrin-debug)
+ (message "wttrin mode-line: Added to global-mode-string = %S" global-mode-string)))
+ (when (featurep 'wttrin-debug)
+ (message "wttrin mode-line: Mode disabled"))
+ (wttrin--mode-line-stop)
+ ;; Remove from global-mode-string
+ (setq global-mode-string
+ (delq 'wttrin-mode-line-string global-mode-string))))
+
;;;###autoload
(defun wttrin (location)
"Display weather information for LOCATION.