Xcode Requires A Lot of Data for Swift Package Resolution

Xcode on mobile data after resolving packages once: 45MB required

This picture shows one of the weird annoyances with Xcode and Swift packages.

One package resolution step swallowed 45MB of my data.

You’ve likely heard it elsewhere: when iOS developers need to work with Swift packages and Xcode but have a shoddy internet connection, tough luck! Xcode will fail to build because it’s “Resolving Packages” step inevitably fails.

Today, our internet was out for the first half of the day, so I had to tether to my iPad to actually get Xcode to work at all. Rebasing was the death blow to the package cache; but even after this initial package fetching, I managed to amass a throughput of almost 200MB in maybe 3 hours of development work.

I would like to think that a SwiftPM-managed project, where you could check in the .swiftpm/ directory if you need, would perform better in that regard.

Does Xcode not grab a shallow clone, maybe?

The packages in this project:

  • Sparkle
  • sindresorhus/Preferences
  • glebd/CocoaFob
  • 9 packages of my own making with almost no commit history worth mentioning

I could see how fetching the whole of Sparkle can amount to more data than the others.

Getting to Know Jetpack Compose by Comparing Concepts with SwiftUI (Markus Müller's CocoaHeads Leipzig Presentation)

Markus Müller (@m_mlr) recently did a presentation at CocoaHeads Leipzig about Jetpack Compose. – Thanks for the inspiring presentation, Markus! I learned a lot.

Update 2023-01-24: Check out the sample project on GitHub!

The similarities are interesting, but the differences in the build tools was also striking. The JetBrains IDE did a great job at hiding noise and presenting useful information, especially when it comes to locating and then auto-importing the various modules that comprise a modern Android app written in Kotlin and using Jetpack Compose.

I took the chance to try a Boox Nova Air 2 (affiliate link) for monochrome, multi-layered “sketchnotes” (more on that in a future post):

Four concatenated "pages" worth of notes, scaled down to 10% (!) of the original size.

My personal highlights follow – please bear in mind this is all hearsay and based on a presentation + looking at some sample code a couple months ago, so I’m by no means an expert! The purpose of this list is to mention important concepts and provide links for further reading.

  • To make UI components remember their state, you need to look into “state hoisting” for view serialization
    • Scary term for a simple concept:

      In Jetpack Compose, state hoisting means moving the state of a composable to its parent (caller). It helps us to reuse the composables. (Source)

      This is actually not dis-similar to how a SwiftUI view has a @State, and its children only get the derived observables.

    • On (very) old Android API versions, text fields by default used to forget their contents when you rotated the devices vigorously. Ouch.
    • Hoisting via a “remember Saveable” is similar to Codable in Swift. But applied to UI components.
    • Unidirectional flow approaches (think: Redux, ReSwift, The Composable Architecture/TCA) can help remove the problem for you. If the component isn’t the state’s source of truth, you can’t lose its state, right? (This could actually be an over-simplification, I don’t know from experience.)
  • Where SwiftUI implements view modifiers as chained method calls, figuring out which modifier works where through the power of protocols, Jetpack Compose’s components provide run-of-the-mill injectable Modifier objects.
    • Modifiers in SwiftUI chain on the view object;
    • Modifiers in Jetpack Compose chain the view input.
    • Potential upside: no surprises when you change the modifier order. You can lose the type information of SwiftUI.Text-specific modifiers when you use general-purpose SwiftUI.View modifiers in some circumstances. That’s what I’m thinking of.
  • StateFlow and MutableStateFlow unify the concepts and benefits that SwiftUI gets from Combine publishers and structured concurrency (async/await). – Don’t ask me how, that’s just what I took note of :)

  • Theming is interesting! The App object is initialized inside the Theme:

     Theme {
         App { ... }
     }
    

    That sounds odd at first, but it makes sense that an app lives in the context of a global app theme. Theme changes affect all of the app. We don’t have that sort of theming in iOS and instead tweak the default look and feel of components with e.g. (shared) view modifiers. As an upside, SwiftUI view modifiers can affect a whole view sub-tree; it looked like you need to tell some Compose view components how to theme themselves a bit more when you want to deviate from the theme’s defaults.

  • The SwiftUI documentation pales in comparison. Apple docs imporoved in recent years, but it’s not fair how much better the Jetpack docs are.
    • The Jetpack Compose documentation is live-filterable,
    • a lot of pages come with sample project links,
    • the sample projects reside on GitHub,
    • and a huge focus is to get the idea across in a way that a new developer would understand it.
    • High-level overviews and samples take precedence over (rather boring) API method listings.
  • Jetpack Compose is an external library with a dedicated life-cycle, not tied to Android OS version upgrades. I wish SwiftUI were that flexible.

Dictionary.init to Make Use of Swift.Identifiable

