summaryrefslogtreecommitdiff
path: root/lisp/my-tinty.el
blob: 3780b26813d64c6fde26bbfded5f16a9002738a7 (plain) (blame)
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
;;; my-tinty.el --- Follow Tinty changes in this Emacs -*- lexical-binding: t; -*-

(require 'filenotify)

(defgroup my/tinty nil
  "Automatic Tinty theme updates."
  :group 'faces)

(defcustom my/tinty-theme-file
  (expand-file-name "themes/tinty-theme.el" user-emacs-directory)
  "Shared theme source to watch in every Emacs process."
  :type 'file
  :group 'my/tinty)

(defcustom my/tinty-reload-delay 0.15
  "Seconds to combine filesystem events before loading the theme."
  :type 'number
  :group 'my/tinty)

(defvar my/tinty-auto-reload-mode nil)
(defvar my/tinty--watch nil)
(defvar my/tinty--timer nil)
(defvar my/tinty--loaded-content nil)

(defun my/tinty--reload ()
  "Reload only changed theme content, without success messages."
  (setq my/tinty--timer nil)
  (when (and my/tinty-auto-reload-mode (file-readable-p my/tinty-theme-file))
    (condition-case err
        (let* ((content (with-temp-buffer
                          (insert-file-contents-literally my/tinty-theme-file)
                          (secure-hash 'sha256 (current-buffer))))
               (inhibit-message t)
               (message-log-max nil))
          (unless (equal content my/tinty--loaded-content)
            (load my/tinty-theme-file nil 'nomessage 'nosuffix)
            (enable-theme 'tinty)
            (setq my/tinty--loaded-content content)))
      (error
       (let ((inhibit-message t))
         (message "Tinty theme: %s" (error-message-string err)))))))

(defun my/tinty--watch-start ()
  "Watch the theme directory, retrying if it is temporarily unavailable."
  (setq my/tinty--timer nil)
  (when my/tinty-auto-reload-mode
    (condition-case nil
        (unless (and my/tinty--watch (file-notify-valid-p my/tinty--watch))
          (setq my/tinty--watch
                (file-notify-add-watch
                 (file-name-directory (expand-file-name my/tinty-theme-file))
                 '(change attribute-change) #'my/tinty--changed))
          (my/tinty--reload))
      (file-notify-error
       (setq my/tinty--timer (run-at-time 2 nil #'my/tinty--watch-start))))))

(defun my/tinty--changed (event)
  "React only to events for the theme file or a stopped directory watch."
  (when (and my/tinty-auto-reload-mode (equal (car event) my/tinty--watch))
    (cond
     ((eq (cadr event) 'stopped)
      (setq my/tinty--watch nil)
      (when (timerp my/tinty--timer) (cancel-timer my/tinty--timer))
      (setq my/tinty--timer (run-at-time 2 nil #'my/tinty--watch-start)))
     ((member (expand-file-name my/tinty-theme-file) (cddr event))
      (when (timerp my/tinty--timer) (cancel-timer my/tinty--timer))
      (setq my/tinty--timer
            (run-at-time my/tinty-reload-delay nil #'my/tinty--reload))))))

;;;###autoload
(define-minor-mode my/tinty-auto-reload-mode
  "Keep this Emacs's theme synchronized with Tinty, independently of config reloads."
  :global t
  :group 'my/tinty
  (if my/tinty-auto-reload-mode
      (unless (and my/tinty--watch (file-notify-valid-p my/tinty--watch))
        (when (timerp my/tinty--timer) (cancel-timer my/tinty--timer))
        (my/tinty--watch-start))
    (when (timerp my/tinty--timer) (cancel-timer my/tinty--timer))
    (let ((watch my/tinty--watch))
      (setq my/tinty--timer nil my/tinty--watch nil my/tinty--loaded-content nil)
      (when watch (file-notify-rm-watch watch)))))

(provide 'my-tinty)
;;; my-tinty.el ends here