book covers

Indie macOS Dev Bundle, Swift 3 Ed..

Design solid applications and own your distribution process. Learn software architecture principles to make your Swift apps easier to maintain and extend. Set up your app for sale, with a trial mode and license generation all set. Including fully functional sample projects and code templates for your app.

Indent Code and Lists by 4 Spaces in Emacs Markdown Mode

I noticed that my Emacs didn’t maintain the current line’s indentation when editing code blocks in Markdown (markdown-mode). I indent the first line with 4 spaces like any sane person would. When I hit enter to continue in the next line, thanks to the markdown-mode defaults, I am presented with a new line that’s properly indented visually. Only when committing to git did I notice that Emacs inserted tabs instead of spaces. Gasp!

Turns out you’re supposed to turn this off for text-mode (a parent mode of Markdown). See the great Emacs indentation tutorial for a good reference.

Using hooks, here’s what I am now using to indent by 4 spaces instead of adhering to tab stops:

(add-hook 'text-mode-hook
          '(lambda ()
             (setq indent-tabs-mode nil)
             (setq tab-width 4)))

This will also affect the “press tab to indent the current line” behavior that’s active even when your in the middle of text. I needed to get used to this at first, because I expected hitting tab to insert a tabulator at the current cursor position. By default, this indent-line-function is configured to indent the current line up to the previous line’s indentation, at least for a lot of cases. In some modes, it is even more aware of the context and does clever things. You can change this to be dump and always indent by 4 spaces, too. Just add this to the hook’s lambda list of expressions:

             (setq indent-line-function (quote insert-tab))

Global TODO Capture Hotkey for Emacs Org Mode on macOS

I am using GNU Emacs for a while now to do all kinds of stuff. I’m thinking about migrating away from OmniFocus as my task manager and use Org mode instead. What I like so far is the free-form list nature of Org files. I can have an outline of notes and sprinkle TODO items inside them. This way I can take notes on puzzling problems I’m working on and mark things I need to do later. This is super useful to remind myself what to clean up before a commit, for example write or amend tests or remove debug code somewhere. I like it. I got used to a lot of shortcuts already, so most of the pain of daily use is gone.

Today I played around with org-capture. It’s a command that opens a temporary text entry field (i.e. a buffer) and stores the entry in a specific location for you. You need to configure Capture templates for this to work, and they can do really amazing stuff, like “insert the captured item in file foo.txt below the heading XYZ for the current date”. Ah, the power of plain text.

A very simple capture template is a simple TODO item that gets filed in your inbox, e.g. located at ~/org/inbox.org:

(setq org-capture-templates
      (quote (("t" "todo" entry (file "~/org/inbox.org")
               "* TODO %?\n%a\n"))))

When you invoke org-capture and select this template with the shorthand “t”, it will populate the temporary text entry for you with a new list item starting with * TODO. In a line below the cursor position (indicated by %? in the template), a link to the current place you were at is inserted (%a). That link can be a file, or a location in an Org document. You can click on these links to jump to that location. These TODO items become system-wide file annotations, so to speak.

Now if you’re not working inside Emacs at the moment but want to capture a task for your inbox from Xcode or Chrome or whatever, you can actually remote-control Emacs with the emacsclient command-line application. That will be able to execute Lisp commands in your current Emacs session.

First, start the Emacs server with the command server-start. (You can execute commands with M-x, which is shorthand for Meta-x. The Meta key has to be configured first. It’s my left Command key, for example.)

The template I pasted above had the key “t”, so the remote control command to use org-capture is this:

$ /usr/local/bin/emacsclient org-protocol://capture?template=t

To enable recognition of org-protocol:// as a parameter, you need to enable this optional module in Emacs, too. The module is called org-protocol, and the variable that holds the list of enabled modules is called org-modules:

(setq org-modules (quote (org-protocol)))

I actually configured this in Emacs’s “customize” tool that stores user-preferences (you can get there when you hit Cmd-, in the macOS app). Search for “Modules”, expand “Org Modules”, and select the “protocol” module. Then apply & save changes.

