I'm Thinking About the Gemini Protocol a Lot

In the past couple of months, I’ve been thinking about the Gemini Protocol on and off. As a protocol, it’s a SSL/TLS-enabled text transfer protocol. The stuff it’s supposed to transfer is text files sorta similar to Markdown. And the client is supposed to render the result.

It sits somewhere between Gopher and HTTP.

If HTML is the language of the web we know via HTTP, Gemini’s text/gemini plain text result is the language of the Gemini protocol.

Unlike Markdown, which was designed to translate to HTML nicely and be human-readable at the same time, text/gemini doesn’t translate to HTML and is only made to be read the way it’s written; plus it’s made to be simpler to write parsers for.

I’m not thinking about the protocol implementation. I’m thinking about this plain text part.

I’m a huge fan of Markdown to write everything – my notes and my website content and my book manuscripts alike. But the text/gemini specs are captivating, too: they decide to not allow inline links at all. If you want to write a link, it must be written on one line per link. The whole plain text format is line-oriented. Here’s an example of a few links:

=> gemini://example.org/
=> gemini://example.org/ An example link
=> gemini://example.org/foo	Another example link at the same host
=> foo/bar/baz.txt	A relative link
=> 	gopher://example.org:70/1 A gopher link.

Clients typically seem to render this as a list of links where “An example link” is the click-able anchor text, and the target URL is not shown on screen. You know, just like HTML links, but more restrictive.

DistroTube.com in a HTTP browser to mimick the Gemini rendering

If you’re curious how that scales, check out Derek Taylor’s DistroTube.com page that is nowadays a rendered HTML output of his “Gemini Capsule”. (It was one of Derek’s videos that brought the topic to my attention.)

I am drawn to “Gemini” the topic, and text/gemini the format, because its promise of simplicity is still appealing. Just text; that’s nice. And the one-link-per-line policy resembles how I write my notes. Inline links are too noisy for my taste when I’m writing and consuming the plain text notes as they are. Rendering text to e.g. HTML is a different thing; but for notes, I often prefer to have an annotated reference per line.

The whole Gemini topic is some form of escapism, I think. And that’s not bad. Escaping the web of 2021 that is ridden with ads, full of cookie consent banners, and where … ‘weird’ platforms dominate the web traffic, it’s perfectly understandable that folks want to go someplace else. A Gemini Capsule might be just that thing.

It’s a nerdy past-time activtiy. You’ll find cool people in Gemini Space, for sure. But some say this is the future of the web – but the plain text world never appealed to the main stream users once GUIs game along. So don’t plan to transfer all your traffic over to gemini:// anytime soon, anticipating that the HTTP web is going to go away.

See also (in a list of links, one per line):

How to Print on macOS With An NSTableView and Customize the Result

So a client of mine recently asked how he could customize his app’s printing.

The app’s main window there mostly showed tabular data, so when you’re inside the NSTableView and hit ⌘P to print, the table view will redirect its draw(_:) output to the print dialog. That’s how you get printing for free.

Note: Since NSView.draw(_:) is the basis for the printed output, CALayer settings will be effectively ignored.

How to customize how a view is drawn when printed

If you want to customize the printed output, you can check which NSGraphicsContext is active when you override the drawing routine. Adapted from the old Apple docs:

class MyView: NSView {
    override func draw(_ dirtyRect: NSRect) {
        if NSGraphicsContext.currentContextDrawingToScreen() {
            // draw for screen
        } else {
            // draw for print
        }
    }
}

How to customize the view hierarchy that’s printed

My client wanted to customize the printed output beyond the drawing routine. The default table view drawing is fine. But he wanted to prepend a descriptive text before the table. The page headers won’t do because they repeat on every page. The printed page should contain the table view and a multi-line label that’s only visible when printing.

Printing relies on NSView, so we can use NSStackView to lay out contents on the page for printing. This requires a rebinding of the print shortcut from the system default to any custom @IBAction function so we can handle the command ourselves.

Create a NSPrintOperation programmatically

That changes the question from “how can we customize drawing a view” to “how can we define the content on the page”. This in turn boils down to bringing up the printing panel with custom content like this:

let sheetHostingWindow: NSWindow = ...
let viewToPrint = NSStackView()
// ... prepare the stack view ...
let printOperation = NSPrintOperation(view: viewToPrint)
printOperation.runModal(
    for: sheetHostingWindow,
    delegate: nil,
    didRun: nil,
    contextInfo: nil)

That’s it – create a NSPrintOperation and then show the system printing panel for the print operation.

I whipped up an example project with the resulting code. Check it out to see everything.

So if you ever created a view hierarchy programmaically, you can easily fill in the gaps to prepare the view to be printed. Note that you can also load a view hierarchy from Nib and print that. Either way, make sure the Auto Layout constraints fill the page.

Prepare programmatically created views for printing

I found that the main view that’s going to be printed produces the best output if you set its frame to the page size, excluding the page margins. This information can be changed in the system default “Print Setup” dialog, for example. Again, I got the idea from the archived docs:

extension NSPrintInfo {
    var availableContentSize: NSSize {
        NSSize(width:  paperSize.width  - leftMargin - rightMargin,
               height: paperSize.height - topMargin  - bottomMargin)
    }
}

// ...

let initialFrameForPrinting = NSRect(
    origin: .zero,
    size: pageContentSize)
let stackView = NSStackView(frame: initialFrameForPrinting)
// set up view and then create NSPrintOperation as seen above

I’m not sure why setting the initial frame to the available page size helps, since the view easily resizes and is printed onto multiple pages as needed. But for smaller stack views, this will try to fill the available page without zooming-in on the content. This could be a quirk in how the printed view is embedded into the page, maybe Auto Layout isn’t used when printing and that affects the result, maybe it’s a stupid oversight somewhere else in the code – but if you run into a similar problem, here you go.

Armed with this knowledge, you can now prepare your custom view hierarchy for printing and start the print sheet with it.

How to print a NSTableView

Print dialog with a wrapping label that's only part of the page, not the app's window

In the sample project, I’ve added a multi-line label just before the table view. And to print the actual table view, I added a new table view for printing. So the stack view that’s being printed has 2 arranged subviews.

Printing a custom view hierarchy with the same table data that you see on screen won’t work if you move the on-screen table view over. I tried, that’s hacky and looks weird. When you take the tableView from your app window and put it into the stack view for printing, it’ll be removed from the app window. Any view can only have 1 parent view.

Programmatically creating a new NSTableView instance worked best. This required a couple of adjustments to the approach.

The client wants to keep Storyboards. So the table view on screen is set up in a Storyboard, the table view on the page is set up in a Nib or programmatically. (I chose the programmatical route to mix things up and show how it’s done.)

In order to have the on-screen and the for-print table views populate from the same data source, we can actually reuse the view controller that is the NSTableView.delegate and dataSource. That will prepare the table data, but we do have to move the creation of NSTableViewCells all into the code. The cells cannot be configured in the Storyboard alone: the Storyboard references aren’t known to the programmatically created table view, and adding this connection sounded like more trouble than it was worth.

Instead of pointing the programmatically created table view to the cells in the Storyboard, I made the table view from the Storyboard obtain its cells programmatically. To get an experience that’s pretty close to the stuff you see in Nibs or Storyboards, use this, cobbled together from docs and StackOverflow of course:

extension NSUserInterfaceItemIdentifier {
    static let cell = NSUserInterfaceItemIdentifier("my_custom_cell")
}

class TableCellView: NSTableCellView {
    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        self.identifier = .cell

        let textField = NSTextField.label()
        textField.autoresizingMask = [.width, .height]
        self.textField = textField  // is an 'unsafe' reference
        self.addSubview(textField)  // creates a 'strong' reference

        textField.bind(
            NSBindingName.value,
            to: self,
            withKeyPath: "objectValue",
            options: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension NSTextField {
    /// Return an `NSTextField` configured like one created
    /// by dragging a “Label” into a storyboard.
    class func label(title: String = "") -> NSTextField {
        let label = NSTextField()
        label.isEditable = false
        label.isSelectable = false
        label.textColor = .labelColor
        label.backgroundColor = .controlColor
        label.drawsBackground = false
        label.isBezeled = false
        label.alignment = .natural
        label.controlSize = .regular
        label.font = NSFont.systemFont(
            ofSize: NSFont.systemFontSize(for: .regular))
        label.lineBreakMode = .byClipping
        label.cell?.isScrollable = true
        label.cell?.wraps = false
        label.stringValue = title
        return label
    }
}

The view controller in turn relies on the Cocoa Bindings of NSTableCellView.objectValue to the text field and has very simple data source and delegate implementations:

extension ViewController: NSTableViewDelegate, NSTableViewDataSource {
    func numberOfRows(in tableView: NSTableView) -> Int {
        return items.count
    }

    func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
        return items[row]
    }

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        return tableView.makeView(withIdentifier: .cell, owner: self)
            ?? TableCellView()
    }
}

This works for the table view from the Storyboard and the custom table view for printing. If you have multiple columns, you have to adjust everything, of course.

To top things off and document everything in the example project, here’s the table view creation code as well:

let tableViewForPrint = NSTableView(frame: .zero)
let soleColumn = NSTableColumn()
soleColumn.resizingMask = .autoresizingMask
tableViewForPrint.columnAutoresizingStyle = .lastColumnOnlyAutoresizingStyle
tableViewForPrint.allowsColumnSelection = false
tableViewForPrint.allowsColumnResizing = true
tableViewForPrint.addTableColumn(soleColumn)
tableViewForPrint.selectionHighlightStyle = .none
tableViewForPrint.allowsEmptySelection = true
if #available(macOS 11.0, *) {
    // Avoid Big Sur's default horizontal padding in print-outs
    tableViewForPrint.style = .plain
}

