-;;; Versioning
-
-(defun context-coloring-parse-version (string)
- "Extract segments of a version STRING into a list. \"v1.0.0\"
-produces (1 0 0), \"19700101\" produces (19700101), etc."
- (let (version)
- (while (string-match "[0-9]+" string)
- (setq version (append version
- (list (string-to-number (match-string 0 string)))))
- (setq string (substring string (match-end 0))))
- version))
-
-(defun context-coloring-check-version (expected actual)
- "Check that version EXPECTED is less than or equal to ACTUAL."
- (let ((expected (context-coloring-parse-version expected))
- (actual (context-coloring-parse-version actual))
- (continue t)
- (acceptable t))
- (while (and continue expected)
- (let ((an-expected (car expected))
- (an-actual (car actual)))
- (cond
- ((> an-actual an-expected)
- (setq acceptable t)
- (setq continue nil))
- ((< an-actual an-expected)
- (setq acceptable nil)
- (setq continue nil))))
- (setq expected (cdr expected))
- (setq actual (cdr actual)))
- acceptable))
-
-(defvar context-coloring-check-scopifier-version-hook nil
- "Hooks to run after checking the scopifier version.")
-
-(defun context-coloring-check-scopifier-version (&optional callback)
- "Asynchronously invoke CALLBACK with a predicate indicating
-whether the current scopifier version satisfies the minimum
-version number required for the current major mode."
- (let ((dispatch (context-coloring-get-dispatch-for-mode major-mode)))
- (when dispatch
- (let ((version (plist-get dispatch :version))
- (command (plist-get dispatch :command)))
- (context-coloring-shell-command
- (context-coloring-join (list command "--version") " ")
- (lambda (output)
- (cond
- ((context-coloring-check-version version output)
- (when callback (funcall callback t)))
- (t
- (when callback (funcall callback nil))))
- (run-hooks 'context-coloring-check-scopifier-version-hook)))))))
-
-
-;;; Themes
-
-(defvar context-coloring-theme-hash-table (make-hash-table :test #'eq)
- "Map theme names to theme properties.")
-
-(defun context-coloring-theme-p (theme)
- "Return t if THEME is defined, nil otherwise."
- (and (gethash theme context-coloring-theme-hash-table)))
-
-(defconst context-coloring-level-face-regexp
- "context-coloring-level-\\([[:digit:]]+\\)-face"
- "Extract a level from a face.")
-
-(defvar context-coloring-originally-set-theme-hash-table
- (make-hash-table :test #'eq)
- "Cache custom themes who originally set their own
-`context-coloring-level-N-face' faces.")
-
-(defun context-coloring-theme-originally-set-p (theme)
- "Return t if there is a `context-coloring-level-N-face'
-originally set for THEME, nil otherwise."
- (let (originally-set)
- (cond
- ;; `setq' might return a non-nil value for the sake of this `cond'.
- ((setq
- originally-set
- (gethash
- theme
- context-coloring-originally-set-theme-hash-table))
- (eq originally-set 'yes))
- (t
- (let* ((settings (get theme 'theme-settings))
- (tail settings)
- found)
- (while (and tail (not found))
- (and (eq (nth 0 (car tail)) 'theme-face)
- (string-match
- context-coloring-level-face-regexp
- (symbol-name (nth 1 (car tail))))
- (setq found t))
- (setq tail (cdr tail)))
- found)))))
-
-(defun context-coloring-cache-originally-set (theme originally-set)
- "Remember if THEME had colors originally set for it. If
-ORIGINALLY-SET is non-nil, it did, otherwise it didn't."
- ;; Caching whether a theme was originally set is kind of dirty, but we have to
- ;; do it to remember the past state of the theme. There are probably some
- ;; edge cases where caching will be an issue, but they are probably rare.
- (puthash
- theme
- (if originally-set 'yes 'no)
- context-coloring-originally-set-theme-hash-table))
-
-(defun context-coloring-warn-theme-originally-set (theme)
- "Warn the user that the colors for THEME are already originally
-set."
- (warn "Context coloring colors for theme `%s' are already defined" theme))
-
-(defun context-coloring-theme-highest-level (theme)
- "Return the highest level N of a face like
-`context-coloring-level-N-face' set for THEME, or `-1' if there
-is none."
- (let* ((settings (get theme 'theme-settings))
- (tail settings)
- face-string
- number
- (found -1))
- (while tail
- (and (eq (nth 0 (car tail)) 'theme-face)
- (setq face-string (symbol-name (nth 1 (car tail))))
- (string-match
- context-coloring-level-face-regexp
- face-string)
- (setq number (string-to-number
- (substring face-string
- (match-beginning 1)
- (match-end 1))))
- (> number found)
- (setq found number))
- (setq tail (cdr tail)))
- found))
-
-(defun context-coloring-apply-theme (theme)
- "Apply THEME's properties to its respective custom theme,
-which must already exist and which *should* already be enabled."
- (let* ((properties (gethash theme context-coloring-theme-hash-table))
- (colors (plist-get properties :colors))
- (level -1))
- ;; Only clobber when we have to.
- (when (custom-theme-enabled-p theme)
- (setq context-coloring-maximum-face (- (length colors) 1)))
- (apply
- #'custom-theme-set-faces
- theme
- (mapcar
- (lambda (color)
- (setq level (+ level 1))
- `(,(context-coloring-level-face level) ((t (:foreground ,color)))))
- colors))))
-
-(defun context-coloring-define-theme (theme &rest properties)
- "Define a context theme named THEME for coloring scope levels.
-
-PROPERTIES is a property list specifiying the following details:
-
-`:aliases': List of symbols of other custom themes that these
-colors are applicable to.
-
-`:colors': List of colors that this context theme uses.
-
-`:override': If non-nil, this context theme is intentionally
-overriding colors set by a custom theme. Don't set this non-nil
-unless there is a custom theme you want to use which sets
-`context-coloring-level-N-face' faces that you want to replace.
-
-`:recede': If non-nil, this context theme should not apply its
-colors if a custom theme already sets
-`context-coloring-level-N-face' faces. This option is
-optimistic; set this non-nil if you would rather confer the duty
-of picking colors to a custom theme author (if / when he ever
-gets around to it).
-
-By default, context themes will always override custom themes,
-even if those custom themes set `context-coloring-level-N-face'
-faces. If a context theme does override a custom theme, a
-warning will be raised, at which point you may want to enable the
-`:override' option, or just delete your context theme and opt to
-use your custom theme's author's colors instead.
-
-Context themes only work for the custom theme with the highest
-precedence, i.e. the car of `custom-enabled-themes'."
- (let ((aliases (plist-get properties :aliases))
- (override (plist-get properties :override))
- (recede (plist-get properties :recede)))
- (dolist (name (append `(,theme) aliases))
- (puthash name properties context-coloring-theme-hash-table)
- (when (custom-theme-p name)
- (let ((originally-set (context-coloring-theme-originally-set-p name)))
- (context-coloring-cache-originally-set name originally-set)
- ;; In the particular case when you innocently define colors that a
- ;; custom theme originally set, warn. Arguably this only has to be
- ;; done at enable time, but it is probably more useful to do it at
- ;; definition time for prompter feedback.
- (when (and originally-set
- (not recede)
- (not override))
- (context-coloring-warn-theme-originally-set name))
- ;; Set (or overwrite) colors.
- (when (not (and originally-set
- recede))
- (context-coloring-apply-theme name)))))))
-
-(defun context-coloring-enable-theme (theme)
- "Apply THEME if its colors are not already set, else just set
-`context-coloring-maximum-face' to the correct value for THEME."
- (let* ((properties (gethash theme context-coloring-theme-hash-table))
- (recede (plist-get properties :recede))
- (override (plist-get properties :override)))
- (cond
- (recede
- (let ((highest-level (context-coloring-theme-highest-level theme)))
- (cond
- ;; This can be true whether originally set by a custom theme or by a
- ;; context theme.
- ((> highest-level -1)
- (setq context-coloring-maximum-face highest-level))
- ;; It is possible that the corresponding custom theme did not exist at
- ;; the time of defining this context theme, and in that case the above
- ;; condition proves the custom theme did not originally set any faces,
- ;; so we have license to apply the context theme for the first time
- ;; here.
- (t
- (context-coloring-apply-theme theme)))))
- (t
- (let ((originally-set (context-coloring-theme-originally-set-p theme)))
- ;; Cache now in case the context theme was defined after the custom
- ;; theme.
- (context-coloring-cache-originally-set theme originally-set)
- (when (and originally-set
- (not override))
- (context-coloring-warn-theme-originally-set theme))
- (context-coloring-apply-theme theme))))))
-
-(defadvice enable-theme (after context-coloring-enable-theme (theme) activate)
- "Enable colors for context themes just-in-time."
- (when (and (not (eq theme 'user)) ; Called internally by `enable-theme'.
- (custom-theme-p theme) ; Guard against non-existent themes.
- (context-coloring-theme-p theme))
- (when (= (length custom-enabled-themes) 1)
- ;; Cache because we can't reliably figure it out in reverse.
- (setq context-coloring-original-maximum-face
- context-coloring-maximum-face))
- (context-coloring-enable-theme theme)))
-
-(defadvice disable-theme (after context-coloring-disable-theme (theme) activate)
- "Update `context-coloring-maximum-face'."
- (when (custom-theme-p theme) ; Guard against non-existent themes.
- (let ((enabled-theme (car custom-enabled-themes)))
- (cond
- ((context-coloring-theme-p enabled-theme)
- (context-coloring-enable-theme enabled-theme))
- (t
- ;; Assume we are back to no theme; act as if nothing ever happened.
- ;; This is still prone to intervention, but rather extraordinarily.
- (setq context-coloring-maximum-face
- context-coloring-original-maximum-face))))))
-
-(context-coloring-define-theme
- 'ample
- :recede t
- :colors '("#bdbdb3"
- "#baba36"
- "#6aaf50"
- "#5180b3"
- "#ab75c3"
- "#cd7542"
- "#df9522"
- "#454545"))
-
-(context-coloring-define-theme
- 'anti-zenburn
- :recede t
- :colors '("#232333"
- "#6c1f1c"
- "#401440"
- "#0f2050"
- "#205070"
- "#336c6c"
- "#23733c"
- "#6b400c"
- "#603a60"
- "#2f4070"
- "#235c5c"))
-
-(context-coloring-define-theme
- 'grandshell
- :recede t
- :colors '("#bebebe"
- "#5af2ee"
- "#b2baf6"
- "#f09fff"
- "#efc334"
- "#f6df92"
- "#acfb5a"
- "#888888"))
-
-(context-coloring-define-theme
- 'leuven
- :recede t
- :colors '("#333333"
- "#0000ff"
- "#6434a3"
- "#ba36a5"
- "#d0372d"
- "#036a07"
- "#006699"
- "#006fe0"
- "#808080"))
-
-(context-coloring-define-theme
- 'monokai
- :recede t
- :colors '("#f8f8f2"
- "#66d9ef"
- "#a1efe4"
- "#a6e22e"
- "#e6db74"
- "#fd971f"
- "#f92672"
- "#fd5ff0"
- "#ae81ff"))
-
-(context-coloring-define-theme
- 'solarized
- :recede t
- :aliases '(solarized-light
- solarized-dark
- sanityinc-solarized-light
- sanityinc-solarized-dark)
- :colors '("#839496"
- "#268bd2"
- "#2aa198"
- "#859900"
- "#b58900"
- "#cb4b16"
- "#dc322f"
- "#d33682"
- "#6c71c4"
- "#69b7f0"
- "#69cabf"
- "#b4c342"
- "#deb542"
- "#f2804f"
- "#ff6e64"
- "#f771ac"
- "#9ea0e5"))
-
-(context-coloring-define-theme
- 'spacegray
- :recede t
- :colors '("#ffffff"
- "#89aaeb"
- "#c189eb"
- "#bf616a"
- "#dca432"
- "#ebcb8b"
- "#b4eb89"
- "#89ebca"))
-
-(context-coloring-define-theme
- 'tango
- :recede t
- :colors '("#2e3436"
- "#346604"
- "#204a87"
- "#5c3566"
- "#a40000"
- "#b35000"
- "#c4a000"
- "#8ae234"
- "#8cc4ff"
- "#ad7fa8"
- "#ef2929"
- "#fcaf3e"
- "#fce94f"))
-
-(context-coloring-define-theme
- 'zenburn
- :recede t
- :colors '("#dcdccc"
- "#93e0e3"
- "#bfebbf"
- "#f0dfaf"
- "#dfaf8f"
- "#cc9393"
- "#dc8cc3"
- "#94bff3"
- "#9fc59f"
- "#d0bf8f"
- "#dca3a3"))
-
-