Screenshot of Emacs Customize
Interactive Customize editor with Org Modules expanded

With the server running and the org-protocol enabled, the shell command will just work as if you invoked the capture template yourself, e.g. hit C-c c t.

I bound this to a Keyboard Maestro macro that replaces my global shortcut to enter a new task into OmniFocus. The macro also brings the Emacs app to front, if it isn’t the frontmost app already.

RxSwift: Typing/Idle Event Stream for NSTextView

To improve my note editing app The Archive’s responsiveness, I want to find out when the user is actively editing a note. Here’s how to create the idle switch in RxSwift/RxCocoa. A simple enum of .typing and .idle will do for a start.

enum State {
    case typing, idle
}

Of course, NSTextViewDelegate provides a textDidChange(_:) callback to hook into; it’s based on a notification, so you can also subscribe to the NSText.didChangeNotification directly if you don’t want to use a delegate. That’s the input signal I’m going to use.

let textView: NSTextView = ... // e.g. use an @IBOutlet
let textDidChange = NotificationCenter.default.rx
    .notification(NSText.didChangeNotification, object: textView)
    .map { _ in () }

I map the notification to produce an Observable<Void> because I don’t need any actual information, just the signal itself. To find out if the user stops typing, use the debounce operator of Rx to wait a second. (Got the idea from StackOverflow)

This is typical of Rx, in my opinion: it’s all backwards but makes sense in the end. You don’t implement a timer that waits 1 second after the last event (although you could, but the overhead is larger). Instead you ignore events emitted within a time window, then produce an event if that time has elapsed:

let state = Observable
    .merge(textDidChange.map { _ in State.typing },
           textDidChange.debounce(1, scheduler: MainScheduler.instance).map { _ in State.idle })
    .distinctUntilChanged()
    .startWith(.idle)
    // Add these if you reuse the indicator:
    .replay(1)
    .refCount()

You can print this to the console or hook it up to a UI component as an idle indicator. In my test project, I was using a label to show “Idle” and “Typing”:

let idleLabel: NSTextFiel = ... // again, an @IBOutlet
state.map { $0.string }
    .bind(to: idleLabel.rx.text)
    .disposed(by: disposeBag)

Like any good OO citizen, I put the string conversion into the State type, of course:

extension State {
    var string: String {
        switch self {
        case .typing: return "Typing ..."
        case .idle: return "Idle"
        }
    }
}

I’d never have figured to use debounce on my own, I guess. In the end, this turned out to be a super simple approach, though.

If you want to write an idle indicator for another UI component, maybe you don’t even have to create the initial notification-based connection: NSTextView on Mac does not expose a ControlEvent or ControlProperty for content changes, but NSTextField has .rx.text, and UIKit components have, too. The remainder of this approach will stay the same.

Broken Help Menu Breaks Rather Unpredictably

Last week, I wrote about the Help menu bug that seems to “freeze” apps inasmuch as it makes them not handle any keyboard input.

The thing is, I cannot always reproduce this issue. I rebooted my system and just couldn’t break the behavior anymore. Which sounds an awful lot like running the system for a while and using it in a specific way might affect the Help menu’s behavior. But I don’t know which steps you need to take to reproduce this. It’s puzzling me. With Daniel Jalkut and a beta tester of my latest app having experienced the same issue, I know I’m not alone. Or crazy. Just clueless.

Plain Controllers to Ease the Burden of View Controllers

I like to see that Soroush and I think among the same lines. For one, that makes me feel less like a loony. And since he writes a lot more in recent months, it means my sloppy blogging schedule does not affect spreading ancient wisdom. In February, Soroush blogged about writing “Just Controllers”: service objects, usually reference types, that you use to orchestrate processes and offer a simpler API. Requiring a delegate to be plugged-in is one way to design a simpler API: the delegate procotol defines all the output events from the service object, or controller. That’s all there is going to happen as far as outside code is concerned. But on its inside, the controller can do the fanciest of things to accomplish its goal. These implementation details are hidden from code that uses these service objects. It’s object-oriented programming 101 all over again – encapsulation, information hiding, stuff like that. But, and this is a big But, the go-to massive view controllers in iOS apps are a symptom of not encapsulating properly. Go and delineate a few more boundaries inside your app. As with raising children, boundaries help your code grow properly, too!

