Magit's Killer Feature #1: Commit Text Completion Based on the Diff

I’m using the Emacs git frontend (‘porcelain’) Magit for all my projects nowadays. I fire up GitUp (which is great) only to traverse the commit history visually.

Here’s one of the reasons:

With auto-completion framework company, I get completion suggestions when I type my commit message. These are based on the actual code diff of the commit.

That means when I introduce veryLongAndCumbersomeToTypeAttributeName, I can begin craft my commit message by typing this:

Introduce new attribute ver|

And company will suggest an auto-completion candidate veryLongAndCumbersomeToTypeAttributeName for the “ver” I’ve just typed.

That makes talking about the classes and attributes and functions that were modified or introduced in a commit so much simpler. No more copy and paste, no more typing mistakes.

Completion candidates starting with 'ver' for this blog post. (The blue-ish background at the bottom isn't a selection but an insertion; I have color-blind modus-themes settings enabled to not have to work with red/green there.)

“Acktshually, that’s not a Magit feature!”, someone might point out without being asked, thankyouverymuch, but that’s only part of the truth: Yes it’s 100% dependent on company being enabled to suggest completion candidates like this for me, but company wouldn’t suggest anything if Magit didn’t display the commit message next to the code diff!

@EnvironmentObject Is a Branched Global Variable

In SwiftUI, @EnvironmentObject is used to loosely couple any ObservableObject without directly passing it down the view hierarchy, e.g. via parameter injection.

Unlike Singletons and global variables, Environment Objects are local to view hierarchies: different branches in the hierarchy can maintain different object references.

It’s like a “branched global”. That term was coined by Dominik Hauser (@dasdom) on Twitter. That’s quite catchy!

Happy People = Mac Users

I talked about my “job” the other day and, again, pointed out that I’m making apps, but for Mac, not iPhone.

No, no, I also do iPhone. I just don’t like to, and avoid it if I can.

But using a Mac, that is fun, and working on a Mac is great. The machines are good, the OS is still good. That’s what I believe the most. For the things I’m interested in making, the Mac is a good platform. It’s a platform to get serious stuff done. And I can be a part in making the experience enjoyable and make “work” fell less like a chore. That’s what I’m interested in.

And I don’t believe that people should spend more time with their phones. I don’t want to use my iPhone more, and I don’t wish others use their iPhones more. So no iPhone apps, just more happy Mac apps. That’s a better world in my book.1

  1. The iPad is a bit different. It’s in the twilight zone between serious work horse and potential time sink. I ponder writing iPad apps more, but it’s still an afterthought. 

Link Your Website to Mastodon via Metadata

Since hackers and normies alike check out Mastodon nowadays, here’s an undocumented (as far as I can tell) variant to connect your website link to your Mastodon profile.

Update 2022-05-16: Dave Barr made a much nicer illustrated guide for this!

The general flow is this:

  1. Add your website link to your profile page.
  2. Add a link from your website to your Mastodon profile.

After a while, the Mastodon server will pick up this backlink and mark the website as “verified”. That’s supposedly indicating this is really your website, and you’re not an imposter.

The docs usually only recommend regular links of the form:

<a rel="me" href="https://mastodon.social/@ctietze">My Mastodon Profile</a>

But if you don’t want to plaster your web pages with clickable Mastodon links, metadata links work as well:

<link rel="me" href="https://mastodon.social/@ctietze" />

As I said, I couldn’t find this in the docs so I just tried and it works. Just takes a while to register.

Update 2022-05-10: The Mastodon docs don’t mention this, but I was informed that this is expected behavior according to the HTML spec – because a [hyperlink can be many things](https://html.spec.whatwg.org/#hyperlink], and a <link> tag semantically links the whole page, while an <a> link links the anchor text as context. Ah well, I cannot recommend trying to read the HTML specs, by the way, it’s, uh, a very complex document. Either way, I’m happy to inform y’all that Mastodon servers are built ‘to spec’ in this case. That’s not to be taken for granted.

Dependency Injection and the Tree of Knowledge

On Twitter, Manuel Schulze (@zet_manu shared the Swift package Resolver that does dependency injection in a very convenient way with little boilerplate thanks to property wrappers:

class BasicInjectedViewController: UIViewController {
    @Injected var service: XYZService
    @LazyInjected var service2: XYZLazyService
    @WeakLazyInjected var service3: XYZAnotherLazyService?
}

Mr Dr Dominik Hauser replied, and that’s how it entered my Twitter timeline. I was curious why people use packages like this – I know the concept from Java, but have always found constructor injection and maybe a Service Locator here and there to suffice.

Example: Pass-Through Injections

Manuel replied with an interesting example that shows a real pain point and code smell: passing on dependencies from A (over B and C and D) to E, say in a view controller hierarchy where you go from one scene to the next:

class First {
    private let service: SomeRandomService
    init(service: SomeRandomService) {
        self.service = service
        service.doSomething()
    }
    func createNext() {
        // passes the service down the tree
        _ = Second (service: service)
    }
}

class Second {
    // Does not need the service
    private let service: SomeRandomService
    init(service: SomeRandomService) {
        self. service = service
    }
    func createNext() {
        // passes the service down the tree
        _ = Third(service: service)
    }
}

class Third {
    // Does not need the service
    private let service: SomeRandomService
    init(service: SomeRandomService) {
        self.service = service
    }
    func createNext () {
        // passes the service down the tree
        _  = Forth (service: service)
    }
}

class Forth {
    private let service: SomeRandomService
    init(service: SomeRandomService) {
        self. service = service
        service.doSomething()
    }
    // ...
}

It sucks, I agree.

If you run into this, I believe it’s a good call to stop and question the approach. Why is the service passed on? That’s useless for all intermediate steps. (That’s the code smell part.)

With a centralized Service Locator and property wrappers, you get direct access and get rid of the code smell:

class ForthResolver {
    @Injected private var service: SomeRandomService
    init() {
        service.doSomething()
    }
}

That’s much more direct!

You also don’t need to create FourthResolver from ThirdResolver – they don’t pass stuff around anymore, so you’re free to solve this differently. (Manuel did keep the createNext() part in his example, but I believe that could be distracting from the problem we wanted to focus on.)

Extracting Knowledge

My personal solution would be different.

Using a Service Locator of any kind is similar to using global variables and singletons. There’s a place for this.

But it’s not the only solution to the problem at hand!

If we have these 4 controllers, and they are in a programmatic sequence (similar to a Storyboard), then there’s reason to extract the sequence itself as a thing, i.e. an object.

class ExtractedSequence {
    func setUp() {
        // Shared service:
        let service = SomeRandomService()

        // Inject service to controllers:
        let first = First(service: service)
        let second = Second()
        let third = Third()
        let fourth = Fourth(service: service)

        // Wire up the sequence
        first.next = second
        second.next = third
        third.next = fourth
    }
}

// The "next scene" logic, sketched as a protocol:
protocol Scene {
    var next: Scene? { get set }
}
class First: Scene { ... }
class Second: Scene { ... }
class Third: Scene { ... }
class Fourth: Scene { ... }

Manuel described the situation as a “tree” (again, like a branch in a Storyboard) – so my suggestion is: extract the tree-ness into its own thing.

Or ‘reify the sequential connection’. Or whatever you want to call this.

Instead of implicit logic via hops from A to B to C to …, we make the sequence explicit.

In the past, I’ve noticed this has been called “Wireframes” in the context of VIPER. Dominik used the term “Coordinator”, which I attribute to Soroush Khanlou, and which was changed over the years to mean many things, but it seems to be compatible with my ExtractedSequence above.

Injecting the shared service then isn’t that much of a hassle anymore, either.

For lots of shared services, it can pay off to bundle them together in a Context object. (I just again read about this a couple weeks ago but didn’t bookmark it, so pointers would be welcome in the comments!)

class SceneContext {
    let someRandomService = SomeRandomService()
    // ... more services ...
    // ... and also:
    var sharedState: Int = 4
}

let context = SceneContext()
let first = First(context: context)
let second = Second(context: context)
let third = Third(context: context)
let fourth = Fourth(context: context)

The shared state in the context might be interesting to all scenes – so it could make sense to add an init(context: SceneContext) requirement to a protocol implemented by all scenes here. Or maybe not.

Instead of a reference type context with implicitly mutable state, you might also want to explore options to use value type contexts and pass on a changed context copy to the next scene.

Either way though, there’s a shared context that was not explicit before, but now is.

Service Locator Pattern and Dependency Injection Bad?

No!

Not bad. It’s just not the tool I’d use for a job like this.

But things can and do get much worse sometimes, and then constructor injection might not work out as well anymore. And you’re under pressure. And it’s a start-up that needs an app quick. Then a shared repository of services that essentially hides global variables isn’t the worst approach.

Rehashing the reasons why I reach for some tools and not others was a valuable exercise for me tha I wanted to share. I don’t believe there’s anything wrong with the Resolver package or Manuel’s example per se. Just be aware there’s other solutions you could reach for.

PSA: TextKit 2 Has Bugs

When I linked to Marcin Krzyzanowski’s STTextView, I didn’t want to pollute the link post with this – but I did notice that Marcin added a very informative section to the README: a Bug Report List.

List of issues I reported to Apple so far:

  • FB9856587: TextKit2 unexpected additional line fragment for last line
  • FB9925766: NSTextSelectionNavigation.deletionRanges only works at the end of the word
  • FB9925647: NSTextLayoutManager.replaceContents(in range: with attributedString:) is documented but is not part of the public API
  • FB9907261: NSTextElementProvider.replaceContents(in:with:) does not replace content as documented
  • FB9692714: Rendering attributes does not draw properly
  • FB9886911: NSTextView can’t properly layout and display long lines (this one is nasty since it causes the view “jump” whenever text attribute updates)
  • FB9713415: NSTextView drawInsertionPoint(in:color:turnedOn) is never called
  • FB9971054: NSLayoutManager.enumerateCaretOffsetsInLineFragment ignores starting location
  • FB9971054: NSTextView assert on selection when setup with TextKit2
  • (pending): Incorrect text segment frame reported when first character attribute font size is bigger then the rest

Keep this in mind when you hop onto the TextKit 2 bandwagon with excitement!

STTextView: A TextKit 2 Text Editor without NSTextView

In my recent post about the TextKit 2 sample app, commenter Frizlab pointed out that Marcin Krzyzanowski (@krzyzanowskim on Twitter) is doing TextKit 2 stuff. I didn’t notice that in my Twitter timeline even though I follow him for years now, so I was confused, nay, angry about the state of my Twitter timeline since Musk’s takeover /s.

And yes, Marcin does have a very extensive open source sample project that explores TextKit 2! He’s working on Swift Studio, a pure Swift IDE and his STTextView is a part of that.

So meet STTextView, a “TextKit2 text view without NSTextView baggage”.

Check out the video demo!

One thing I love about the code comments for the UI compoents is the ASCII diagrams of layer contents, e.g. in STTextView.swift:

//  STTextView
//      |---selectionLayer (CALayer)
//      |---contentLayer (CALAyer)
//              |---(STInsertionPointLayer | TextLayoutFragmentLayer)
//

Am totally going to adopt this :)

Clean Downloads Folder on Mac with Hazel

I have a confession to make. I eased into this by sharing the same info on Twitter/Mastodon already.

My ~/Downloads folder is a mess.

It’s much less messy than your Downloads folder, most likely, but still.

I achieved relative de-messification by automatically filing old downloads into sub-folders, one per month, like ~/Downloads/2022-05 Downloaded. That was amazing because the actual Downloads folder was clean, and old stuff was somewhat highlighted.

But I have 2019-06 Downloaded, and 2020-06 Downloaded, and then some. I often do not delete these. And since there are so many super old archives already, I stopped manually deleting newer ones, too.

Not so clean downloads, actually.

I tagged this post #minimalism and #productivity, so here’s what I’m going to do, now that I’ve confessed:

Even though I know deep down in my heart that there are useful article PDFs and other downloads in some of these folders – I will delete everything that’s older than 3 months!1

And I’ll adjust my Hazel rules accordingly!

Here’s the old and trusty archival rule:

And here’s the new rule that matches the YYYY-MM Downloaded pattern and trashes everything older than a couple of months:

And the result is magical!

Much more clean downloads now.

I am still afraid that I’ve overlooked something, of course. That’s why I didn’t trash the old folders in the first place. But I can assume that it wasn’t important – or else I’d have moved it someplace else.

With the clean(er) slate, it’ll be easier to keep a tap on things, I hope. For starters, I don’t need to scroll down two windowfuls of folder names to get to actual downloads, so I don’t overlook unfiled invoices in the future.

Ah well, the benefits of blogging and publications in general. Peer pressure, even if only imagined, is really a thing. So y’all have bullied me into this as I was writing. Thanks, I guess.

  1. Ok, I lied a bit – I did quickly review all the folders to check if I forgot to move any downloaded invoice into my actual invoice archive for bookkeeping and the like. Then I deleted everything. 

Weak Self -- Closure Rules of Thumb

In Swift, you can weak-ify references to self in escaping closures, and then you need to deal with the case that the reference is gone when the block is called.

Last month, Benoit Pasquier and Chris Downie presented different takes on the problem. That discussion was excellent. It prompted me to take some more time to revisit this problem systematically, and I took away a couple of notes for future-me.

The three popular options as Chris listed them are these:

  1. Do not capture strong reference
  2. Override self with strong reference
  3. Bind strongSelf

As always, either way has its pitfalls.

I’ve used all approaches in my own and client apps.

But I do prefer (2), to use [weak self] in for outer and inner closures, with guard let self = self else { return } early on because this can symmetrically and consistently be used everywhere: It doesn’t matter if it’s the inner or outer closure. You can do this habitually, with code completion or templates, and write static code analyzers to catch that in PRs. These are all benefits in my book.

The dance with strongSelf creates a parallel set of problems and can make the inner closure’s setup depend on the outer closure I would like to avoid because I know I’m too stupid to get this correct 100% of the time.

See the rundown below for details.

1. Do not capture strong reference

Use self?.foo everywhere – but self can become nil in the middle of the block.(via Chris Downie)

Might not be what you want to use outside of one-liners.

2. Overriding self

guard let self = self else { return } override the local weak, optional self reference with the strong, non-optional one.

  • Benoit’s point: You can forget weakifying inner closures and accidentally create a retain cycle again!
  • Can arguable make it a bit harder to mess this up when you do it consistently, like a machine. (See 3. below.)

Problematic example adapted from Benoit:

self.doSomething = { [weak self] in
    guard let self = self else { return }
    self.doSomethingElse = { // ⚠️ forgot [weak self], now there's a cycle!
        self.foo()
     }
}
self.doSomething()

Could potentially be detected by static analyzers and SwiftLint.

3. Capture in e.g. strongSelf

guard let strongSelf = self else { return } – Bind strong self reference inside the block’s scope with a new name.

Swift compiler doesn’t warn you if you accidentally bind strongSelf from outside. Example by Chris

firstChild.playLater { [weak self] in
    guard let strongSelf = self else { return }
    strongSelf.gamesPlayed += 1
    strongSelf.secondChild.playLater {
        if let strongSelf = self {
            // 👍 Locally bound the weak self reference.
            // (But didn't use the bound variable.)
            print("Played \(self?.gamesPlayed ?? -1) with first child.")
        }
        // ⚠️ Strongly captures `strongSelf` from outside by accident
        // and creates cycle.
        strongSelf.gamesPlayed += 1
        completion(strongSelf.gamesPlayed)
    }
}

To mitigate, make sure to always

guard let strongSelf = self else { return }

at the beginning of a block. Could be detected by static code analyzers.

But this is too clever for my taste: in the example above, you don’t need to pass in any weak reference. The outer block weak-ifies the reference to self already, and that’s enough. Then the strongSelf reference lives next to it and creates a parallel set of problems. – Instead, I favor making the same mistake in all places and consistently apply [weak self] in.

You can of course rewrite this to require [weak self] in the inner closure, too. Doesn’ hurt (I just tried), but is also not necessary to get a weak reference in the inner closure.


Chris’s rules summarize this nicely:

  1. Only use a strong self for non-@escaping closures (ideally, omit it & trust the compiler)
  2. Use weak self if you’re not sure
  3. Upgrade self to a strongly-retained self at the top of your closure.

TextKit 2 Example App from the Apple Docs

The Apple Developer Docs have an example app, “Using TextKit 2 to Interact with Text”. That’s related to WWDC 2021’s introduction to TextKit 2.

Availability:

  • iOS 15.0+
  • iPadOS 15.0+
  • macOS 12.0+
  • Xcode 13.0+

So it’s usable on macOS Monterey and the latest iOS.

I ran the demo app and resized the window a bit. It’s buttery smooth, and that’s good.

But the butter, in part, comes from each block seeming to move up/down independently. Spring-loaded, so to speak. You can see this in the following demo when I move the mouse rather quickly:

For larger layout changes, the smoothing feels strange

When multi-line paragraphs occupy many more or far fewer lines than they did before, the headings between these paragraphs move at different speeds, then ease-out as they catch up.

I believe the intention here is to show what a block or text fragment is, and that you can animate them individually. TextKit 2 is block-based; so you have a paragraph block, and headings blocks. These are laid-out, eh, en bloc. When you turn on the turtle mode in the toolbar, the ease-out animation is even more pronounced.

Most of this is visible in TextDocumentView.swift:

func textViewportLayoutController(
        _ controller: NSTextViewportLayoutController,
        configureRenderingSurfaceFor textLayoutFragment: NSTextLayoutFragment) {
    // (1) Tuple-returning API
    let (layer, layerIsNew) = findOrCreateLayer(textLayoutFragment)
    if !layerIsNew {
        let oldPosition = layer.position
        let oldBounds = layer.bounds
        layer.updateGeometry()
        if oldBounds != layer.bounds {
            layer.setNeedsDisplay()
        }
        if oldPosition != layer.position {
            // (2) Layer animation
            animate(layer, from: oldPosition, to: layer.position)
        }
    }
    if layer.showLayerFrames != showLayerFrames {
        layer.showLayerFrames = showLayerFrames
        layer.setNeedsDisplay()
    }

    contentLayer.addSublayer(layer)
}
  1. The findOrCreateLayer method is interesting because the Apple developer(s) who wrote this sample return a tuple. That’s refreshingly new. But that’s not TextKit 2 API, just part of the TextDocumentView.

  2. The layer is of type TextFragmentLayer, which is a CALayer subclass. So you can animate each NSTextLayoutFragment independently.

Here’s another video showing slow-motion with layer borders turned on. You can see how the layers move until they touch:

Slow-motion mode and layout borders

It’s interesting, to say the least, that layout fragments or blocks will be laid-out with independent CALayers or NSView. I’m not certain if that’s actually true, to be honest, but this seems to be the implication when you mess with NSTextViewportLayoutController, because the docs for the delegate method implementation from above is:

The delegate presents the text layout fragment in the UI, for example, in a sublayer or a subview. Layout information such as viewportBounds on textViewportLayoutController isn’t up to date at the point of this call.

I need to actually experiment with TextKit 2 to get any understanding of the components at all. Wanted to share my surprise about CALayer usage here, though.

When you remove the delegate method, by the way, there won’t be any laid-out content. So either the TextKit 2 demo is demonstrating very low level interaction or making text editors of the future will just be different.

Will keep you posted when I find out more.


→ Blog Archive