tableViewForPrint.dataSource = self
tableViewForPrint.delegate = self
tableViewForPrint.reloadData()

Some alternative approaches to creating a new table view for print:

  • Use the on-screen table view’s draw(_:) method and call it where appropriate. Think of it like a canvas: this could be a custom NSView subclass that forwards intrinsicContentSize and draw(_:) to the table view to replicate the pixel output.
  • Create a copy of the on-screen table view. I tried copy() and serializing and deserializing to NSData, but that didn’t work, and it was so weird I abandoned it. But maybe this could work?
  • Add everything you want to print to the NSWindow, but set isHidden = true, and then un-hide when you print. Could be a cheap cop-out that produces visual glitches.

Conclusion

To wrap things up:

  • If you want to customize the drawing routine of a custom view when it’s being printed, you can check NSGraphicsContext.currentContextDrawingToScreen().
  • If you want to print different content than is shown in the app, you may want to create a custom view hierarchy for print. NSStackView is an excellent choice for vertical layouts.
    • If you also want to share the table date on screen and on paper, a good choice is to create a new NSTableView with the same data source and add this to the printed view hierarchy.

See also:

Single Function to Center Emacs Window on Screen

Fellow Emacs-user Göktuğ Kayaalp condensed my frame centering stuff into one function as I mentioned one could do, and then improved it by adding a condition for full-screen frames. This is probably the last post on this topic, ever, because what else could be said. Right?!

It’s so nice, I want to share the code, reformatted a bit. And Göktuğ didn’t add the binding of frame to selected-frame as a fallback, so I added that back in, plus longer docs.