In my conceptual post about equality vs identity, I mentioned Helge Heß’ advice to use a Dictionary with an ID as key, and the identified object as value when you would (wrongly) reach for Set and Hashable.

I found a code snippet for this from late last year just for this!

The initializer Dictionary.init(_:uniquingKeysWith:) takes a sequence of (Key, Value) tuples and a closure to resolve duplicate key conflicts.

The following code makes use of the Identifiable protocol to infer the Key to be the ID. The call site becomes:

let foos: [Foo] = ...
let dict: [Foo.ID : Foo] =
    Dictionary(foos, uniquingKeysWith: { _, last in last })

You could also argue for uniquing with { first, _ in first } to discard subsequent objects with the same ID and keep the initial one instead.

And here’s the declaration:

extension Dictionary
where Value: Identifiable, Value.ID == Key {
    @inlinable init<S>(
        _ elements: S,
        uniquingKeysWith combine: (Value, Value) throws -> Value
    ) rethrows where S : Sequence, S.Element == Value {
        try self.init(
            elements.map { ($0.id, $0) },
            uniquingKeysWith: combine)
    }
}

The Difference between Entity and Value Object, and How They Relate to Swift's Identifiable and Equatable Protocols

Helge Heß recently posted on Mastodon that he “still find[s] it disturbing how many #SwiftLang devs implement Equatable as something that breaks the Equatable contract, just to please some API requiring it. Feels like they usually should implement Identifiable and build on top of that instead.”

An interesting Swift piece he shared is to not slap Hashable onto a type just to reap the benefits of Set logic and uniqueness in collections. This usually starts by conforming to Hashable but then only pick one or a few properties for the hash computation. That’s basically a code smell. Consider to use an identifier that’s hashable instead if you want to “hash by ID” anyway:

struct Foo: Identifiable {
  let id: UUID
  let otherPropertyYouWantToIgnore: String = ...
  // ...
}

// Set<Foo> then becomes:
var foos: [Foo.ID : Foo] = [...]

Never thought of that, so kudos!

Update 2023-01-18: Here’s a Dictionary.init extension to simplify getting these instead of Sets.

But I really want to bring attention to the abuse of Equatable he mentioned.

To me, it looks like users of the Swift language may be conflating the following:

  • Value Object and value type (e.g. struct and enum), and
  • Entity and reference type (e.g. class and actor).

The capitalized ones are concepts – the rest is a language construct. (I encountered the most succinct and sensible definitions of these concepts in Domain-Driven Design, but they are older.)

Entities have some kind of identity over time. In practice, this is often accompanyied by some kind of persistence. You fetch the same SQL database table row 10 times, but it’s supposed to be the same thing, no matter if the type of the resulting object in main memory has a reference type or value type. This applies to e.g. customers and products, or posts and tweets.

Value Objects, on the other hand, are considered to be equal in virtue of their data. If you have two Value Objects in your app’s memory with the same attributes (in Swift parlance: properties), then they are the same thing. This is used for stuff like money/currency, time, addresses, and names.

In Swift, i.e. in code and not in land of concepts, you can get “identity” of two objects by using the Identifiable protocol, no matter if it’s used on a struct or class.

You make different objects of the same type equal via the Equatable protocol, also no matter if used on a struct or class.

We had Value Objects in Objective-C, a language without the value types we know from Swift, by overriding isEqual: to make the NSObject subclasses compare their objects not by memory address, but by their properties. In other words, we’d ignore if two references were identical objects in memory or not.

We also had Entities in Objective-C. You could get by with memory address chcks, but if you were serious, you had some database anyway and thus would have access to an Entity ID of sorts. Could be an auto-incrementing SQLite table id, for example.

Conversely, you can have structs (a value type) represent Value Objects or Entities.

struct PersonName: Equatable {
  let firstName: String
  let middleName: String?
  let lastName: String
}

struct Person: Identifiable {
  let id: Int
  let name: PersonName
}

With this definition, it’s tempting to slap Equatable onto Person as well because you want to test if personA and personB are different. The equality operator == feels like a sensible choice, so you’d start with this:

extension Person: Equatable {
  static func == (lhs: Person, rhs: Person) -> Bool {
    lhs.id == rhs.id
  }
}

This correctly omits the name from the equality test.

It (probably incorrectly) also makes two instances of Person equatable – for the convenience of comparing two objects – while it’d have sufficed to use this in call sites:

personA.id == personB.id

This does the same thing but does drive home that you’re dealing with Entities that define same-ness via their ID and ignore the attributes.

Conforming to Equatable arguable improves the ergonomics when you type comparisons:

personA.id == personB.id
// vs
personA == personB

But the second approach also leaves to guesswork what kind of domain concept you’re trying to express. And then you cannot check if the attributes of the Person instances are also equal.

That’s the delineation of “equal” vs “identical”. Equal in terms of data, identical in terms of it being an Entity.

