Tianchi YU's Blog

Emacs 重塑计划 - 2. 主题与配色 + lisp函数

一个编辑器除了其功能外,另一个对编辑者非常重要的特点,就是他的颜值了。只有一个符合你的审美、视觉使用习惯的编辑器,才能让你免于疲劳和乏味。

因此我们势必要给我们的Emacs增加外观特征。

主题

关于Emacs的主题,Emacs自带以及互联网上其实有非常多的选择。我曾经用过monokai,tango-dark还有dracula等。

LazyCat 创建了一个他自己的主题版本,是一个相当丰富的主题和配色设置。我肯定是没有这个精力从零开始打造一个主题,但是通过借鉴和模仿,构建一个属于我的主题配色是完全可行的。

自定义选项组

首先,我们要定义一个关于Emacs主题的自定义选项组(custom group),命名为lialittis-themes,这个组用于组织和管理与主题相关的各种选项,以及一些全局变量等。例如:

(defgroup lialittis-themes nil
  "Options for lialittis-themes."
  :group 'faces)

(defcustom lialittis-themes-padded-modeline nil
  "Default value for padded-modeline setting for themes that support it."
  :group 'lialittis-themes
  :type '(choice integer boolean))

(defcustom lialittis-themes-enable-bold t
  "If nil, bold will be disabled across all faces."
  :group 'lialittis-themes
  :type 'boolean)

(defcustom lialittis-themes-enable-italic t
  "If nil, italics will be disabled across all faces."
  :group 'lialittis-themes
  :type 'boolean)

(defvar lialittis-themes--colors nil)
(defvar lialittis--min-colors '(257 256 16))
(defvar lialittis--quoted-p nil)
(defvar lialittis-themes--faces nil)

辅助函数 - 分析部分lazycat主题中的代码

我们需要预先加载 cl-lib 库,该库提供了一些通用的 Emacs Lisp 函数,比如列表操作和迭代器等。在这里可能被用于一些辅助函数或者宏的定义。

def-lazycat-theme

定义一个宏,用于定义一个LAZYCAT主题。

(defmacro def-lazycat-theme (name docstring defs &optional extra-faces extra-vars)
  "Define a LAZYCAT theme, named NAME (a symbol)."
  (declare (doc-string 2))
  (let ((lazycat-themes--colors defs))
    `(let* ((bold   lazycat-themes-enable-bold)
            (italic lazycat-themes-enable-italic)
            ,@defs)
       (setq lazycat-themes--colors
             (list ,@(cl-loop for (var val) in defs
                              collect `(cons ',var ,val))))
       (deftheme ,name ,docstring)
       (custom-theme-set-faces
        ',name ,@(lazycat-themes-prepare-facelist extra-faces))
       (custom-theme-set-variables
        ',name ,@(lazycat-themes-prepare-varlist extra-vars))
       (unless bold (set-face-bold 'bold nil))
       (unless italic (set-face-italic 'italic nil))
       (provide-theme ',name))))

(defmacro def-lazycat-theme (name docstring defs &optional extra-faces extra-vars) ...)有四个参数:name 是主题名称(一个符号),docstring 是主题的文档字符串,defs 是主题的定义,extra-facesextra-vars 是额外的面部和变量。 (let ((lazycat-themes--colors defs)) ...) 局部变量 lazycat-themes--colors, defs 是一个列表,包含了主题的颜色定义。 (let* ((bold lazycat-themes-enable-bold) ...) ...) 定义了几个局部变量,bolditalic 变量分别表示是否启用了粗体和斜体效果,它们的值取自于全局变量 lazycat-themes-enable-boldlazycat-themes-enable-italic(setq lazycat-themes--colors ...)lazycat-themes--colors 设置为一个新的列表,其中包含了主题的各种颜色定义。这些定义是从 defs 中提取并转换成 (variable . value) 对的形式。 (deftheme ,name ,docstring) 使用 deftheme 宏定义了一个新的 Emacs 主题,名称为 name,并附带了相应的文档字符串 docstring(custom-theme-set-faces ...) 用于设置主题的面部,使用了 custom-theme-set-faces 函数,并调用了一个辅助函数 lazycat-themes-prepare-facelist,该函数用于处理额外的面部列表 extra-faces(custom-theme-set-variables ...) 设置主题的变量,使用了 custom-theme-set-variables 函数,并调用 lazycat-themes-prepare-varlist,该函数用于处理额外的变量列表 extra-vars(unless bold (set-face-bold 'bold nil))(unless italic (set-face-italic 'italic nil))根据 bolditalic 变量的值来设置粗体和斜体效果。如果它们的值为 nil,则分别禁用对应的效果。最后(provide-theme ',name) 提供了刚刚定义的主题,使得其他代码可以使用并加载这个主题。

下面演示一下lazycat是如何使用这个宏的:

(def-lazycat-theme lazycat-dark
  "A dark theme inspired by Atom One Dark"

  ;; name        default   256       16
  ((bg         '("#242525" nil       nil            ))
   (bg-alt     '("#333333" nil       nil            ))
   (base0      '("#1B2229" "black"   "black"        ))
   (base1      '("#1c1f24" "#1e1e1e" "brightblack"  ))
   (base2      '("#202328" "#2e2e2e" "brightblack"  ))
   (base3      '("#23272e" "#262626" "brightblack"  ))
   (base4      '("#3f444a" "#3f3f3f" "brightblack"  ))
   (base5      '("#5B6268" "#525252" "brightblack"  ))
   (base6      '("#73797e" "#6b6b6b" "brightblack"  ))
   (base7      '("#9ca0a4" "#979797" "brightblack"  ))
   (base8      '("#DFDFDF" "#dfdfdf" "white"        ))
   (fg         '("#00CE00" "#bfbfbf" "brightwhite"  ))
   (fg-alt     '("green4" "#2d2d2d" "white"        ))

   (grey       base4)
   (red        '("#ff6c6b" "#ff6655" "red"          ))
   (orange     '("#da8548" "#dd8844" "brightred"    ))
   (green      '("#98be65" "#99bb66" "green"        ))
   (teal       '("#4db5bd" "#44b9b1" "brightgreen"  ))
   (yellow     '("#ECBE7B" "#ECBE7B" "yellow"       ))
   (blue       '("#51afef" "#51afef" "brightblue"   ))
   (dark-blue  '("#2257A0" "#2257A0" "blue"         ))
   (magenta    '("#c678dd" "#c678dd" "brightmagenta"))
   (violet     '("#a9a1e1" "#a9a1e1" "magenta"      ))
   (cyan       '("#46D9FF" "#46D9FF" "brightcyan"   ))
   (dark-cyan  '("#5699AF" "#5699AF" "cyan"         ))

   ;; face categories -- required for all themes
   (highlight      "green")
   (vertical-bar   (lazycat-darken base1 0.1))
   (selection      dark-blue)
   (builtin        "#00b8ff")
   (comments       "#a7a7a7")
   (doc-comments   "#aaaaaa")
   (constants      "#bd00ff")
   (functions      "gold2")
   (keywords       "#004FFF")
   (methods        cyan)
   (operators      "cyan3")
   (type           "#00b8ff")
   (strings        "#DFD67A")
   (variables      "gold2")
   (numbers        orange)
   (region         "#3F90F7")
   (region-fg      "#FFF")
   (error          red)
   (warning        yellow)
   (success        green)
   (vc-modified    orange)
   (vc-added       green)
   (vc-deleted     red)

   ;; custom categories
   (hidden     `(,(car bg) "black" "black"))
   (-modeline-bright lazycat-dark-brighter-modeline)
   (-modeline-pad
    (when lazycat-dark-padded-modeline
      (if (integerp lazycat-dark-padded-modeline) lazycat-dark-padded-modeline 4)))

   (modeline-fg     fg)
   (modeline-fg-alt base5)

   (modeline-bg
    (if -modeline-bright
        (lazycat-darken blue 0.475)
      `(,(lazycat-darken (car bg-alt) 0.15) ,@(cdr base0))))
   (modeline-bg-l
    (if -modeline-bright
        (lazycat-darken blue 0.45)
      `(,(lazycat-darken (car bg-alt) 0.1) ,@(cdr base0))))
   (modeline-bg-inactive   `(,(lazycat-darken (car bg-alt) 0.1) ,@(cdr bg-alt)))
   (modeline-bg-inactive-l `(,(car bg-alt) ,@(cdr base1))))


  ;; --- extra faces ------------------------
  ;; ...

   ;; --- major-mode faces -------------------
   ;; css-mode / scss-mode
   (css-proprietary-property :foreground orange)
   (css-property             :foreground green)
   (css-selector             :foreground blue)

   ;; markdown-mode
   (markdown-markup-face :foreground base5)
   (markdown-header-face :inherit 'bold :foreground red)
   ((markdown-code-face &override) :background (lazycat-lighten base3 0.05))

   ;; org-mode
   (org-hide :foreground hidden)
   (solaire-org-hide-face :foreground hidden)

   ;; secondary region.
   (secondary-selection :background grey)
   )


  ;; --- extra variables ---------------------
  ())

好了,我们可以看到,这里定义了一系列变量,用于表示不同颜色的值。例如,bg 表示背景颜色,它被设置为一个字符串列表 ("#242525" nil nil),这个列表表示在不同的色彩深度下的值。 (-modeline-bright lazycat-dark-brighter-modeline) 这行代码设置了一个局部变量 -modeline-bright,其值取自 lazycat-dark-brighter-modeline,用于控制模式栏是否明亮。 (modeline-fg fg)(modeline-fg-alt base5)...定义了模式栏(modeline)的前景和背景颜色。我们也可以定义额外的面部设置。

lazycat-color

;;;###autoload
(defun lazycat-color (name &optional type)
  "Retrieve a specific color named NAME (a symbol) from the current theme."
  (let ((colors (if (listp name)
                    name
                  (cdr-safe (assq name lazycat-themes--colors)))))
    (and colors
         (cond ((listp colors)
                (let ((i (or (plist-get '(256 1 16 2 8 3) type) 0)))
                  (if (> i (1- (length colors)))
                      (car (last colors))
                    (nth i colors))))
               (t colors)))))

LazyCat 定义了一个名为lazycat-color的函数,用于从当前主题中检索指定名称的颜色。其中,;;;###autoload 是一种特殊的注释格式,用于告诉 Emacs 在加载这个文件时自动定义函数。这个注释通常用于将函数定义与它们的使用位置分开,以减少启动时间。(defun lazycat-color (name &optional type) ...) 这个宏定义了一个函数 lazycat-color,它接受两个参数:name 是要检索的颜色名称(一个符号),type 是一个可选参数,用于指定颜色的类型。(let ((colors ... ))) 中 let 表达式定义了一个局部变量 colors,它用于存储从当前主题中检索到的颜色数据。如果 name 是一个列表,那么 colors 就是这个列表本身;否则,通过 assq 函数查找 lazycat-themes--colors 中与 name 关联的颜色数据。(and colors ... ) 如果 colors 不为 nil,则执行 cond 表达式内部的条件。

#emacs