| 1 | ;;; cus-theme.el -- custom theme creation user interface |
| 2 | ;; |
| 3 | ;; Copyright (C) 2001-2011 Free Software Foundation, Inc. |
| 4 | ;; |
| 5 | ;; Author: Alex Schroeder <alex@gnu.org> |
| 6 | ;; Maintainer: FSF |
| 7 | ;; Keywords: help, faces |
| 8 | ;; Package: emacs |
| 9 | |
| 10 | ;; This file is part of GNU Emacs. |
| 11 | |
| 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
| 13 | ;; it under the terms of the GNU General Public License as published by |
| 14 | ;; the Free Software Foundation, either version 3 of the License, or |
| 15 | ;; (at your option) any later version. |
| 16 | |
| 17 | ;; GNU Emacs is distributed in the hope that it will be useful, |
| 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 20 | ;; GNU General Public License for more details. |
| 21 | |
| 22 | ;; You should have received a copy of the GNU General Public License |
| 23 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
| 24 | |
| 25 | ;;; Code: |
| 26 | |
| 27 | (require 'widget) |
| 28 | (require 'cus-edit) |
| 29 | |
| 30 | (eval-when-compile |
| 31 | (require 'wid-edit)) |
| 32 | |
| 33 | (defvar custom-new-theme-mode-map |
| 34 | (let ((map (make-keymap))) |
| 35 | (set-keymap-parent map widget-keymap) |
| 36 | (suppress-keymap map) |
| 37 | (define-key map "\C-x\C-s" 'custom-theme-write) |
| 38 | (define-key map "n" 'widget-forward) |
| 39 | (define-key map "p" 'widget-backward) |
| 40 | map) |
| 41 | "Keymap for `custom-new-theme-mode'.") |
| 42 | |
| 43 | (define-derived-mode custom-new-theme-mode nil "Custom-Theme" |
| 44 | "Major mode for editing Custom themes. |
| 45 | Do not call this mode function yourself. It is meant for internal use." |
| 46 | (use-local-map custom-new-theme-mode-map) |
| 47 | (custom--initialize-widget-variables) |
| 48 | (set (make-local-variable 'revert-buffer-function) 'custom-theme-revert)) |
| 49 | (put 'custom-new-theme-mode 'mode-class 'special) |
| 50 | |
| 51 | (defvar custom-theme-name nil) |
| 52 | ;; Each element has the form (VAR CHECKBOX-WIDGET VAR-WIDGET) |
| 53 | (defvar custom-theme-variables nil) |
| 54 | ;; Each element has the form (FACE CHECKBOX-WIDGET FACE-WIDGET) |
| 55 | (defvar custom-theme-faces nil) |
| 56 | (defvar custom-theme-description nil) |
| 57 | (defvar custom-theme--migrate-settings nil) |
| 58 | (defvar custom-theme-insert-variable-marker nil) |
| 59 | (defvar custom-theme-insert-face-marker nil) |
| 60 | |
| 61 | (defvar custom-theme--listed-faces '(default cursor fixed-pitch |
| 62 | variable-pitch escape-glyph minibuffer-prompt highlight region |
| 63 | shadow secondary-selection trailing-whitespace |
| 64 | font-lock-builtin-face font-lock-comment-delimiter-face |
| 65 | font-lock-comment-face font-lock-constant-face |
| 66 | font-lock-doc-face font-lock-function-name-face |
| 67 | font-lock-keyword-face font-lock-negation-char-face |
| 68 | font-lock-preprocessor-face font-lock-regexp-grouping-backslash |
| 69 | font-lock-regexp-grouping-construct font-lock-string-face |
| 70 | font-lock-type-face font-lock-variable-name-face |
| 71 | font-lock-warning-face button link link-visited fringe |
| 72 | header-line tooltip mode-line mode-line-buffer-id |
| 73 | mode-line-emphasis mode-line-highlight mode-line-inactive |
| 74 | isearch isearch-fail lazy-highlight match next-error |
| 75 | query-replace) |
| 76 | "Faces listed by default in the *Custom Theme* buffer.") |
| 77 | |
| 78 | (defvar custom-theme--save-name) |
| 79 | |
| 80 | ;;;###autoload |
| 81 | (defun customize-create-theme (&optional theme buffer) |
| 82 | "Create or edit a custom theme. |
| 83 | THEME, if non-nil, should be an existing theme to edit. If THEME |
| 84 | is `user', provide an option to remove these as custom settings. |
| 85 | BUFFER, if non-nil, should be a buffer to use; the default is |
| 86 | named *Custom Theme*." |
| 87 | (interactive) |
| 88 | (switch-to-buffer (get-buffer-create (or buffer "*Custom Theme*"))) |
| 89 | (let ((inhibit-read-only t)) |
| 90 | (erase-buffer) |
| 91 | (dolist (ov (overlays-in (point-min) (point-max))) |
| 92 | (delete-overlay ov))) |
| 93 | (custom-new-theme-mode) |
| 94 | (make-local-variable 'custom-theme-name) |
| 95 | (set (make-local-variable 'custom-theme--save-name) theme) |
| 96 | (set (make-local-variable 'custom-theme-faces) nil) |
| 97 | (set (make-local-variable 'custom-theme-variables) nil) |
| 98 | (set (make-local-variable 'custom-theme-description) "") |
| 99 | (set (make-local-variable 'custom-theme--migrate-settings) nil) |
| 100 | (make-local-variable 'custom-theme-insert-face-marker) |
| 101 | (make-local-variable 'custom-theme-insert-variable-marker) |
| 102 | (make-local-variable 'custom-theme--listed-faces) |
| 103 | (when (called-interactively-p 'interactive) |
| 104 | (unless (y-or-n-p "Include basic face customizations in this theme? ") |
| 105 | (setq custom-theme--listed-faces nil))) |
| 106 | |
| 107 | (if (eq theme 'user) |
| 108 | (widget-insert "This buffer contains all the Custom settings you have made. |
| 109 | You can convert them into a new custom theme, and optionally |
| 110 | remove them from your saved Custom file.\n\n")) |
| 111 | |
| 112 | (widget-create 'push-button |
| 113 | :tag " Visit Theme " |
| 114 | :help-echo "Insert the settings of a pre-defined theme." |
| 115 | :action (lambda (_widget &optional _event) |
| 116 | (call-interactively 'custom-theme-visit-theme))) |
| 117 | (widget-insert " ") |
| 118 | (widget-create 'push-button |
| 119 | :tag " Merge Theme " |
| 120 | :help-echo "Merge in the settings of a pre-defined theme." |
| 121 | :action (lambda (_widget &optional _event) |
| 122 | (call-interactively 'custom-theme-merge-theme))) |
| 123 | (widget-insert " ") |
| 124 | (widget-create 'push-button |
| 125 | :tag " Revert " |
| 126 | :help-echo "Revert this buffer to its original state." |
| 127 | :action (lambda (&rest ignored) (revert-buffer))) |
| 128 | |
| 129 | (widget-insert "\n\nTheme name : ") |
| 130 | (setq custom-theme-name |
| 131 | (widget-create 'editable-field |
| 132 | :value (if (and theme (not (eq theme 'user))) |
| 133 | (symbol-name theme) |
| 134 | ""))) |
| 135 | (widget-insert "Description: ") |
| 136 | (setq custom-theme-description |
| 137 | (widget-create 'text |
| 138 | :value (format-time-string "Created %Y-%m-%d."))) |
| 139 | (widget-create 'push-button |
| 140 | :notify (function custom-theme-write) |
| 141 | " Save Theme ") |
| 142 | (when (eq theme 'user) |
| 143 | (setq custom-theme--migrate-settings t) |
| 144 | (widget-insert " ") |
| 145 | (widget-create 'checkbox |
| 146 | :value custom-theme--migrate-settings |
| 147 | :action (lambda (widget &optional event) |
| 148 | (when (widget-value widget) |
| 149 | (widget-toggle-action widget event) |
| 150 | (setq custom-theme--migrate-settings |
| 151 | (widget-value widget))))) |
| 152 | (widget-insert (propertize " Remove saved theme settings from Custom save file." |
| 153 | 'face '(variable-pitch (:height 0.9))))) |
| 154 | |
| 155 | (let (vars values faces face-specs) |
| 156 | |
| 157 | ;; Load the theme settings. |
| 158 | (when theme |
| 159 | (unless (eq theme 'user) |
| 160 | (load-theme theme t)) |
| 161 | (dolist (setting (get theme 'theme-settings)) |
| 162 | (if (eq (car setting) 'theme-value) |
| 163 | (progn (push (nth 1 setting) vars) |
| 164 | (push (nth 3 setting) values)) |
| 165 | (push (nth 1 setting) faces) |
| 166 | (push (nth 3 setting) face-specs)))) |
| 167 | |
| 168 | ;; If THEME is non-nil, insert all of that theme's faces. |
| 169 | ;; Otherwise, insert those in `custom-theme--listed-faces'. |
| 170 | (widget-insert "\n\n Theme faces:\n ") |
| 171 | (if theme |
| 172 | (while faces |
| 173 | (custom-theme-add-face-1 (pop faces) (pop face-specs))) |
| 174 | (dolist (face custom-theme--listed-faces) |
| 175 | (custom-theme-add-face-1 face nil))) |
| 176 | (setq custom-theme-insert-face-marker (point-marker)) |
| 177 | (widget-insert " ") |
| 178 | (widget-create 'push-button |
| 179 | :tag "Insert Additional Face" |
| 180 | :help-echo "Add another face to this theme." |
| 181 | :follow-link 'mouse-face |
| 182 | :button-face 'custom-link |
| 183 | :mouse-face 'highlight |
| 184 | :pressed-face 'highlight |
| 185 | :action (lambda (_widget &optional _event) |
| 186 | (call-interactively 'custom-theme-add-face))) |
| 187 | |
| 188 | ;; If THEME is non-nil, insert all of that theme's variables. |
| 189 | (widget-insert "\n\n Theme variables:\n ") |
| 190 | (if theme |
| 191 | (while vars |
| 192 | (if (eq (car vars) 'custom-enabled-themes) |
| 193 | (progn (pop vars) (pop values)) |
| 194 | (custom-theme-add-var-1 (pop vars) (eval (pop values)))))) |
| 195 | (setq custom-theme-insert-variable-marker (point-marker)) |
| 196 | (widget-insert " ") |
| 197 | (widget-create 'push-button |
| 198 | :tag "Insert Variable" |
| 199 | :help-echo "Add another variable to this theme." |
| 200 | :follow-link 'mouse-face |
| 201 | :button-face 'custom-link |
| 202 | :mouse-face 'highlight |
| 203 | :pressed-face 'highlight |
| 204 | :action (lambda (_widget &optional _event) |
| 205 | (call-interactively 'custom-theme-add-variable))) |
| 206 | (widget-insert ?\n) |
| 207 | (widget-setup) |
| 208 | (goto-char (point-min)) |
| 209 | (message ""))) |
| 210 | |
| 211 | (defun custom-theme-revert (_ignore-auto noconfirm) |
| 212 | (when (or noconfirm (y-or-n-p "Discard current changes? ")) |
| 213 | (customize-create-theme custom-theme--save-name (current-buffer)))) |
| 214 | |
| 215 | ;;; Theme variables |
| 216 | |
| 217 | (defun custom-theme-add-variable (var value) |
| 218 | "Add a widget for VAR (a symbol) to the *New Custom Theme* buffer. |
| 219 | VALUE should be a value to which to set the widget; when called |
| 220 | interactively, this defaults to the current value of VAR." |
| 221 | (interactive |
| 222 | (let ((v (read-variable "Variable name: "))) |
| 223 | (list v (symbol-value v)))) |
| 224 | (let ((entry (assq var custom-theme-variables))) |
| 225 | (cond ((null entry) |
| 226 | ;; If VAR is not yet in the buffer, add it. |
| 227 | (save-excursion |
| 228 | (goto-char custom-theme-insert-variable-marker) |
| 229 | (custom-theme-add-var-1 var value) |
| 230 | (move-marker custom-theme-insert-variable-marker (point)) |
| 231 | (widget-setup))) |
| 232 | ;; Otherwise, alter that var widget. |
| 233 | (t |
| 234 | (widget-value-set (nth 1 entry) t) |
| 235 | (let ((widget (nth 2 entry))) |
| 236 | (widget-put widget :shown-value (list value)) |
| 237 | (custom-redraw widget)))))) |
| 238 | |
| 239 | (defun custom-theme-add-var-1 (symbol val) |
| 240 | (widget-insert " ") |
| 241 | (push (list symbol |
| 242 | (prog1 (widget-create 'checkbox |
| 243 | :value t |
| 244 | :help-echo "Enable/disable this variable.") |
| 245 | (widget-insert " ")) |
| 246 | (widget-create 'custom-variable |
| 247 | :tag (custom-unlispify-tag-name symbol) |
| 248 | :value symbol |
| 249 | :shown-value (list val) |
| 250 | :notify 'ignore |
| 251 | :custom-level 0 |
| 252 | :custom-state 'hidden |
| 253 | :custom-style 'simple)) |
| 254 | custom-theme-variables) |
| 255 | (widget-insert " ")) |
| 256 | |
| 257 | ;;; Theme faces |
| 258 | |
| 259 | (defun custom-theme-add-face (face &optional spec) |
| 260 | "Add a widget for FACE (a symbol) to the *New Custom Theme* buffer. |
| 261 | SPEC, if non-nil, should be a face spec to which to set the widget." |
| 262 | (interactive (list (read-face-name "Face name" nil nil) nil)) |
| 263 | (unless (or (facep face) spec) |
| 264 | (error "`%s' has no face definition" face)) |
| 265 | (let ((entry (assq face custom-theme-faces))) |
| 266 | (cond ((null entry) |
| 267 | ;; If FACE is not yet in the buffer, add it. |
| 268 | (save-excursion |
| 269 | (goto-char custom-theme-insert-face-marker) |
| 270 | (custom-theme-add-face-1 face spec) |
| 271 | (move-marker custom-theme-insert-face-marker (point)) |
| 272 | (widget-setup))) |
| 273 | ;; Otherwise, if SPEC is supplied, alter that face widget. |
| 274 | (spec |
| 275 | (widget-value-set (nth 1 entry) t) |
| 276 | (let ((widget (nth 2 entry))) |
| 277 | (widget-put widget :shown-value spec) |
| 278 | (custom-redraw widget))) |
| 279 | ((called-interactively-p 'interactive) |
| 280 | (error "`%s' is already present" face))))) |
| 281 | |
| 282 | (defun custom-theme-add-face-1 (symbol spec) |
| 283 | (widget-insert " ") |
| 284 | (push (list symbol |
| 285 | (prog1 |
| 286 | (widget-create 'checkbox |
| 287 | :value t |
| 288 | :help-echo "Enable/disable this face.") |
| 289 | (widget-insert " ")) |
| 290 | (widget-create 'custom-face |
| 291 | :tag (custom-unlispify-tag-name symbol) |
| 292 | :documentation-shown t |
| 293 | :value symbol |
| 294 | :custom-state 'hidden |
| 295 | :custom-style 'simple |
| 296 | :shown-value spec |
| 297 | :sample-indent 34)) |
| 298 | custom-theme-faces) |
| 299 | (widget-insert " ")) |
| 300 | |
| 301 | ;;; Reading and writing |
| 302 | |
| 303 | ;;;###autoload |
| 304 | (defun custom-theme-visit-theme (theme) |
| 305 | "Set up a Custom buffer to edit custom theme THEME." |
| 306 | (interactive |
| 307 | (list |
| 308 | (intern (completing-read "Find custom theme: " |
| 309 | (mapcar 'symbol-name |
| 310 | (custom-available-themes)))))) |
| 311 | (unless (custom-theme-name-valid-p theme) |
| 312 | (error "No valid theme named `%s'" theme)) |
| 313 | (cond ((not (eq major-mode 'custom-new-theme-mode)) |
| 314 | (customize-create-theme theme)) |
| 315 | ((y-or-n-p "Discard current changes? ") |
| 316 | (setq custom-theme--save-name theme) |
| 317 | (custom-theme-revert nil t)))) |
| 318 | |
| 319 | (defun custom-theme-merge-theme (theme) |
| 320 | "Merge the custom theme THEME's settings into the current buffer." |
| 321 | (interactive |
| 322 | (list |
| 323 | (intern (completing-read "Merge custom theme: " |
| 324 | (mapcar 'symbol-name |
| 325 | (custom-available-themes)))))) |
| 326 | (unless (eq theme 'user) |
| 327 | (unless (custom-theme-name-valid-p theme) |
| 328 | (error "Invalid theme name `%s'" theme)) |
| 329 | (load-theme theme t)) |
| 330 | (let ((settings (reverse (get theme 'theme-settings)))) |
| 331 | (dolist (setting settings) |
| 332 | (funcall (if (eq (car setting) 'theme-value) |
| 333 | 'custom-theme-add-variable |
| 334 | 'custom-theme-add-face) |
| 335 | (nth 1 setting) |
| 336 | (nth 3 setting)))) |
| 337 | theme) |
| 338 | |
| 339 | ;; From cus-edit.el |
| 340 | (defvar custom-reset-standard-faces-list) |
| 341 | (defvar custom-reset-standard-variables-list) |
| 342 | |
| 343 | (defun custom-theme-write (&rest _ignore) |
| 344 | "Write the current custom theme to its theme file." |
| 345 | (interactive) |
| 346 | (let* ((name (widget-value custom-theme-name)) |
| 347 | (doc (widget-value custom-theme-description)) |
| 348 | (vars custom-theme-variables) |
| 349 | (faces custom-theme-faces) |
| 350 | filename) |
| 351 | (when (string-equal name "") |
| 352 | (setq name (read-from-minibuffer "Theme name: " (user-login-name))) |
| 353 | (widget-value-set custom-theme-name name)) |
| 354 | (unless (custom-theme-name-valid-p (intern name)) |
| 355 | (error "Custom themes cannot be named `%s'" name)) |
| 356 | |
| 357 | (setq filename (expand-file-name (concat name "-theme.el") |
| 358 | custom-theme-directory)) |
| 359 | (and (file-exists-p filename) |
| 360 | (not (y-or-n-p (format "File %s exists. Overwrite? " filename))) |
| 361 | (error "Aborted")) |
| 362 | |
| 363 | (with-temp-buffer |
| 364 | (emacs-lisp-mode) |
| 365 | (unless (file-directory-p custom-theme-directory) |
| 366 | (make-directory (file-name-as-directory custom-theme-directory) t)) |
| 367 | (setq buffer-file-name filename) |
| 368 | (erase-buffer) |
| 369 | (insert "(deftheme " name) |
| 370 | (if doc (insert "\n \"" doc "\"")) |
| 371 | (insert ")\n") |
| 372 | (custom-theme-write-variables name (reverse vars)) |
| 373 | (custom-theme-write-faces name (reverse faces)) |
| 374 | (insert "\n(provide-theme '" name ")\n") |
| 375 | (save-buffer)) |
| 376 | (message "Theme written to %s" filename) |
| 377 | |
| 378 | (when custom-theme--migrate-settings |
| 379 | ;; Remove these settings from the Custom file. |
| 380 | (let ((custom-reset-standard-variables-list '(t)) |
| 381 | (custom-reset-standard-faces-list '(t))) |
| 382 | (dolist (var vars) |
| 383 | (when (and (not (eq (car var) 'custom-enabled-themes)) |
| 384 | (widget-get (nth 1 var) :value)) |
| 385 | (widget-apply (nth 2 var) :custom-mark-to-reset-standard))) |
| 386 | (dolist (face faces) |
| 387 | (when (widget-get (nth 1 face) :value) |
| 388 | (widget-apply (nth 2 face) :custom-mark-to-reset-standard))) |
| 389 | (custom-save-all)) |
| 390 | (let ((custom-theme-load-path (list 'custom-theme-directory))) |
| 391 | (load-theme (intern name)))))) |
| 392 | |
| 393 | (defun custom-theme-write-variables (theme vars) |
| 394 | "Write a `custom-theme-set-variables' command for THEME. |
| 395 | It includes all variables in list VARS." |
| 396 | (when vars |
| 397 | (let ((standard-output (current-buffer))) |
| 398 | (princ "\n(custom-theme-set-variables\n") |
| 399 | (princ " '") |
| 400 | (princ theme) |
| 401 | (princ "\n") |
| 402 | (dolist (spec vars) |
| 403 | (when (widget-get (nth 1 spec) :value) |
| 404 | (let* ((symbol (nth 0 spec)) |
| 405 | (widget (nth 2 spec)) |
| 406 | (child (car-safe (widget-get widget :children))) |
| 407 | (value (if child |
| 408 | (widget-value child) |
| 409 | ;; Child is null if the widget is closed (hidden). |
| 410 | (car (widget-get widget :shown-value))))) |
| 411 | (when (boundp symbol) |
| 412 | (unless (bolp) |
| 413 | (princ "\n")) |
| 414 | (princ " '(") |
| 415 | (prin1 symbol) |
| 416 | (princ " ") |
| 417 | (prin1 (custom-quote value)) |
| 418 | (princ ")"))))) |
| 419 | (if (bolp) |
| 420 | (princ " ")) |
| 421 | (princ ")") |
| 422 | (unless (looking-at "\n") |
| 423 | (princ "\n"))))) |
| 424 | |
| 425 | (defun custom-theme-write-faces (theme faces) |
| 426 | "Write a `custom-theme-set-faces' command for THEME. |
| 427 | It includes all faces in list FACES." |
| 428 | (when faces |
| 429 | (let ((standard-output (current-buffer))) |
| 430 | (princ "\n(custom-theme-set-faces\n") |
| 431 | (princ " '") |
| 432 | (princ theme) |
| 433 | (princ "\n") |
| 434 | (dolist (spec faces) |
| 435 | (when (widget-get (nth 1 spec) :value) |
| 436 | (let* ((symbol (nth 0 spec)) |
| 437 | (widget (nth 2 spec)) |
| 438 | (value |
| 439 | (if (car-safe (widget-get widget :children)) |
| 440 | (custom-face-widget-to-spec widget) |
| 441 | ;; Child is null if the widget is closed (hidden). |
| 442 | (widget-get widget :shown-value)))) |
| 443 | (when (and (facep symbol) value) |
| 444 | (princ (if (bolp) " '(" "\n '(")) |
| 445 | (prin1 symbol) |
| 446 | (princ " ") |
| 447 | (prin1 value) |
| 448 | (princ ")"))))) |
| 449 | (if (bolp) (princ " ")) |
| 450 | (princ ")") |
| 451 | (unless (looking-at "\n") |
| 452 | (princ "\n"))))) |
| 453 | |
| 454 | \f |
| 455 | ;;; Describing Custom themes. |
| 456 | |
| 457 | ;;;###autoload |
| 458 | (defun describe-theme (theme) |
| 459 | "Display a description of the Custom theme THEME (a symbol)." |
| 460 | (interactive |
| 461 | (list |
| 462 | (intern (completing-read "Describe custom theme: " |
| 463 | (mapcar 'symbol-name |
| 464 | (custom-available-themes)))))) |
| 465 | (unless (custom-theme-name-valid-p theme) |
| 466 | (error "Invalid theme name `%s'" theme)) |
| 467 | (help-setup-xref (list 'describe-theme theme) |
| 468 | (called-interactively-p 'interactive)) |
| 469 | (with-help-window (help-buffer) |
| 470 | (with-current-buffer standard-output |
| 471 | (describe-theme-1 theme)))) |
| 472 | |
| 473 | (defun describe-theme-1 (theme) |
| 474 | (prin1 theme) |
| 475 | (princ " is a custom theme") |
| 476 | (let ((fn (locate-file (concat (symbol-name theme) "-theme.el") |
| 477 | (custom-theme--load-path) |
| 478 | '("" "c"))) |
| 479 | doc) |
| 480 | (when fn |
| 481 | (princ " in `") |
| 482 | (help-insert-xref-button (file-name-nondirectory fn) |
| 483 | 'help-theme-def fn) |
| 484 | (princ "'")) |
| 485 | (princ ".\n") |
| 486 | (if (custom-theme-p theme) |
| 487 | (progn |
| 488 | (if (custom-theme-enabled-p theme) |
| 489 | (princ "It is loaded and enabled.") |
| 490 | (princ "It is loaded but disabled.")) |
| 491 | (setq doc (get theme 'theme-documentation))) |
| 492 | (princ "It is not loaded.") |
| 493 | ;; Attempt to grab the theme documentation |
| 494 | (when fn |
| 495 | (with-temp-buffer |
| 496 | (insert-file-contents fn) |
| 497 | (let ((sexp (let ((read-circle nil)) |
| 498 | (condition-case nil |
| 499 | (read (current-buffer)) |
| 500 | (end-of-file nil))))) |
| 501 | (and sexp (listp sexp) |
| 502 | (eq (car sexp) 'deftheme) |
| 503 | (setq doc (nth 2 sexp))))))) |
| 504 | (princ "\n\nDocumentation:\n") |
| 505 | (princ (if (stringp doc) |
| 506 | doc |
| 507 | "No documentation available."))) |
| 508 | (princ "\n\nYou can ") |
| 509 | (help-insert-xref-button "customize" 'help-theme-edit theme) |
| 510 | (princ " this theme.")) |
| 511 | |
| 512 | \f |
| 513 | ;;; Theme chooser |
| 514 | |
| 515 | (defvar custom--listed-themes) |
| 516 | |
| 517 | (defcustom custom-theme-allow-multiple-selections nil |
| 518 | "Whether to allow multi-selections in the *Custom Themes* buffer." |
| 519 | :type 'boolean |
| 520 | :group 'custom-buffer) |
| 521 | |
| 522 | (defvar custom-theme-choose-mode-map |
| 523 | (let ((map (make-keymap))) |
| 524 | (set-keymap-parent map widget-keymap) |
| 525 | (suppress-keymap map) |
| 526 | (define-key map "\C-x\C-s" 'custom-theme-save) |
| 527 | (define-key map "n" 'widget-forward) |
| 528 | (define-key map "p" 'widget-backward) |
| 529 | (define-key map "?" 'custom-describe-theme) |
| 530 | map) |
| 531 | "Keymap for `custom-theme-choose-mode'.") |
| 532 | |
| 533 | (define-derived-mode custom-theme-choose-mode nil "Themes" |
| 534 | "Major mode for selecting Custom themes. |
| 535 | Do not call this mode function yourself. It is meant for internal use." |
| 536 | (use-local-map custom-theme-choose-mode-map) |
| 537 | (custom--initialize-widget-variables) |
| 538 | (set (make-local-variable 'revert-buffer-function) |
| 539 | (lambda (_ignore-auto noconfirm) |
| 540 | (when (or noconfirm (y-or-n-p "Discard current choices? ")) |
| 541 | (customize-themes (current-buffer)))))) |
| 542 | (put 'custom-theme-choose-mode 'mode-class 'special) |
| 543 | |
| 544 | ;;;###autoload |
| 545 | (defun customize-themes (&optional buffer) |
| 546 | "Display a selectable list of Custom themes. |
| 547 | When called from Lisp, BUFFER should be the buffer to use; if |
| 548 | omitted, a buffer named *Custom Themes* is used." |
| 549 | (interactive) |
| 550 | (switch-to-buffer (get-buffer-create (or buffer "*Custom Themes*"))) |
| 551 | (let ((inhibit-read-only t)) |
| 552 | (erase-buffer)) |
| 553 | (custom-theme-choose-mode) |
| 554 | (set (make-local-variable 'custom--listed-themes) nil) |
| 555 | (make-local-variable 'custom-theme-allow-multiple-selections) |
| 556 | (and (null custom-theme-allow-multiple-selections) |
| 557 | (> (length custom-enabled-themes) 1) |
| 558 | (setq custom-theme-allow-multiple-selections t)) |
| 559 | |
| 560 | (widget-insert |
| 561 | (substitute-command-keys |
| 562 | "Type RET or click to enable/disable listed custom themes. |
| 563 | Type \\[custom-describe-theme] to describe the theme at point. |
| 564 | Theme files are named *-theme.el in `")) |
| 565 | (widget-create 'link :value "custom-theme-load-path" |
| 566 | :button-face 'custom-link |
| 567 | :mouse-face 'highlight |
| 568 | :pressed-face 'highlight |
| 569 | :help-echo "Describe `custom-theme-load-path'." |
| 570 | :keymap custom-mode-link-map |
| 571 | :follow-link 'mouse-face |
| 572 | :action (lambda (_widget &rest _ignore) |
| 573 | (describe-variable 'custom-theme-load-path))) |
| 574 | (widget-insert "'.\n\n") |
| 575 | |
| 576 | ;; If the user has made customizations, display a warning and |
| 577 | ;; provide buttons to disable or convert them. |
| 578 | (let ((user-settings (get 'user 'theme-settings))) |
| 579 | (unless (or (null user-settings) |
| 580 | (and (null (cdr user-settings)) |
| 581 | (eq (caar user-settings) 'theme-value) |
| 582 | (eq (cadr (car user-settings)) 'custom-enabled-themes))) |
| 583 | (widget-insert |
| 584 | (propertize |
| 585 | " Note: Your custom settings take precedence over theme settings. |
| 586 | To migrate your settings into a theme, click " |
| 587 | 'face 'font-lock-warning-face)) |
| 588 | (widget-create 'link :value "here" |
| 589 | :button-face 'custom-link |
| 590 | :mouse-face 'highlight |
| 591 | :pressed-face 'highlight |
| 592 | :help-echo "Migrate." |
| 593 | :keymap custom-mode-link-map |
| 594 | :follow-link 'mouse-face |
| 595 | :action (lambda (_widget &rest _ignore) |
| 596 | (customize-create-theme 'user))) |
| 597 | (widget-insert ".\n\n"))) |
| 598 | |
| 599 | (widget-create 'push-button |
| 600 | :tag " Save Theme Settings " |
| 601 | :help-echo "Save the selected themes for future sessions." |
| 602 | :action 'custom-theme-save) |
| 603 | (widget-insert ?\n) |
| 604 | (widget-create 'checkbox |
| 605 | :value custom-theme-allow-multiple-selections |
| 606 | :action 'custom-theme-selections-toggle) |
| 607 | (widget-insert (propertize " Select more than one theme at a time" |
| 608 | 'face '(variable-pitch (:height 0.9)))) |
| 609 | |
| 610 | (widget-insert "\n\nAvailable Custom Themes:\n") |
| 611 | (let ((help-echo "mouse-2: Enable this theme for this session") |
| 612 | widget) |
| 613 | (dolist (theme (custom-available-themes)) |
| 614 | (setq widget (widget-create 'checkbox |
| 615 | :value (custom-theme-enabled-p theme) |
| 616 | :theme-name theme |
| 617 | :help-echo help-echo |
| 618 | :action 'custom-theme-checkbox-toggle)) |
| 619 | (push (cons theme widget) custom--listed-themes) |
| 620 | (widget-create-child-and-convert widget 'push-button |
| 621 | :button-face-get 'ignore |
| 622 | :mouse-face-get 'ignore |
| 623 | :value (format " %s" theme) |
| 624 | :action 'widget-parent-action |
| 625 | :help-echo help-echo) |
| 626 | (widget-insert " -- " |
| 627 | (propertize (custom-theme-summary theme) |
| 628 | 'face 'shadow) |
| 629 | ?\n))) |
| 630 | (goto-char (point-min)) |
| 631 | (widget-setup)) |
| 632 | |
| 633 | (defun custom-theme-summary (theme) |
| 634 | "Return the summary line of THEME." |
| 635 | (let (doc) |
| 636 | (if (custom-theme-p theme) |
| 637 | (setq doc (get theme 'theme-documentation)) |
| 638 | (let ((fn (locate-file (concat (symbol-name theme) "-theme.el") |
| 639 | (custom-theme--load-path) |
| 640 | '("" "c")))) |
| 641 | (when fn |
| 642 | (with-temp-buffer |
| 643 | (insert-file-contents fn) |
| 644 | (let ((sexp (let ((read-circle nil)) |
| 645 | (condition-case nil |
| 646 | (read (current-buffer)) |
| 647 | (end-of-file nil))))) |
| 648 | (and sexp (listp sexp) |
| 649 | (eq (car sexp) 'deftheme) |
| 650 | (setq doc (nth 2 sexp)))))))) |
| 651 | (cond ((null doc) |
| 652 | "(no documentation available)") |
| 653 | ((string-match ".*" doc) |
| 654 | (match-string 0 doc)) |
| 655 | (t doc)))) |
| 656 | |
| 657 | (defun custom-theme-checkbox-toggle (widget &optional event) |
| 658 | (let ((this-theme (widget-get widget :theme-name))) |
| 659 | (if (widget-value widget) |
| 660 | ;; Disable the theme. |
| 661 | (progn |
| 662 | (disable-theme this-theme) |
| 663 | (widget-toggle-action widget event)) |
| 664 | ;; Enable the theme. |
| 665 | (unless custom-theme-allow-multiple-selections |
| 666 | ;; If only one theme is allowed, disable all other themes and |
| 667 | ;; uncheck their boxes. |
| 668 | (dolist (theme custom-enabled-themes) |
| 669 | (and (not (eq theme this-theme)) |
| 670 | (assq theme custom--listed-themes) |
| 671 | (disable-theme theme))) |
| 672 | (dolist (theme custom--listed-themes) |
| 673 | (unless (eq (car theme) this-theme) |
| 674 | (widget-value-set (cdr theme) nil) |
| 675 | (widget-apply (cdr theme) :notify (cdr theme) event)))) |
| 676 | (when (load-theme this-theme) |
| 677 | (widget-toggle-action widget event))) |
| 678 | ;; Mark `custom-enabled-themes' as "set for current session". |
| 679 | (put 'custom-enabled-themes 'customized-value |
| 680 | (list (custom-quote custom-enabled-themes))))) |
| 681 | |
| 682 | (defun custom-describe-theme () |
| 683 | "Describe the Custom theme on the current line." |
| 684 | (interactive) |
| 685 | (let ((widget (widget-at (line-beginning-position)))) |
| 686 | (and widget |
| 687 | (describe-theme (widget-get widget :theme-name))))) |
| 688 | |
| 689 | (defun custom-theme-save (&rest _ignore) |
| 690 | (interactive) |
| 691 | (customize-save-variable 'custom-enabled-themes custom-enabled-themes) |
| 692 | (message "Custom themes saved for future sessions.")) |
| 693 | |
| 694 | (defun custom-theme-selections-toggle (widget &optional event) |
| 695 | (when (widget-value widget) |
| 696 | ;; Deactivate multiple-selections. |
| 697 | (if (< 1 (length (delq nil (mapcar (lambda (x) (widget-value (cdr x))) |
| 698 | custom--listed-themes)))) |
| 699 | (error "More than one theme is currently selected"))) |
| 700 | (widget-toggle-action widget event) |
| 701 | (setq custom-theme-allow-multiple-selections (widget-value widget))) |
| 702 | |
| 703 | (provide 'cus-theme) |
| 704 | |
| 705 | ;;; cus-theme.el ends here |