cus-theme.el 25 KB

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