403 lines
16 KiB
Plaintext
403 lines
16 KiB
Plaintext
|
||
|
||
<p>In Emacs a “theme” is a set of configurations that can be enabled or
|
||
disabled as a block. Each of those controls a construct of the
|
||
rendering engine known as a “face”. Faces store the properties that are
|
||
associated with each element on display. These properties encompass
|
||
background and/or foreground colours as well as typographic attributes,
|
||
such as the font weight or slant.</p>
|
||
|
||
<h2>Finding faces</h2>
|
||
|
||
<p>Themes are programs written in Emacs Lisp (Elisp), whose intended role
|
||
is to control faces. We can learn about all the faces that are loaded
|
||
in the current session with <code>M-x list-faces-display</code>. The command will
|
||
produce a buffer with the <em>symbol</em> (i.e. unique name) of the face and a
|
||
preview of how it looks.</p>
|
||
|
||
<p>You can always consult the help page of a given symbol with <code>C-h o</code>
|
||
(<code>M-x describe-symbol</code>). Place the point over a face’s symbol, type
|
||
<code>C-h o</code> to have the thing at point as the default option. Select that
|
||
(such as with <code>M-n</code>) to get a description of what it is supposed to do.</p>
|
||
|
||
<p>If we do this over the <code>cursor</code> face, we get the following:</p>
|
||
|
||
<blockquote>
|
||
<p>Basic face for the cursor color under X. Currently, only the
|
||
‘:background’ attribute is meaningful; all other attributes are
|
||
ignored. The cursor foreground color is taken from the background
|
||
color of the underlying text.</p>
|
||
|
||
<p>Note: Other faces cannot inherit from the cursor face.</p>
|
||
</blockquote>
|
||
|
||
<p>As with all <code>*Help*</code> buffers, the ones for individual faces contain a
|
||
link to the library that defines them. We are informed, for instance,
|
||
that the <code>cursor</code> is defined in <code>faces.el</code>. So we can always visit the
|
||
source code from there whenever we need to understand more about the
|
||
item of our inquiry.</p>
|
||
|
||
<p>Note that <code>list-faces-display</code> will only cover the libraries that are
|
||
currently loaded, but not necessarily the faces that your active theme
|
||
defines. If you have installed some package that you have not used yet,
|
||
then any faces it may be defining will not be immediately available in
|
||
the <code>*Faces*</code> buffer. To actually include those in the list, you need
|
||
to either use their package or explicitly load the relevant file with
|
||
<code>M-x load-library</code>. You can always regenerate the <code>*Faces*</code> buffer by
|
||
typing <code>g</code> while inside of it.</p>
|
||
|
||
<h2>Configuring an individual face</h2>
|
||
|
||
<p>Before we proceed to write a fully fledged theme, let us first examine
|
||
how to control faces one by one. The function dedicated to that task is
|
||
<code>set-face-attribute</code>. Read its documentation string with <code>C-h f</code>
|
||
followed by its symbol. <strong>This is important</strong> because it provides
|
||
valuable information about the properties that a face may be associated
|
||
with. You will need it when configuring your own theme.</p>
|
||
|
||
<p><em>Assuming you read the documentation</em> of <code>set-face-attribute</code>, let us
|
||
consider this example:</p>
|
||
|
||
<pre><code class="language-elisp">(set-face-attribute 'cursor nil :background "red")
|
||
</code></pre>
|
||
|
||
<p>We have learnt that the <code>cursor</code> only recognises a <code>:background</code>
|
||
property and will ignore any other. What we do here is instruct it to
|
||
use the generic red colour.</p>
|
||
|
||
<p>To confirm that this works, place the point to the right of the closing
|
||
parenthesis and type <code>C-x C-e</code> (which calls <code>eval-last-sexp</code>). Your
|
||
cursor show now be coloured red. If you were to put this in your
|
||
initialisation file, or any other library that gets loaded when you open
|
||
Emacs, your cursor would always get the colour you specified (unless
|
||
something else overrides it later on, but you get the point).</p>
|
||
|
||
<p>A good use-case for this is to define your font families for the three
|
||
main constructs of <code>default</code>, <code>variable-pitch</code>, and <code>fixed-pitch</code>.</p>
|
||
|
||
<p>This is the gist of what is included in the manual of the Modus themes
|
||
on the topic of <a href="https://protesilaos.com/emacs/modus-themes/#h:defcf4fc-8fa8-4c29-b12e-7119582cc929">font configurations for Org (and
|
||
others)</a></p>
|
||
|
||
<pre><code class="language-elisp">;; my custom build of Iosevka
|
||
;; https://gitlab.com/protesilaos/iosevka-comfy
|
||
(set-face-attribute 'default nil :font "Iosevka Comfy-15")
|
||
|
||
(set-face-attribute 'variable-pitch nil :family "Source Sans Pro" :height 1.0)
|
||
|
||
(set-face-attribute 'fixed-pitch nil :family "Iosevka Comfy" :height 1.0)
|
||
</code></pre>
|
||
|
||
<p>Depending on what you want to do, you can use Elisp to further control
|
||
things. Here we can be a bit more succinct by using <code>dolist</code> (remember
|
||
that <code>C-h f</code>, <code>C-h v</code>, or just <code>C-h o</code> are among your most valuable
|
||
tools in your Emacs journey).</p>
|
||
|
||
<pre><code class="language-elisp">(dolist (face '(default fixed-pitch))
|
||
(set-face-attribute face nil :family "Iosevka Comfy"))
|
||
</code></pre>
|
||
|
||
<h2>Using colours</h2>
|
||
|
||
<p>We can find the names of all generic colours with <code>list-colors-display</code>.
|
||
Notice how earlier we specified the <code>:background</code> of the <code>cursor</code> face
|
||
to a <code>"red"</code> value. Alternatively, one could use a hexadecimal RGB
|
||
code, such as <code>"#ff0000"</code> for pure red. I prefer the latter because it
|
||
is more precise and flexible.</p>
|
||
|
||
<p>How you specify colours is ultimately up to you. Picking the right
|
||
values is not an easy task. It is a field of endeavour that stands at
|
||
the intersection or art and science, as I explained in my essay about
|
||
the <a href="https://protesilaos.com/codelog/2020-03-17-design-modus-themes-emacs/">design of the Modus themes</a>.</p>
|
||
|
||
<h2>Deconstructing an Emacs face</h2>
|
||
|
||
<p>While <code>set-face-attribute</code> is perfectly fine for a few items, it becomes
|
||
inefficient at scale. This is why Emacs provides the
|
||
<code>custom-theme-set-faces</code> function. Before we start using that, <em>we must
|
||
first understand what the specifications of a face are</em>.</p>
|
||
|
||
<p>Consider this excerpt from <code>M-x find-library RET faces</code> (here “RET”
|
||
means to type the command, then confirm your choice with the
|
||
Return/Enter key, and follow it up with the “faces” library).</p>
|
||
|
||
<pre><code class="language-elisp">(defface tab-bar
|
||
'((((class color) (min-colors 88))
|
||
:inherit variable-pitch
|
||
:background "grey85"
|
||
:foreground "black")
|
||
(((class mono))
|
||
:background "grey")
|
||
(t
|
||
:inverse-video t))
|
||
"Tab bar face."
|
||
:version "27.1"
|
||
:group 'basic-faces)
|
||
</code></pre>
|
||
|
||
<p>We can read all about these specs with <code>C-h o defface</code>. Again, read the
|
||
docs to save yourself from trouble and frustration. While you start
|
||
making a habit of that, let me simplify this <code>defface</code> for you (extra
|
||
space for didactic purposes):</p>
|
||
|
||
<pre><code class="language-elisp">(defface tab-bar
|
||
'(
|
||
|
||
(
|
||
((class color) (min-colors 88))
|
||
:inherit variable-pitch
|
||
:background "grey85"
|
||
:foreground "black")
|
||
|
||
(
|
||
((class mono))
|
||
:background "grey")
|
||
|
||
(t
|
||
:inverse-video t)
|
||
|
||
)
|
||
|
||
"Tab bar face.")
|
||
</code></pre>
|
||
|
||
<p>Here we have the general structure of an expression that evaluates
|
||
multiple conditions. It looks like <code>cond</code>:</p>
|
||
|
||
<pre><code class="language-elisp">(cond
|
||
((FIRST TEST)
|
||
FIRST RESULT)
|
||
((SECOND TEST)
|
||
SECOND RESULT)
|
||
(t ; if none of the above
|
||
FALLBACK RESULT))
|
||
</code></pre>
|
||
|
||
<p>With these in mind, we can read each test more easily. Focus on this:</p>
|
||
|
||
<pre><code class="language-elisp">(
|
||
((class color) (min-colors 88))
|
||
:inherit variable-pitch
|
||
:background "grey85"
|
||
:foreground "black")
|
||
</code></pre>
|
||
|
||
<p>It checks whether the display terminal can support a minimum of 88
|
||
colours. If you are using Emacs with a graphical toolkit, this is most
|
||
likely the case. If the condition is satisfied, this face will use
|
||
<code>grey85</code> for its background and <code>black</code> for its foreground. Whereas in
|
||
more limited display terminals, it uses something simpler:</p>
|
||
|
||
<pre><code class="language-elisp">(
|
||
((class mono))
|
||
:background "grey")
|
||
</code></pre>
|
||
|
||
<p>Same principle for the fallback condition, which merely inverts the
|
||
colours with the assumption that those are some variant of black and
|
||
white for the foreground/background:</p>
|
||
|
||
<pre><code class="language-elisp">(t
|
||
:inverse-video t)
|
||
</code></pre>
|
||
|
||
<p>While you could define all your faces to adapt to every possible display
|
||
terminal out there, I find that what one typically needs is to optimise
|
||
for <code>((class color) (min-colors 89))</code>.</p>
|
||
|
||
<p>With these in mind, we can start writing our first theme.</p>
|
||
|
||
<h2>Skeleton of a custom theme</h2>
|
||
|
||
<p>As noted in the previous section, Emacs offers <code>custom-theme-set-faces</code>
|
||
for the express purpose of streamlining the process of controlling faces
|
||
in bulk. As always, read the documentation of that function to learn
|
||
more about the finer points.</p>
|
||
|
||
<p>Here we will be working with a minimal, yet perfectly usable base.
|
||
Every theme must be placed in a file whose name follows the pattern of
|
||
<code>SYMBOL-theme.el</code>. We declare the symbol of our theme with the
|
||
following:</p>
|
||
|
||
<pre><code class="language-elisp">(deftheme prot-base
|
||
"The basis for a custom theme.")
|
||
</code></pre>
|
||
|
||
<p>The above means that the file name must be <code>prot-base-theme.el</code> (we have
|
||
some more code at the end of the file, but we take things one step at a
|
||
time).</p>
|
||
|
||
<p>Now we want to configure a set of faces that are optimised for the
|
||
display spec of <code>((class color) (min-colors 89))</code>. Instead of writing
|
||
this expression each time, we will dynamically bind it to a variable,
|
||
using <code>let</code>.</p>
|
||
|
||
<pre><code class="language-elisp">(let ((class '((class color) (min-colors 89)))
|
||
...other variables)
|
||
...body)
|
||
</code></pre>
|
||
|
||
<p>Since we are defining local variables, it is a good idea to also write
|
||
our colours here, so that we economise on typing, but also to avoid
|
||
discrepencies. Each colour is defined as <code>(name value)</code>.</p>
|
||
|
||
<pre><code class="language-elisp">(let ((class '((class color) (min-colors 89)))
|
||
(main-bg "#ffffff") (main-fg "#000000")
|
||
(red "#a00000") (green "#005000") (blue "#000077"))
|
||
...body)
|
||
</code></pre>
|
||
|
||
<p>Everything is in place to start defining face attributes. The body of
|
||
our dynamically bound variables contains <code>custom-theme-set-faces</code>,
|
||
followed by the name of the <code>deftheme</code> we declared and then each face’s
|
||
symbol, display spec and attributes:</p>
|
||
|
||
<pre><code class="language-elisp">(deftheme prot-base
|
||
"The basis for a custom theme.")
|
||
|
||
(let ((class '((class color) (min-colors 89)))
|
||
(main-bg "#ffffff") (main-fg "#000000")
|
||
(red "#a00000") (green "#005000") (blue "#000077"))
|
||
(custom-theme-set-faces
|
||
'prot-base
|
||
`(default ((,class :background ,main-bg :foreground ,main-fg)))
|
||
`(cursor ((,class :background ,red)))
|
||
`(font-lock-builtin-face ((,class :foreground ,blue))))
|
||
`(font-lock-string-face ((,class :foreground ,green))))
|
||
|
||
(provide-theme 'prot-base)
|
||
|
||
(provide 'prot-base-theme)
|
||
</code></pre>
|
||
|
||
<p>This is a valid theme. To actually use it, you must write it to a file,
|
||
which in this case is <code>prot-base-theme.el</code>. This file must be in a
|
||
directory read by Emacs. Say you put it in <code>~/.emacs.d/themes/</code>. To
|
||
inform Emacs about it, evaluate this:</p>
|
||
|
||
<pre><code class="language-elisp">(add-to-list 'load-path "~/.emacs.d/themes/")
|
||
</code></pre>
|
||
|
||
<p>With the theme written at <code>~/.emacs.d/themes/prot-base-theme.el</code>, you
|
||
can now <code>M-x load-theme RET prot-base</code>. And there you have it!</p>
|
||
|
||
<p>Note though that you may also need to <code>M-x disable-theme</code> and specify
|
||
the one currently in use to make sure you do not get mixed results
|
||
(unless you want to overlay one theme on top of another, but I will let
|
||
you run such experiments).</p>
|
||
|
||
<p>Remember to rely on <code>list-faces-display</code> to find all the symbols you
|
||
wish to cover. Furthermore, you can always identify the properties of
|
||
the character at point with <code>M-x describe-char</code> (or type it directly
|
||
with <code>C-u C-x =</code>). If it uses a face, you will see it mentioned in the
|
||
resulting <code>*Help*</code> buffer.</p>
|
||
|
||
<p>To understand the syntax for backquotes and commas, type <code>M-:</code> and then
|
||
insert <code>(info "(elisp) Backquote")</code>. This will take you to the relevant
|
||
node in the Emacs Lisp Reference Manual.</p>
|
||
|
||
<h2>More tools for theme developers</h2>
|
||
|
||
<p>These are excerpts from my dotemacs. They are meant to further assist
|
||
you in the task of developing a custom theme. Check the doc string of
|
||
each variable and adapt things to your liking.</p>
|
||
|
||
<h3>Rainbow mode for colour previews</h3>
|
||
|
||
<p>While experience may help estimate with decent accuracy a hexadecimal
|
||
RGB colour, it is always better to have a live preview available. Once
|
||
the following package is loaded, you can get it with <code>M-x rainbow-mode</code>.</p>
|
||
|
||
<pre><code class="language-elisp">(use-package rainbow-mode
|
||
:ensure
|
||
:diminish
|
||
:commands rainbow-mode
|
||
:config
|
||
(setq rainbow-ansi-colors nil)
|
||
(setq rainbow-x-colors nil))
|
||
</code></pre>
|
||
|
||
<h3>Use a linter front-end to improve your code</h3>
|
||
|
||
<p>You can either rely on the built-in <code>flymake</code> or the third party
|
||
<code>flycheck</code>. Both work great with Elisp files. You activate them with
|
||
<code>flymake-mode</code> or <code>flycheck-mode</code> respectively.</p>
|
||
|
||
<pre><code class="language-elisp">(use-package flymake
|
||
:commands flymake-mode
|
||
:config
|
||
(setq flymake-fringe-indicator-position 'left-fringe)
|
||
(setq flymake-suppress-zero-counters t)
|
||
(setq flymake-start-on-flymake-mode t)
|
||
(setq flymake-no-changes-timeout nil)
|
||
(setq flymake-start-on-save-buffer t)
|
||
(setq flymake-proc-compilation-prevents-syntax-check t)
|
||
(setq flymake-wrap-around nil))
|
||
|
||
(use-package flycheck
|
||
:ensure
|
||
:commands flycheck-mode
|
||
:config
|
||
(setq flycheck-check-syntax-automatically
|
||
'(save mode-enabled))
|
||
:hook (flycheck-error-list-mode-hook . visual-line-mode))
|
||
</code></pre>
|
||
|
||
<p>If you go with Flycheck, you may also want a modeline indicator, unless
|
||
you use a custom modeline that already defines one:</p>
|
||
|
||
<pre><code class="language-elisp">(use-package flycheck-indicator
|
||
:ensure
|
||
:after flycheck
|
||
:config
|
||
(setq flycheck-indicator-icon-error (string-to-char "!"))
|
||
(setq flycheck-indicator-icon-info (string-to-char "·"))
|
||
(setq flycheck-indicator-icon-warning (string-to-char "*"))
|
||
(setq flycheck-indicator-status-icons
|
||
'((not-checked "%")
|
||
(no-checker "-")
|
||
(running "&")
|
||
(errored "!")
|
||
(finished "=")
|
||
(interrupted "#")
|
||
(suspicious "?")))
|
||
:hook (flycheck-mode-hook . flycheck-indicator-mode))
|
||
</code></pre>
|
||
|
||
<p>And here is how to ensure that you are following best practices for
|
||
packaging Elisp libraries (you only need one of the two, depending on
|
||
the front-end you choose):</p>
|
||
|
||
<pre><code class="language-elisp">(use-package flycheck-package
|
||
:ensure
|
||
:after flycheck
|
||
:config
|
||
(flycheck-package-setup))
|
||
|
||
(use-package package-lint-flymake
|
||
:ensure
|
||
:after flymake
|
||
:config
|
||
(package-lint-flymake-setup))
|
||
</code></pre>
|
||
|
||
<h2>Remember that Emacs themes are Elisp programs</h2>
|
||
|
||
<p>It should be clear by now that a theme can rely on advanced programming
|
||
techniques to do its work. Here we used <code>let</code>. While you can always go
|
||
with something simple, you retain the option to define more elaborate
|
||
criteria that, say, come into effect once a certain variable is enabled.</p>
|
||
|
||
<p>My Modus themes, which were <a href="https://protesilaos.com/codelog/2020-08-27-emacs-modus-themes-core/">recently added to upstream
|
||
Emacs</a>,
|
||
contain lots of Elisp logic, making them highly customisable. Study
|
||
their <a href="https://gitlab.com/protesilaos/modus-themes">source code</a> if you
|
||
want. It can help you learn more about defining and then evaluating
|
||
customisation options.</p>
|
||
|
||
<p>Use the information in this document to write your own theme or to just
|
||
gain insight into how the theme of your choice is designed.</p>
|
||
|
||
<p>Good luck!</p>
|
||
|
||
|