Put Test Files Next to Production Code for Better Discoverability?

The Kickstarter engineering team posted an interesting proposal: put your XCTestCase files into the same directory and group as your production code. Do keep the separate target ownership, of course.

One clear benefit is directory tree or file management, and discoverability of tests for people browsing the code: In most cases, you have FooService in FooProject/FooApp/FooService.swift, and FooServiceTests in FooProject/FooAppTests/FooServiceTests.swift. With nested folders, you need to navigate around even more. I got used to this Xcode default. But I also got annoyed by this split at the root level when I browsed code on GitHub. Multiple tabs in a modern browser to the rescue, this was never that problematic, given the test directory is structured in a similar way. If not, good look finding the file you’re looking for. (I hate that the RxSwift project, which I often had to browse to look up an implementation detail, has files that contain only a path reference to the real file, similar to ../../Core/SomethingSomething.swift, making tests pretty hard to find).

I will try this approach with a library module that I develop as a sub-project of The Archive and report back.

High Sierra: Help Main Menu is Broken and Freezes Apps

If you run macOS 10.13 High Sierra, try not to use the Help menu. It will appear to freeze the app for which you invoke the Help menu: it will not accept keyboard input anymore, emitting the NSBeep “invalid action” sound in most circumstances.

The reason seems to be that the nifty Search bar inside the Help menu will acquire focus so you can search for a menu item or help book entry. This is taking away focus from the app window. You notice this when the blue focus ring around an active text field in the window goes away; instead, the Help menu’s Search bar obtains focus and the accompanying blue focus ring. All this is supposed to happen. This process just isn’t reverted for some reason since at least macOS 10.13.3, maybe earlier. When you click back into the app outside of the Help menu, the key window status will not be assigned back to the window you click on. That means keyboard input will not work at all. (As a non-native English speaker, I asked if “key window” was supposed to mean “window that responds to key events” a while back. While people agreed this wasn’t the intention, I still cannot help to think that way. The app has no key window, so key events are not handled, resulting in NSBeep.)

You can fix this inconsistent state yourself: open the Help menu again, then hover over an adjacent main menu entry, like “Window”. This will apparently close the Help menu in a different, proper way, unlike clicking back inside the main app’s window. Restarting the app works too. Cmd-tabbing out and back in does not help, though. (Backstage info: the Help menu will send willOpen when you tab back into the app, re-acquiring key status while not being on screen.)

I filed a bug report with Apple. See rdar://39374865 and make sure to clone the report so this is fixed sooner than later.

That’s a bad problem. As an app developer, your app will appear broken, so you need to fix this for your users even though it’s an Apple bug.

A working fix is to assign a NSMenuDelegate to the Help main menu item’s submenu (which consider to “be” the Help menu) and restore key status in the app. This is a pretty simple brute-force solution:

extension AppDelegate: NSMenuDelegate {
    func menuDidClose(_ menu: NSMenu) {
        if menu === NSApp.helpMenu {
            // Replace with more elaborate restoration:
            NSApp.mainWindow?.makeKey()
        }
    }
}

Setting the key window this way might actually result in a second menuDidClose(_:) call (after another menuDidOpen, by the way, which is odd).

Also, I found the window needs a reason to redraw afterwards to draw the blue focus ring where it belongs. Your app will forward key events to the current responder as expected, but if the firstResponder is a text field, the blue ring will not be visible immediately. I can live with this for now.

Also, I have yet to find out how multi-window apps or NSDocument-based apps behave. Is the right window restored? Do you want the mainWindow to become key? I’ll experiment with TableFlip and see what happens.

Today, Daniel Jalkut wrote about this, too, with a more elaborate approach to restore the actual state before the help menu was invoked.


→ Blog Archive