Let’s get back to the false conflation of “Value Object” and “value type”: once you find yourself using Equatable (or Hashable) in a struct (a value type) and then also limit it to a small subset of properties to express when two instances should be considered identical, you maybe (ab-)using equatability to express identity. Try to use Identifiable instead.

If you care about modeling a rich domain, consider these rules of thumb:

  • Express identity/same-ness of an Entity via the Identifiable protocol;
  • Express same-ness of Value Objects via their data using the Equatable protocol;
  • Instead of (ab-)using Hashable to get Set semantics for cheap uniqueness on a subset of properties, use a hashable identifier instead. Replace Set<Foo> with Dictionary<Foo.ID, Foo>.

By the way, when Swift value objects came along and it became so simple to implement Equatable by selecting just the identifier, I fell for this exact trap. There’s still code in my apps that abuses the == operator when I should’ve compared their identifiers. So thanks to Helge for bringing clarity by talking about the more recent addition of Identifiable to the Swift language a bit more!

Use San Francisco Font for SF Symbols Everywhere in Emacs

Today I learned that you can tell Emacs to use fontsets (or just “fonts”) for specific character ranges. Thanks to Alan at idiocy.org for the explanation!

His example is this:

(set-fontset-font t '(?😊 . ?😎) "Segoe UI Emoji")

Now instead of Emoji, which work fine in Emacs for me out of the box, I want SF Symbols. Yes, again.

SF Symbols are in the Unicode private use area-b, at the very end of the Unicode table, U+100000..U+10FFFD.

I have no clue how to get the first and last of them; so I just map the whole private use area-b to the San Francisco font SF Pro Display, which shows SF Symbols just nicely.

To replace the Emoji characters from Alan’s example with these un-typable unicode characters, rely on their hex code range: insert-char accepts the hexadecimal codes directly even though the characters are not part of the preview. So use M-x insert-char 100000 and M-x insert-char 10FFFD.

The result (you can copy-paste, but won’t see the symbols here):

(set-fontset-font t '(?􀀀 . ?􏿽) "SF Pro Display")

And that’s it!

I can now revert the font overrides in my tab bar modifications.

And as a bonus, I now actually see the symbols in the Elisp code (which was previously not possible at all):

My tab bar numbers display SF Symbols now!

So wherever I paste a SF Symbol, I now see the symbol. Mode-line, tab-bar, code, text, it doesn’t matter.

Update 2023-01-08: Álvaro Ramírez uses a fallback font instead. So no need to specify the character range. This might suffice, too, if you don’t mind other glyphs being taken from the fallback font in case your main font misses some arcane special character. Check out Álvaro’s post for a GIF demo, too.

Display a Random Inspiring Quote for Journaling or Shell Prompts

The odd title gives it away – I don’t have a good use-case for this :)

Dr. Drang shared how he displayed one random line of “Oblique Strategies” in his email signatures many years ago.

The “Oblique Strategies” can be found here: http://www.rtqe.net/ObliqueStrategies/Edition1-3.html

They look like this:

Abandon normal instruments
Accept advice
Accretion
A line has two sides
Allow an easement (an easement is the abandonment of a stricture)
Always first steps

“Fortunes” or inspirational “quotes for thought” can work as journaling prompts. It’s also possible to start a new shell session with this “fortune cookie” format as a greeting or Message of the Day.

I’ve formatted the file thus:

  • Treat # at the beginning of a line to be a comment (referencing the source(s))
  • Assuming ~/fortunes.txt as the location

Bash script, e.g. for MotD updates

Bash is simple. Filter out comments, then use shuf to get 1 random line:

$ grep -e "^[^#]" ~/fortunes.txt | shuf -n 1

That prints any line from the file. Easy. Use cron to replace the Message of the Day in /etc/motd and then you have an inspirational greeting in the shell.

Elisp random fortune function for journaling

To get a random prompt in my Emacs org-mode journaling templates was a bit more involved. Instead of piping in the shell, I’m using “the Emacs way”, i.e. applying modifications to invisible, temporary buffers with the file contents:

(defun ct/random-fortune (&optional file)
  "From a fortunes file, produce a random line.
Prints the line to the minibuffer when called interactively,
returns the line otherwise.

When FILE is not nil, read that file. Otherwise, defaults to '~/fortunes.txt'.
Ignores lines that start with a hash \"#\"."
  (interactive)
  (let (($random-line (with-temp-buffer
                        (insert-file-contents "~/fortunes.txt")
                        (keep-lines "^[^#]")
                        (goto-line (1+ (random (count-lines (point-min) (point-max)))))
                        (buffer-substring-no-properties (line-beginning-position) (line-end-position)))))
    ;; 22.4 Distinguish Interactive Calls <https://www.gnu.org/software/emacs/manual/html_node/elisp/Distinguish-Interactive.html>
    (if (called-interactively-p 'any)
        (message "%s" $random-line)
      $random-line)))

This either “"”inspires””” me interactively, by showing the line at the bottom of the window, or when called programmatically, it returns the string for insertion into the meditative journaling template.

That all presupposes I actually have such a template or perform a practice like this, but in reality, this was just a short and fun thing to do between holidays, really.

Maybe your journaling practice benefits from this, though!

Migrating to modus-themes v4 and Going Through the Changes

New year marks the day Protesilaos releases modus-themes v4 into the wild; and my package update from MELPA already ingested preliminary changes on Friday Dec 30th (much to my chagrin, because I initially wanted to do something else than fiddling with my Emacs setup) that were absolutely not backwards compatible.

While I migrated my configuration over, I reported some issues, which were promptly fixed, and after waiting for a couple days, this post is what’s left of my v3-to-v4 migration without any temporary workarounds.

A lot of the old configuration options are gone; instead of many defvars (i.e. “state” that affects theme loading) to affect e.g. the color of code blocks, the selection region, or buffer fringes, we’re now interacting with a large option list instead and declare what we want (i.e. one data source for the truth about the theme). That change means we all need to figure out which old setting now are affected by which names color, and then sprinkle some hooks on top to use named colors for our own purposes.

This is not a migration guide by any means, but it could help you understand how the themes now work by following along. The v4 documentation is thorough, but for a first read, you need to keep a lot of stuff in your head. I hope this eases that a bit.

New themes, and how to load them

The modus-themes package comes with 6 themes:

  1. Light
    1. modus-operandi
    2. modus-operandi-deuteranopia
    3. modus-operandi-tinted
  2. Dark
    1. modus-vivendi
    2. modus-vivendi-deuteranopia
    3. modus-vivendi-tinted

I used to have the deuteranopia flag enabled, so I’m now picking the base theme modus-operandi-deuteranopia. Same if you were >50% using tinted variants: use modus-operandi-tinted. If in doubt, start with the default.

To declare that I want to use the deuteranopia-friendly dark and light variant e.g. when I use F5 to toggle themes, I start with:

(setq modus-themes-to-toggle '(modus-operandi-deuteranopia modus-vivendi-deuteranopia))

Before loading the theme (via built-in load-theme, which replaces the old modus-themes-load-theme that did some extra work), you need to set all your configuration options so they are available during the loading process. We’ll check these out in a minute, but this here is ultimately the last line in my theme config:

;; Replaces a call to (modus-themes-load-themes)
(load-theme (car modus-themes-to-toggle) t t)

Note that I could just as well write (load-theme 'modus-operandi-deuteranopia), but this way I can treat the modus-themes-to-toggle variable as the source of truth. Hashtag DRY.

Heads up: While the load-theme call above works anytime after I’ve confirmed the theme, it refuses to load the theme for the first time. I noticed that it wouldn’t load after a fresh start of Emacs. So I ran (load-theme 'modus-operandi-deuteranopia) once and gave my approval; same for the vivendi variant. Then all is good. (I also had no luck with :no-confirm parameter, by the way.)

So that’s the basic wireframe for how I load and configure the theme. With my actual use-package call, this is the structrue:

(use-package modus-themes
  :init
  (setq modus-themes-to-toggle '(modus-operandi-deuteranopia modus-vivendi-deuteranopia))
  ;; ... setting all variable that need to
  ;; be present before loading the theme ...
  :config
  (load-theme (car modus-themes-to-toggle) t t)
  :bind ("<f5>" . modus-themes-toggle))

Next, we’ll fill out the placeholder I put in the :init section’s comments.

Configuration options

The simple configuration options for me that I kept and still like:

(setq modus-themes-italic-constructs t
      modus-themes-bold-constructs t
      modus-themes-variable-pitch-ui t
      modus-themes-mixed-fonts t)

Most of the work is done by palettes and their overrides, that we’ll look at for the rest of this post. These affect all colors; but to affect bold constructs and font sizes, there are still a couple more variables that inform theme loading. These are listed as 5. Customization options in the docs. The ones I use are:

;; Color customizations
(setq modus-themes-prompts '(bold))
(setq modus-themes-completions nil)
(setq modus-themes-org-blocks 'gray-background)

;; Font sizes for titles and headings, including org
(setq modus-themes-headings '((1 . (light variable-pitch 1.5))
                              (agenda-date . (1.3))
                              (agenda-structure . (variable-pitch light 1.8))
						      (t . (medium))))

Only these simple variable settings did survive; the rest is trickier, because we need to understand the new (and very flexible and quite amazing) palettes.

Palette overrides

The “more power to the user” release comes with some cost: you need to understand how the new palettes work. Theres a whole section in the docs, 5.11 Palette Overrides: for each of the 6 themes, theres a -palette-overrides variable you can use to inject your own styles that, well, override the default settings of the respective theme. The actual code that merges overrides with the defaults is this:

;; modus-operandi-deuteranopia-theme.el
(modus-themes-theme modus-operandi-deuteranopia
                    modus-operandi-deuteranopia-palette
                    modus-operandi-deuteranopia-palette-overrides)

Think of palettes are lists of color names and colors. If you peek into the modus-operandi-deuteranopia-theme.el code, you’ll see it boils down to a very long list like this:

;; modus-operandi-deuteranopia-theme.el
(defconst modus-operandi-deuteranopia-palette
  '((bg-main          "#ffffff")
    (bg-dim           "#f0f0f0")
    ;; ...
    ))

The good news is that there are still many named colors, like bg-main. These semantic colors can be used to style other pieces of Emacs to have the modus colors.

The bad news is getting to them is a bit more cumbersome if you need their values, but it works well and consistently. I’ll show some applications of this below.

Another upside is that some “settings” are part of this list, too, usually further down, and they can reference the more basic color definitions. One example is (fg-link blue-warmer) which declared the link foreground color by referencing the blue-warmer color setting from elsewhere in the file. This replaces old settings like (setq modus-themes-links '(neutral underline)).

I didn’t use many of these, but I noticed the deprecation warnings to guide me through the process.

It’s hard to say in retrospect (as e.g. Tony Zorman pointed out) what e.g. the ‘neutrality’ setting used to do. You need to look up the hex colors and find close matches, is my guess. (After Tony’s email exchange, the v4 release candidate improved a lot, so your personal migration might go much smoother.)

For example, if you don’t like the theme/palette’s default link color and want something with less contrast, override fg-link with blue-faint instead of blue-warmer, maybe.

So how do you do that?

If you want global overrides like the old modus-themes-links setting, you use the shared palette overrides, called modus-themes-common-palette-overrides. These should not use absolute hex colors but reference named colors instead. That way, you get the proper “faint blue” for the dark and light theme.

(setq modus-themes-common-palette-overrides
      '(;; Globally use faint instead of warm blue for links
        (fg-link blue-faint)
        ;; ... more overrides here ...
        ))

If you want to tweak just one particular palette, you choose one from the 6 palette override options like I mentioned above.

Here’s an example to change the inactive background color of the light deuteranopia theme:

(customize-set-variable 'modus-operandi-deuteranopia-palette-overrides
                        '(
                          ;; More subtle gray for the inactive window and modeline
                          (bg-inactive "#efefef")
                          ))

Setting a light gray like "#efefef" wouldn’t work for the modus-vivendi variants.

Notice how I also actually use customize-set-variable, not setq: the variable modus-themes-custom-auto-reload is enabled by default, which means when I change the palette via customize-set-variable, the theme auto-reloads and shows the changes quickly. This is great to experiment with colors, so I recommend you use that, too.

At this point, we know how to

  • pick the toggle-able themes, i.e. which dark and light one F5 should switch between;
  • override color “pointers” (like fg-link) by referencing other named colors in the “common” palette;
  • override colors with absolute hex codes.

You can also use override presets (5.11.1 Palette override presets) to get back e.g. an overall “faint” look and feel. The documentation explains how to use that and even how to inherit from it while setting your own colors. Armed with that knowledge, my actual common palette settings were the following (“were”, because I removed inheriting from -faint eventually to give the new colors a real chance):

(customize-set-variable
 'modus-themes-common-palette-overrides
 `(
   ;; Make the mode-line borderless and stand out less
   (bg-mode-line-active bg-inactive)
   (fg-mode-line-active fg-main)
   (bg-mode-line-inactive bg-inactive)
   (fg-mode-line-active fg-dim)
   (border-mode-line-active bg-main)
   (border-mode-line-inactive bg-inactive)

   ;; Inherit rest from faint style
   ,@modus-themes-preset-overrides-faint))

I’ll explain my choice for the mode-line color overrides in the next section.

Depending on when you read this, this will just work – on 2022-12-30 I reported an issue that the overrides wouldn’t merge properly. See there for a quick fix; I didn’t bother to put it here again because it was already fixed by Prot within the hour, but YMMV when you try to do crazy things with the themes.

Working with modus-themes color values

There’s no convenient way to get a single named color from the themes at the moment. The modus-themes-with-colors macro is clever, but at the time of writing, the documentation is sparse and only shows how you can use it to produce a list, i.e. use the macro for reading the variable names. On the mailing list, Prot shared more examples and eventually included mode-line color overrides in the docs based on our email exchange, which I believe serves as a better example.

To understand the modus-themes-with-colors macro, you need to feel somewhat comfortable with Elisp macros because you’ll be using lists eventually and need to escape variable names for the colors inside these.

Check out these links if you need a refresher for the backquoted `(...) list form and how the comma splicing works within macro expressions:

With that, you can use theme color variables like so:

(modus-themes-with-colors
  (set-face-attribute 'tab-bar nil :background bg-main))

The macro makes all theme variables like bg-main available as locally bound variables.

The actual use within backquoted lists is a bit different, as you’ll see next.

Overriding the mode-line even more

Above, I shared my mode line color settings: commonly, I use the background color in the inactive window (to blend it in), and a light contrast in the active window. I tweaked the bg-inactive color of the light theme to be even less dark, so it’s a more subtle tonal contrast. (And IMHO less ugly.)

These changes aren’t all, though. I also apply a box to the modeline to make it appear larger (and add whitespace around the text). The box should have the appropriate color to appear like an extension of the background, so I need to reference the mode-line background colors of the theme when adding a box.

The inactive window and the mode line have the same light gray color to blend in. The mlscroll indicator is a bit stark, still, but this is WIP.

With an improved implementation suggested by Prot, I’m now using this:

(defun ct/modus-themes-customize-mode-line ()
  "Apply padding to mode-line via box that has the background color"
  (modus-themes-with-colors
    (custom-set-faces
     `(mode-line ((,c :box (:line-width 10 :color ,bg-mode-line-active))))
     `(mode-line-inactive ((,c :box (:line-width 10 :color ,bg-mode-line-inactive)))))))
(add-hook 'modus-themes-after-load-theme-hook #'ct/modus-themes-customize-mode-line)

You might wonder what ,c means: it inlines the variable c’s value. What is c, then? Well, it’s locally bound in the modus-themes-theme function, and its value is '((class color) (min-colors 256)). This is actually scoping when the theme’s colors are applied: in a color environment with at least 256 colors available. There are no constructs in the modus-themes for e.g. terminals that only support 16 colors, but Emacs’s faces do support settings that are scoped to different environments (with t as the fallback). So this tells Emacs to only use these faces if it makes sense, and as users we need to remember to prepend this scoping variable.

Overriding tab bar faces and colors

With that tool as a good stand-in for the old modus-themes-color function, I could rewrite my tab-bar overrides to this form:

(defun ct/modus-themes-tab-bar-colors ()
  "Override tab faces to have even less variety"
  (modus-themes-with-colors
    (custom-set-faces
     `(tab-bar ((,c
                 :height 0.8
                 :background ,bg-main
                 :box nil)))
     `(tab-bar-tab ((,c
                     :background ,bg-main
                     :underline (:color ,blue-intense :style line)
                     :box (:line-width 2 :style flat-button))))
     `(tab-bar-tab-inactive ((,c
                              :background ,bg-main
                              :box (:line-width 2 :style flat-button)))))))
(add-hook 'modus-themes-after-load-theme-hook #'ct/modus-themes-tab-bar-colors)

If you recall my previous tab-bar related posts, you will notice I am now referencing the faces 'tab-bar etc. directly instead of changing modus-* tab bar colors.

There used to be custom modus-themes faces like modus-themes-tab-backdrop. These are now gone in v4. In effect, they’re “inlined” when the merged palette specificiations are applied by the modus-themes-theme call which I already showed above. It maps over a list of face names and applies their style; for tabs, this is what’s going on:

;; modus-themes.el
(defconst modus-themes-faces
  '(
    ;; ...
    `(tab-bar ((,c :inherit modus-themes-ui-variable-pitch :background ,bg-tab-bar)))
    `(tab-bar-tab-group-current ((,c :inherit bold :background ,bg-tab-current :box (:line-width -2 :color ,bg-tab-current) :foreground ,fg-alt)))
    `(tab-bar-tab-group-inactive ((,c :background ,bg-tab-bar :box (:line-width -2 :color ,bg-tab-bar) :foreground ,fg-alt)))
    ;; ...
   )
  "Face specs for use with `modus-themes-theme'.")

A downside of v4 in its current form is that I couldn’t find a way to change the :box. So directly overriding the actual faces via set-face-attribute was my best idea; and Prot’s suggestion to use custom-set-faces made this even better. (The default application of boxes to the tab bar is “opt-out”, one could say, and requires more cumbersome overrides.)

So while I could use the palette variables to change the color of tabs, I couldn’t remove the box, and I personally opted for doing all overrides in my custom function instead of setting the palette colors for bg-tab-bar and bg-tab-curent first, and then still override the box declaration later. This way, it’s all in one place and easy to revert. But that’ just my opinion.

In summary

Printed below is my current configuration of the theme itself in full. On top, there are the hook-based overrides for a couple of things: the mode-line, the tab-bar, the mlscroll indicator, and neotree fonts. I’m also using the latest HEAD revision from Prot’s git repository instead of the latest MELPA release because things are moving so fast, but commented-out the :load-path option to share it here.

(use-package modus-themes
  ;; :load-path "~/.emacs.d/src/modus-themes"
  :ensure
  :demand
  :init
  (require 'modus-themes)

  (setq modus-themes-to-toggle '(modus-operandi-deuteranopia modus-vivendi-deuteranopia))
  (setq modus-themes-italic-constructs t
	    modus-themes-bold-constructs t
	    modus-themes-variable-pitch-ui t
	    modus-themes-mixed-fonts t)

  ;; Color customizations
  (setq modus-themes-prompts '(bold))
  (setq modus-themes-completions nil)
  (setq modus-themes-org-blocks 'tinted-background) ;'gray-background)

  ;; Font sizes for titles and headings, including org
  (setq modus-themes-headings '((1 . (light variable-pitch 1.5))
                                (agenda-date . (1.3))
                                (agenda-structure . (variable-pitch light 1.8))
						        (t . (medium))))
  ;; Theme overrides
  (customize-set-variable 'modus-themes-common-palette-overrides
                          `(
                            ;; Make the mode-line borderless
                            (bg-mode-line-active bg-inactive)
                            (fg-mode-line-active fg-main)
                            (bg-mode-line-inactive bg-inactive)
                            (fg-mode-line-active fg-dim)
                            (border-mode-line-active bg-inactive)
                            (border-mode-line-inactive bg-main)

                            ;; macOS Selection colors
                            (bg-region "mac:selectedTextBackgroundColor")
                            (fg-region "mac:selectedTextColor")
                            ))
  (customize-set-variable 'modus-operandi-deuteranopia-palette-overrides
                          `(
                            ;; More subtle gray for the inactive window and modeline
                            (bg-inactive "#efefef")))
  (customize-set-variable 'modus-vivendi-deuteranopia-palette-overrides
                          `(
                            ;; More subtle gray for the inactive window and modeline
                            (bg-inactive "#202020")))

  ;; Quick fix: Don't load immediately, but during after-init-hook so all other modifications further down can be prepared
  (defun ct/modus-themes-init ()
    (load-theme (car modus-themes-to-toggle)))

  :bind ("<f5>" . modus-themes-toggle)
  :hook (after-init . ct/modus-themes-init))

How to Use SF Symbols in Emacs (for Tab Numbers)

Early this year, I shared my setup to get tab numbers in the form of Unicode characters like “”.

This week, I reduced the font size in the tab bar to 80% of the main font and make the tab bar stand out less in general. The numbers in circled didn’t look so great then anymore, and the baseline looked off.

I know that SF Symbols has a couple of numbers in various boxes, so I wanted to try these. SF Symbol’s number in a circle render much sharper than the old unicode numbers, but I actually prefer the numbers in boxes.

For some numbers, like "4", there are even alternative styles where the upper part of the number is open

Glyphs for special characters like emoji are taken from fallback fonts, like the "AppleEmoji" font, or "ZapfDingbats" for other special characters. It’s the same for SF Symbols: after some digging in how the macOS text system treats SF Symbol code points, I found that they are displayed using the “SFCompact” font family.

And sure enough, that also works in Emacs: Change the text’s ‘face’ to "SF Compact" and then you can copy and paste SF Symbols, and they’ll actually render.

(defface ct/tab-bar-numbers
  '((t
     :inherit tab-bar
     :family "SF Compact"
     :weight light))  ;; Looks better at smaller sizes IMHO than regular
  "Face for tab numbers in both active and inactive tabs.")

In my case I don’t want San Francisco Compact for the whole tab bar; that’s too, well, compact for my taste. So the following addresses how I limit this to the actual number, demonstrating the use of propertize to set the face of a substring along the way.

Update 2022-12-31: I figured out that SF Pro Display also works. Every “SF Pro” font might work – but SF UI Display doesn’t, which was my variable pitch font of choice until now. SF Mono doesn’t work, either. So you can’t simply change your fonts to the SF family and paste symbols in your text just yet. (A minor mode would help that applies an SF Pro font to characters in the SF Symbols range of Unicode characters.)

In my original post, I defined ct/tab-bar-tab-name-format-default and used it as the “tab name format function” that produces a (styled) string for each tab. It’s set via tab-bar-tab-name-format-function.

Before we get there, here’s the new set of numbers:

(defvar ct/box-numbers-alist
  '((1 . "􀃊")
    (2 . "􀃌")
    (3 . "􀃎")
    (4 . "􀘙")
    (5 . "􀃒")
    (6 . "􀑵")
    (7 . "􀃖")
    (8 . "􀃘")
    (9 . "􀑷")
    (0 . "􀃈"))
  "Alist of integers to strings of SF Symbols with numbers in boxes.")

These likely don’t render outside of macOS at all – but don’t fret, they also don’t render in my monospace font in Emacs, either :)

You have to trust me that these unicode graphemes correspond to the “number in a box” SF Symbol. Here’s a screenshot of a manually propertized string for these numbers in fundamental-mode (where, unlike programming modes, the properties remain):

With that in place, here’s the function to render tab names:

(defun ct/tab-bar-tab-name-format-default (tab i)
  (let ((current-p (eq (car tab) 'current-tab)))
    (concat
     ;; First, add the tab number with a custom face
     (propertize
      (when (and tab-bar-tab-hints (< i 10)) (alist-get i ct/box-numbers-alist))
      'face 'ct/tab-bar-numbers)
     ;; Add a space (unstyled)
     " "
     ;; Add tab name with the face returned by tab-bar-tab-face-function
     (propertize
      (concat (alist-get 'name tab)
	          (or (and tab-bar-close-button-show
			           (not (eq tab-bar-close-button-show
				                (if current-p 'non-selected 'selected)))
			           tab-bar-close-button)
		          ""))
      'face (funcall tab-bar-tab-face-function tab))
     ;; Add trailing space (unstyled)
     " ")))

The result, with the actual tab font applied only to the tab’s actual name, but neither to the spaces nor to the leading number, I can change the default tab-bar-tab and tab-bar-tab-inactive faces to e.g. use underline decorations without looking like crap.

I’m okay with the result:

The tab’s don’t jump out at me but fade into the background a bit more. That’s quite nice for a change!

One thing that somewhat annoys me is that at 80% height and variable width, the battery indicator and clock in the top-right corner don’t right-align properly. Then again, since macOS Monterey, I can’t remove the system clock from the main menu anymore, so I might as well remove the Emacs clock completely.

For completion, here’s the function I use to adjust the tab colors within the modus-themes colors:

(defun ct/modus-themes-tab-bar-colors ()
  "Override `modus-themes-tab-*' to have even less variety"
  (let* ((bg-color (modus-themes-color 'bg-main))
         ;; Additional padding between tabs
         (box `(:line-width
                (2 . -1)  ;; -1 for no vertical space
                :color ,bg-color :style flat-button))
         (active-accent-color (modus-themes-color 'blue-active)))
    (set-face-attribute 'tab-bar nil
                        :height 0.8)
    (set-face-attribute 'modus-themes-tab-backdrop nil
                        :background bg-color
                        :box nil)
    (set-face-attribute 'modus-themes-tab-inactive nil
                        :background bg-color
                        :box box)
    (set-face-attribute 'modus-themes-tab-active nil
                        :background bg-color
                        :underline `(:color ,active-accent-color :style line)
                        :box box)))
(add-hook 'modus-themes-after-load-theme-hook #'ct/modus-themes-tab-bar-colors)

Update 2022-12-31: Álvaro Ramírez
created an SF Symbol picker
that’s super nice! Its job is to insert the symbol names for images in e.g. SwiftUI code. But this could also be adapted to work like Emoji pickers, inserting the symbol, not its name; that would require your font to be an SF Pro variant, though.

Better org-mode Agenda display-buffer-alist Settings

In the previous post I showed some settings to manage a tab dedicated to Org Agenda and its related org files.

I also mentioned that I didn’t like how that tab was actually handled, e.g. when I wanted to break out into other tabs.

The following settings work much better for me, 99% thanks to a comment by @dme.

Org Agenda with a split at default widths.

The behavior now is this:

  • Org Agenda shows in the “Org Files” tab exclusively. It’s a dedicated window so that it stays open when I interact with the agends: selecting items on the agenda will force a split window.
  • Org Agenda-related .org files, i.e. the details that will show when I select something from the agenda, will prefer to share the same split instead of making a new split every time.
  • If I’m alreayd in another tab, i.e. not the “Org Files” tab, visiting an .org file, then switching buffers to another org buffer will show the buffer in the current window. (Previously, it would take me to the “Org Files” tab and add a new split there, which was quite jarring.)
(defun ct/display-buffer-org-agenda-managed-p (buffer-name action)
  "Determine whether BUFFER-NAME is an org-agenda managed buffer."
  (with-current-buffer buffer-name
    (and (derived-mode-p 'org-mode)
         (member (buffer-file-name) (org-agenda-files)))))

(add-to-list 'display-buffer-alist
             `("\\*Org Agenda\\*"
               (display-buffer-in-tab  ;; Make sure to use the "Org Files" tab
                display-buffer-reuse-mode-window)
               (ignore-current-tab . t)
               (tab-name . "Org Files")
               (window-width . ,ct/org-agenda-width)
               (dedicated . side)  ;; Make the Agenda a dedicated side-window
               (side . left)       ;; to the left so it always stays open.
               (inhibit-same-window . nil)))
(add-to-list 'display-buffer-alist
             '(ct/display-buffer-org-agenda-managed-p
               (display-buffer-reuse-mode-window  ;; Prioritize reuse of current window
                display-buffer-in-tab)            ;; over switching to the Org tab.
               (tab-name . "Org Files")))

I’m pretty happy with thse settings since the original post 3 weeks ago!


→ Blog Archive