(defun my/frame-recenter (&optional frame)
  "Center FRAME on the screen.
FRAME can be a frame name, a terminal name, or a frame.
If FRAME is omitted or nil, use currently selected frame."
  (interactive)
  (unless (eq 'maximised (frame-parameter nil 'fullscreen))
    (let* ((frame (or (and (boundp 'frame) frame) (selected-frame)))
            (frame-w (frame-pixel-width frame))
            (frame-h (frame-pixel-height frame))
            ;; frame-monitor-workarea returns (x y width height) for the monitor
            (monitor-w (nth 2 (frame-monitor-workarea frame)))
            (monitor-h (nth 3 (frame-monitor-workarea frame)))
            (center (list (/ (- monitor-w frame-w) 2)
                          (/ (- monitor-h frame-h) 2))))
      (apply 'set-frame-position (flatten-list (list frame center))))))

(add-hook 'after-init-hook #'my/frame-recenter)
(add-hook 'after-make-frame-functions #'my/frame-recenter)

I wasn’t aware that instead of add-to-list a hook for after-make-frame-functions would do the job, too. That’s what you get from following some of the docs on the web to the letter. The after-init-hook is a great addition, too.

Since I have this auto-centering functionality in place, I find myself opening and closing new frames quite a lot. For most of 2020, I have been using a single frame, often in full-screen mode, and then have been splitting panes and experimenting with workspaces and tabs. Using the macOS windowing system instead integrates much easier with the rest of my computer life. (Though I still envy the Linux folks with their tiling window managers who use multiple tabs in emacs. Looks so tidy!)

You can also run this function in the terminal no problem. It will just exit with t and you won’t see any effect. The important part is that there are no errors when you’re not in a GUI.

See also:

Multi-Monitor Compatible Code to Center Emacs Frames on Screen

When centering my Emacs windows (aka ‘frames’) on my monitors, I noticed that with 2 monitors active, the computation doesn’t work: It doesn’t center in the main monitor; it centers in the area of both monitors combined. That’s not what I want.

Here’s a fix and an explanation of the problem.

Why 2 monitors caused trouble

Looking at the documentation for display-pixel-width, I learned that this is intended.

There’s a distinction between “monitor” and “display” in Emacs that was absolutely not what I expected. “Display” is the total available space, while “monitor” is a single device.

To get per-monitor geometry information, the documentation advices the reader to look at display-monitor-attributes-list. That shows information for all monitors known to the system, though, so you’d have to find the right onw; looking at the docs there accidentally brought up a related function, frame-monitor-attributes, that limits the information to the monitor of the current Emacs frame. Since I want to center a newly created frame on screen, this works perfectly.

Using the monitor workarea

The result of (frame-monitor-attributes) for the main monitor is a list of attributes and value like this:

((geometry 0 0 3440 1440)
 (workarea 0 25 3440 1415)
 (mm-size 801 335)
 (frames #<frame  *Minibuf-1* 0x7f9c773b3418>)
 (source . "NS"))

This has the added benefit of defining a ‘workarea’ next to ‘geometry’ that excludes the space taken up by main menu. So I don’t need to compute this area myself.

I never worked with an attribute list like that, so I had to look up a couple of functions for hash maps, propertly lists, dictionaries etc. until one eventually worked. In the process I noticed there also is the function (frame-monitor-workarea) that does the job, so I’ll be using that. In case you’re curious how to unpack an attribute, though, try: (alist-get 'workarea (frame-monitor-attributes)).

Now the result of this is the list of values:

(0 25 3440 1415)

To get to the 4th item in this list, i.e. the usable height, cadddr is used. That’s a shorthand:

  • 3x cdr calls, which would be cdddr. In modern languages, we’d be calling this “drop first”. It returns rest of the list sans the first element.
  • That on its own would produce a list with 1 item, (1415). If you’re new to Lisp, thats basically an array with 1 element.
  • So we add a car call that fetches the tip of the list. For example (car '(foo bar fizz buzz)) returns foo. This gives us the number.

To unpack the 3rd item in this list, i.e. the usable width, we use caddr – note it does one less cdr call, so the temporary list result is (3440 1415), and then the car fetches the number, 3440.

Update 2021-06-11: You can also use (nth INDEX LIST). I prefer that because even though it looks like I’m a Lisp noob, it’s easier to figure out which element you get.

Armed with this knowledge, I’ve added these two functions to get the width and height, including proper documentation:

(defun ct/frame-monitor-usable-height (&optional frame)
  "Return the usable height in pixels of the monitor of FRAME.
FRAME can be a frame name, a terminal name, or a frame.
If FRAME is omitted or nil, use currently selected frame.

Uses the monitor's workarea. See `display-monitor-attributes-list'."
  (cadddr (frame-monitor-workarea frame)))

(defun ct/frame-monitor-usable-width (&optional frame)
  "Return the usable width in pixels of the monitor of FRAME.
FRAME can be a frame name, a terminal name, or a frame.
If FRAME is omitted or nil, use currently selected frame.

Uses the monitor's workarea. See `display-monitor-attributes-list'."
  (caddr (frame-monitor-workarea frame)))

If you never ever use these functions anywhere else, the following would do the trick, too, as local variables in a function:

;; ...
  (let* ((workarea (frame-monitor-workarea frame))
          (width (caddr workarea))
          (height (cadddr workarea)))
    ;; use width and height here
    )
;; ...

I guess with more Elisp experience, I’d be using these directly, but at the moment I benefit from dedicated functions with documentation to encapsulate concepts like “unpack the width from a monitor workarea value list”.

Update 2021-06-11: Göktuğ Kayaalp shared a condensed single-function approach that I tweaked a bit and shared in another post.

Resulting code to center a frame on screen

Here is the complete, fully updated version:

(defun ct/frame-monitor-usable-height (&optional frame)
  "Return the usable height in pixels of the monitor of FRAME.
FRAME can be a frame name, a terminal name, or a frame.
If FRAME is omitted or nil, use currently selected frame.

Uses the monitor's workarea. See `display-monitor-attributes-list'."
  (cadddr (frame-monitor-workarea frame)))

(defun ct/frame-monitor-usable-width (&optional frame)
  "Return the usable width in pixels of the monitor of FRAME.
FRAME can be a frame name, a terminal name, or a frame.
If FRAME is omitted or nil, use currently selected frame.

Uses the monitor's workarea. See `display-monitor-attributes-list'."
  (caddr (frame-monitor-workarea frame)))

(defun ct/center-box (w h cw ch)
  "Center a box inside another box.

Returns a list of `(TOP LEFT)' representing the centered position
of the box `(w h)' inside the box `(cw ch)'."
  (list (/ (- cw w) 2) (/ (- ch h) 2)))

(defun ct/frame-get-center (frame)
  "Return the center position of FRAME on it's display."
  (ct/center-box (frame-pixel-width frame) (frame-pixel-height frame)
                 (ct/frame-monitor-usable-width frame) (ct/frame-monitor-usable-height frame)))

(defun ct/frame-center (&optional frame)
  "Center a frame on the screen."
  (interactive)
  (let* ((frame (or (and (boundp 'frame) frame) (selected-frame)))
         (center (ct/frame-get-center frame)))
    (apply 'set-frame-position (flatten-list (list frame center)))))

Now it works, without any hacks and manual macOS menu offsetting.

Also see the shorter single-function variant.

Automatically Center New Emacs Windows (Aka Frames) on Screen

When I open a new GUI window of Emacs on macOS (which Emacs calls frame) it’s positioned in the top-left corner. Since I have an ultrawide monitor at my desk, that’s pretty annoying. Unlike regular macOS apps, Emacs doesn’t remember where I dragged the last NSWindow to, so it doesn’t spawn new windows next to that. It also doesn’t stagger them like NSDocument-type apps usually do.

So I took some code from the internet to determine the window’s center position and the screen’s center position and then align both. It’s part of a larger Gist that would even tile your windows on the screen, but I only want new windows to always start in the middle. I can move them ouyt of the way if needed.

So the whole code is from https://gist.github.com/ieure/80638, only shortened for this particular use case.

Update 2021-06-09: The code below only works on single-monitor setups. If you have 2+ monitors, this unintentionally tries to center the window in the area described by all monitors combined. I published a fixed version that I encourage you to use instead!

(defun ct/screen-usable-height (&optional display)
  "Return the usable height of the display.

Some window-systems have portions of the screen which Emacs
cannot address. This function should return the height of the
screen, minus anything which is not usable."
  (- (display-pixel-height display)
     (cond ((eq window-system 'ns) 22) ;; macOS Menu Bar offset
           (t 0))))

(defun ct/screen-usable-width (&optional display)
  "Return the usable width of the display."
  (display-pixel-width display))

(defun ct/center-box (w h cw ch)
  "Center a box inside another box.

Returns a list of `(TOP LEFT)' representing the centered position
of the box `(w h)' inside the box `(cw ch)'."
  (list (/ (- cw w) 2) (/ (- ch h) 2)))

(defun ct/frame-get-center (frame)
  "Return the center position of FRAME on it's display."
  (let ((disp (frame-parameter frame 'display)))
    (ct/center-box (frame-pixel-width frame) (frame-pixel-height frame)
                   (ct/screen-usable-width disp) (ct/screen-usable-height disp))))
(defun ct/frame-center (&optional frame)
  "Center a frame on the screen."
  (interactive)
  (apply 'set-frame-position
         (let* ((frame (or (and (boundp 'frame) frame) (selected-frame)))
                (center (ct/frame-get-center frame)))
           ;; Flatten the X/Y list in `center` into a single list with `frame`
           ;; so this list can be applied as parameters to `set-frame-position`:
           `(,frame ,@center))))

Then to automatically center the frame after frame creation:

(add-to-list 'after-make-frame-functions #'ct/frame-center)

All these Emacs Lisp backtick-at-comma shorthands begin to confuse me. I’m fine with #'functionname to signity that this isn’t just a string, but a function name. But the commas, and the backticked list, and the at sign?! Geez.

If we skipped the shorthand characters and just wrote (list frame center), we’d get the elements (frame (top left)), because center itself is a tuple already. This essentially flattens the nested list.

That’s the thing with these quotes. They are not just shorthands. The result of (list frame center) is different from '(frame center). Both expressions produces a list, but if you call the list function, the variadic arguments are evaluated and their result is put into the list; if you use the single quote, you get a list of the literal elements. It’s equivalent to (list 'frame 'center), which we clearly didn’t want. The backquote allows to evaluate some elements of the quotes list with the , operator, and by evaluating the variables, we get their names in the original code.

Or we don’t use the quoted forms and have a straight-forward result.

Compare these:

  • Quoted: \(,frame ,@center)` packs a lot of info in characters that are harder to look up than functions;
  • Function calls (flatten-list (list frame center)) is very much readable.

Here’s how I’d really write it, putting the local variable bindings first and not inside the function call to put less lines between function name and parameter list:

(defun ct/frame-center (&optional frame)
  "Center a frame on the screen."
  (interactive)
  (let* ((frame (or (and (boundp 'frame) frame) (selected-frame)))
         (center (ct/frame-get-center frame)))
    (apply 'set-frame-position (flatten-list (list frame center)))))

Does 'Open Source' Lack Proper Gate-Keeping?

I was in a chat with Xah Lee and we ended up talking about “open source” for a short while. The trigger was that he shared quotes by Richard Stallman, one of which boiled down to: packages outside of GNU’s own package repository are not really part of Emacs.

I appreciate the context of the quote: folks should be preserving packages by putting them under the GNU stewardship. Then there’ll always be at least access to the package from the foundation’s maintainers. This should help prevent dead packages without updates for 10 years and no merged patches/PR’s. That’s a heart-warming idea.

What I cannot get behind is the dismissive implication that the world-wide ecosystem of hobbyist contributors sharing stuff on the web is “not part of Emacs”.

I mean, technically that’s right, their stuff doesn’t ship with Emacs. But I take “being part of” as a phrase of identifying with Emacs. Like fans of football (soccer for the less civilized folk) clubs identify with “their team”, even though they cannot kick a ball into the goal successfully if their life depended on it. So they are clearly not part of the football team of players. Yet they are part of something that’s closely associated, part of the fan base. And the team usually appreciates the dedication of their fans. There’s interaction between these groups. – In this sense, RMS’s statement sounds dismissive because it plays down the importance and impact of all the experiments that happen in code bases all over the world.

That’s where the chat between Xah and me started.

Open source culture changed

I really hope that Xah Lee writes up a linkable blog post/essay/article with his position so I can properly connect the pieces of the discussion for context.

Until then, since I don’t want to quote someone by their abbreviated chat messages, here’s my understanding of two larger branches of topics he brought up that could be totally missing his point:

  • Corporations benefit from open source (without paying back) and have incentives to get more people to do more work for free.
  • The open source culture changed for the worse
    • It used to be that ‘hackers’ knew how to code and appreciated all the work involved in maintaining and creating stuff.
      • This is associated with knowledge and respect of the craft.
    • GitHub trivializes collaborating on code.
      • Instead of knowledgeable hackers, you now have laypeople messing around.
        • Respect is gone, because everyone’s a contributor now?
      • GitHub made forking easy by prominently placing “Fork” buttons everywhere.
        • “Forking” used to be considered rather rude.
        • Now you’re encouraged to take code by forking any time.
        • This reduces respect.

There’s probably way more I’m missing in the outline, but it’s a chat, and it’s hard to elaborate there.

The “it used to be that only hackers contributed” line of thought sounds elitist to me. Again, I could be missing nuance. Let me explain why I think that’s an elitist position, how gate-keeping comes into play, and why I ultimately think it’s irrelevant.

It is elitist to wish the old days return by re-installing hurdles

What’s making it elitist?

  1. For one, it’s not just a description of a change of things. This is mostly a description: it used to be just hackers, now everyone has internet and a computer and clicks around on GitHub.
  2. But here, value statements are attached: the way it used to be was better. Now it’s worse, not just different.
  3. What makes it worse? The forking and lack of respect for the craft. What was the basis of respect? You had to know a lot more before you could participate. (E.g. send patches on mailing lists that nobody knows how to operate nowadays, and hope the maintainers apply your patch.) Now code is widely accessible.

The last point gives it an elitist spin: it’s not good that it’s simpler to participate. Knowledge and skill hurdles are/were useful. It’s bad that they are gone or lowered.

A term for this is gate-keeping. There clearly is value in gate-keeping in general: e.g. I wouldn’t want to hire the next best applicant for a job that requires skill, I want to screen applicants first and see if they fit, and gauge what they can achieve at the moment, then pick the best. There’s no point in demanding that anybody should have a chance at getting a particular job; there are requirements, formal and informal, that have to be met. So not all gate-keeping is bad. (Flip side: Not all gate-keeping is good, either.)

The open source love story of Marko

I personally think gate-keeping is not useful in the case of open source contributions, though. I applaud that my friend Marko now has a GitHub account and maintains a JavaScript plugin for a digital table top gaming system. He’s not a certified programmer, he’s a recently self-taught JavaScript/FoundryVTT author. People who want to help him improve the plugin aren’t coders, programmers, hackers, either – he gets PR’s on GitHub from laypeople who cobbled together something with questionable quality and style. I’m happy to help him figure out conventions of what’s useful, what helps keep code maintainable, and how to deal with GitHub PR’s that do 10 things at once but of which you only want to keep 2. It’s amazing that complete strangers self-organize on the internet to create this together.

Now that he tasted blood and enjoys the fiddling and customizing and coding, I’m pretty sure he’d continue maintaining his plugin if GitHub would shut down and people moved back to mailing lists. It’s more effort. It’s harder to keep taps on things. – There’s a reason online issue trackers evolved; they help organize stuff. And if nothing else, GitHub makes it easy to see patches/PR’s in context. Not every email app would be able to show patches or diffs properly. It’s way less convenient. And thus Marko might stay, but lots of other hobbyists might abandon ship because it’s too weird, too much trouble, whatever.

Modern tools make it easier, and thus make it possible to reach more people. In other words, it’s less scary to try to contribute in code.

Gate-keeping is a valid option for every project

I’m reading Working in Public, but didn’t yet process the book to take notes. It’s a treasure trove of citations I’d like to use in this context, but I’m keeping it vague for the moment until I had the time to extract all that.

In the book, maintainers of a popular open source repository eventually moved to GitHub because it was ‘expected’, since everyone used that instead of mailing lists. It doesn’t matter which repo that was. It’s just a stand-in for a compromise people make.

The compromise is: do I want to keep the process ‘pure’ and bare bones, or do I want to onboard more people? If I want more contributors, then I’m incentivized to use a popular code hosting platform. Nobody’s forced to do it. If you want to screen for 1337 haxx0rs, don’t use those platforms. So there is choice, and you as a maintainer can decide. Do you want to identify with people who self-compile their programs, or do you prefer to get more input (but also have more work dealing with that) by a more diverse range of people?

For example, org-mode’s code is self-hosted on a platform with similar convenience to GitHub and GitLab at https://code.orgmode.org/bzg/org-mode. Then there’s the updates overview site that aggregates mailing list messages, including support inquiries, but also [PATCH] and [BUG]-tagged messages that live outside of the self-hosted code platform (where issues, PR’s, etc. are disabled.)

Ironically, by the way, the self-hosted git app they use, Gogs, hosts its code on GitHub. Unlike GitLab, which dogfeeds its application, but also keeps a mirror on GitHub for whatever reason.

GitHub dominates, but it’s not the only thing that exists. Its dominating role makes it more likely that interested would-be contributors are well-versed working on GitHub and have a tough time sending you patches via email. With its critical mass, GitHub’s pull only gets stronger.

My personal values regarding all this

This is a blog, not a formal essay, so here’s my opinion.

I think the new tools produce a good change in the culture. They enable stories like Marko’s, where laypeople can collaborate and share the love for their hobby and get more proficient in the process. The learning curve has flattened a bit.

Since I like to code, and since I like to share, it pleases me that other people can do the same.

And I think that’s all there is to it: preference. We can choose which kind of collaboration we want.

Mailing list-based collaboration isn’t dead. It just pales in comparison to GitHub traffic. If you potentially want that level of attention, use popular platforms. (It’s the same with posting videos on YouTube vs other platforms. Your potential for more eyeballs is higher on YouTube.)

None of this implies that I’m okay with an attitude of entitlement towards maintainers. There are plenty of open source stories where strangers walz into a GitHub project page and demand changes to the code for their use case, then get angry and flame maintainers if nothing happens for a while. This is a flip-side of not appreciating the effort anymore. Angry trolls were on mailing list in the 90’s, too. Everyone with an email address can make a GitHub account and begin complaining. This is a problem we collectively need to figure out how to solve. It’s annoying, and maintainers who are affected by this can become disheartened. I do not think this is deserved; but it’s not easy to grow such a thick skin that you can shrug off the complaint and delete reactions like these.

Is there a problem gate-keeping would solve?

Say you maintain an open source project since the 1980’s and don’t want to change infrastructure. There’s nothing stopping you. You’re not inviting everyone and their grandmother to contribute by making browsing, reading, and patching the code less convenient. Your call.

Let’s return to the metaphor of football clubs and their fans: it doesn’t hurt the team if their fans put on their football dresses and meet once a week to kick a ball on a field. It’s amateur sports at best, it’s not pro football. It’s not the real thing, the thing that’s valued highly.

If laypeople can assemble projects on GitHub and invite similarly unskilled friends to participate, who’s being hurt?

Some pros and amateurs are using the same tools. (Same with ball-kicking, actually.) Doesn’t make the tool any worse. Your owning a good Japanese saw doesn’t devalue a carpenter’s saw.


There appears to be no real problem the way I understand things.

Am I missing something?

Are there other important factors I blatantly overlooked?

Please share in the comments below, respond via email, or on your own blog and send me a link.

Use macOS Native Highlight Colors for Emacs hl-line

In some modes in Emacs, the hl-line (‘hl’ for ‘highlight’) is used. That’s not intended to highlight the current line when you edit text; it’s used for things like feed readers and email, where you operate on items that are represented on lines. This highlight is way too subtle for my taste.

I use the very excellent dark and light [modus-themes][modus] in Emacs. The light theme has black text on white background, and that is pretty much what you see in macOS Finder, too, when you browse directories. In Finder, when you select something, you get the classic blue bakcground with white foreground color.

That’s what was missing from my life, so I overwrite the colors. Native GUI Emacs on macOS has named colors like the NSColors from AppKit, so I can just use selectedContentBackgroundColor and alternateSelectedControlTextColor. This could, in theory, also adapt to dark vs light mode, but on macOS, the color is actually the same for both modes. (At least from eyeballing it.)

Some random newsletter emails with the new colors

Here’s how I hook into dark/light mode changes to make sure the color is replaced:

(defun ct/theme-mac-os-hl-line ()
  "On macOS, use the system selection color combo. Should work for dark and light mode."
  (when (memq window-system '(mac ns))
    (set-face-attribute 'hl-line nil
                        :foreground "alternateSelectedControlTextColor"
                        :background "selectedContentBackgroundColor")))
(add-hook 'ns-system-appearance-change-functions #'ct/theme-mac-os-hl-line)

I also hook this up to modus-themes.

(add-hook 'modus-themes-after-load-theme-hook #'ct/theme-mac-os-hl-line)

Notmuch Mail and Compatible Emacs Theme Updates

This week, I seem to have a lucky streak in terms on Emacs customizations. I use Emacs to read and compose most of my email, using the excellent notmuch CLI tool and its accompanying Emacs package. I also really like Protesilaos Stavrou’s modus-themes in both light and dark mode.

Both packages have received updates in recent weeks, and Protesilaos has discovered the joy of notmuch for himself, resulting in his themes supporting even prettier mail styles.

This is what my tagged collection of unprocessed crash reports looks like in a tree-view of messages so I can see the message at the bottom and the list at the top

Doing email often is pretty unremarkable. But what I liked about notmuch that made me stick was the flexibility of its local tag database. With tags, you can implement a lot of different workflows.

The local database is, well, local, so to get some resemblance of order on my other devices (i.e. phone and tablet), I also have rules to move messages into IMAP folders:

# Move stuff tagged as "feed" into the Newsletters IMAP folder
# to clean up my Inbox on other devices, too.
notmuch tag +feed -- folder:Newsletters

Some of these rules can be set up on the server, too. I use FastMail and am very happy with their service, and they offer rules to file email into folders, but I don’t want to rely on their filing if I can reproduce the same effect in my client.

If you want to quit e.g. Gmail and get onto the FastMail train, you can use my referral link and get 10% off of your first year: https://ref.fm/u21056816 – I’ll then get a 50 cent discount for my paid plan, too.

Auto-File Newsletters

For example, I have a plain text ‘database’ to list all the origins of newsletters so that these are removed from my inbox automatically, and stuffed into a “feed” collection. These entries are all notmuch-search compatible terms, one per line, which means I can write both from:newsletter@foo.com and subject:MacSparky Newsletter (the latter happens to have the same “from” address as David Sparks’s regular email, so I couldn’t rely on the sender for the filtering).

Automate Email Actions

I also experimented with rules that checked for a to-be-deleted tag, and then made sure that the whole thread is deleted, not just one email from the thread. That was useful to quickly remove whole threads with one command, but I ultimately decided to move this responsibility to the email app and thus my manual control. Better select all email in a conversation and delete them all manually than having an auto-deleter remove too many.

Hey! Email Workflow

You can even try to replicate the Hey! workflow of email with notmuch. That’s what initially got me hooked, thanks to Vedang Manerikar. There’s a video from last year that shows his replica of the Hey! Imbox and feed settings, and the result is online as a Gist with configuration details.

The over-arching idea here is to remove stuff from your inbox that you don’t really need to see. Same with my crash logs. I need to process them, but not immediately. Can do this in bulk once a week or so. No need to see them until then.

Indie Support Weeks: Monodraw

I know that #IndieSupportWeeks were supposedly a thing that ended in early 2020, but I don’t see why we shouldn’t continue shouting-out to the devs of apps we use everyday.

Late in 2020, @Splattack on the Zettelkasten Forum brought up Monodraw – think OmniGraffle, but with ASCII box art!

It produces beauties like this:

 ┌─────────────┐
 │ NSTableView │────────────────────┐
 └─────────────┘                    │
        │                           │
        │                           │
        ▼                           ▼
┌───────────────┐          ┌─────────────────┐
│ NSTextStorage │◀─────────│ NSLayoutManager │
└───────────────┘          └─────────────────┘

If you can’t like to look at that, then I’m afraid we won’t be able to become friends :)

Now why should anyone care for diagrams using box drawing characters? It’s super useful in a plain text note-taking app environment to create semi-visual diagrams of hierarchies with clickable links!

Take this, for example:

┌────────────────────┐
│  [[201708031447]]  │
│ Role of NSTextView │
└──────────▲─────────┘
           │ controller for
 ┌──────────────────┐
 │ [[201708031442]] │
 │     Role of      │
 │ NSLayoutManager  │
 └──────────────────┘
           │ delegates to
 ┌─────────▼─────────┐
 │ [[201708031445]]  │
 │ Typesetter drives │
 │      layout       │
 └───────────────────┘

Inside the boxes are double-bracketed wiki links that point to other notes in my Zettelkasten note archive. I can click on these if I view the note in The Archive!

The same box drawing diagram inside of The Archive, which makes wiki links clickable

It’s still a hack, but it’s a hack that makes it possible to create visual diagrams of things in your personal knowledge database. I think this is super rad. And now I have to find a way to make The Archive not use >1.0x line heights to avoid the ugly gaps in the box drawings.

I don’t use Monodraw for simple relationship diagrams only, though. I use it’s beautiful capabilities to make mockups of user interfaces I can share in plain text documents, too.

Mockup of an app window with annotation in Monodraw

The crazy part is that Monodraw is a one-time purchase at 8.99€, so it’s a steal if you like box drawing.

Check out the trial version to see how the interactions work: like all good diagram applications, lines attach to boxes by default and you can move the connections around when you move the connected boxes. But with Unicode box drawing characters. Sweet!

Really, go check out Monodraw!

Using XCTUnwrap Instead of Guard in Unit Tests

In 2015, I sang praises for the guard statement to unwrap optional values in tests. But since then, we were blessed with XCUnwrap, which makes things even better in my book. I received a comment on the 2015 piece a couple weeks ago, so I figured I might as well document that things have changed in the meantime!

The original post had this code:

func testAssigningGroup_AddsInverseRelation() {
    insertFile()
    insertGroup()

    guard let file = soleFile() else {
        XCTFail("no file")
        return
    }

    guard let group = soleGroup() else {
        XCTFail("no group")
        return
    }

    // Precondition
    XCTAssertEqual(group.files.count, 0)

    file.group = group

    // Postcondition
    XCTAssertEqual(group.files.count, 1)
    XCTAssert(group.files.anyObject() === file)
}

With the new-ish XCTest helpers, we can write it like this:

func testAssigningGroup_AddsInverseRelation() throws {
    insertFile()
    insertGroup()

    let file = try XCTUnwrap(soleFile())
    let group = try XCTUnwrap(soleGroup())

    // Precondition
    XCTAssertEqual(group.files.count, 0)

    file.group = group

    // Postcondition
    XCTAssertEqual(group.files.count, 1)
    XCTAssert(group.files.anyObject() === file)
}

Back in 2015, tests couldn’t even be marked with throws. So this is a nice way to exit a test case early when a condition isn’t been met.


→ Blog Archive