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.

Sorting Markdown Tables by Column from the Shell

TableFlip doesn’t sort tables by column as of yet. So we all have to resort to other solutions – like shell or Terminal commands.

Let’s say your (Multi)Markdown table starts with a header (=2 rows) and has leading pipes like this:

| a | b | c | d |
| - | - | - | - |
| 1 | 2 | 3 | 4 |
| 9 | 1 | 2 | 3 |
| 3 | 4 | 5 | 6 |

You can sort the table by the 3rd column (column “c”) like so:

tail -n +3 table.md | sort --field-separator=\| --key=4

Explanation:

  • tail reads a file from the end; tail -n +3 reads a file from the end until the 3rd row, leaving out the first 2 rows, aka the header.
  • sort sorts the textual input; --field-separator=\| configures the sort command to not use tab stops but pipes as column separators; and --key=4 sets the 4th field (3rd column if your table starts with a pipe, which is counted by sort, too) as the input.

The output will be:

| 9 | 1 | 2 | 3 |
| 1 | 2 | 3 | 4 |
| 3 | 4 | 5 | 6 |

You can add the header back by combining tail with head, where head outputs the topmost 2 lines:

head -n 2 table.md && tail -n +3 table.md | sort --field-separator=\| --key=4

Et voilà:

| a | b | c | d |
| - | - | - | - |
| 9 | 1 | 2 | 3 |
| 1 | 2 | 3 | 4 |
| 3 | 4 | 5 | 6 |

Ready to be saved as a new file by routing the output to a new file name with ` > sorted_file.md`:

(head -n 2 table.md && tail -n +3 table.md | sort --field-separator=\| --key=4) > sorted_table.md

Week #18 of 2017 in Review

App News

  • Been working all week on the upcoming The Archive Beta. (You can still sign up!) First batch of testers will be equipped next week. I’m excited to see if nobody’s computer explodes.

Writing

Coding

Other Stuff

  • Accessibility is important in architecture as it is in technology. I found a tongue-in-cheek video with a Super Mario theme that probably resonates with geeks to raise awareness.
  • Last Sunday was sunny, so I went out and gathered the kids of our neighborhood to sculpt stuff from soap stone. I ended up being busy with taking care of the little ones but still finished a small snail.

Week #17 of 2017 in Review

Since I’m going to spread my writing thin on this blog in the upcoming weeks, I thought it might be nice if I highlighted some things that were happening. So here goes.

Update 2017-04-29: Forgot the coding stuff. Is now added!

App News

Blog Posts

Code

Other Stuff

  • Went out to paint a picture with acrylics last Sunday. Kind of happy, but I see where I can improve my strokes and technique. I’m rusty, and I never was that good in the first place, so way to go this year!

The Archive – Mac App Beta

The Archive screenshot
Early development preview of The Archive

My latest project is about to be ready: it’s a strictly plain text note-taking application. If you know our writing over at the Zettelkasten Method blog, you will know the method I’m incorporating in this app. Everything revolves about flexibility and your ownership of the notes. Plus the amazingly productive method itself is baked right into the app to guide your workflow.

Sign up here for a beta invitation in early May:

http://zettelkasten.de/beta/

Website Changes Imminent

The past couple of weeks have been unusually quiet around here. There are a couple of reasons:

  1. I was undergoing an operation (for a nearly-but-not-really abdominal hernia that I got from squats with bad form and tons of weight a couple years back and which showed symptoms in March) and being able to sit in front of a computer again took a while. I now permanently carry a 6x11cm plastic mesh in between the fasciae of my belly. Apart from most strength training still being too painful to do, everything’s fine in my day-to-day activities.
  2. When I could, I spent all my computer time on my current soon-to-be-announced project. It’s an important milestone in my Mac application empire and there are a lot of edge cases I have to cover to prevent data loss and the like. It’s pretty huge in terms of lines of code already, surpassing both the Word Counter and TableFlip.
  3. I am planning to move the programming blog from here to someplace else. Because I don’t have an outlet for all of my stuff at the moment. Programmers will be able to read my posts and notice my books on this site at the moment, but app customers will have a harder time finding their way around. As I grow my empire, I want to take better care of everything. I migrated most stuff to a GitHub Pages hosted Jekyll website. My secondary domain http://cleancocoa.com/ now points there if you want to have a look. Which means the schism is already real, it only doesn’t show on this website, yet.

Then there’s a lot of preparation happening for side-projects and my stuff at the Zettelkasten Method blog where we will run a live video stream today, by the way.

So the delay here is due to managerial problems and some re-structuring. I hope to clean up everything, soon, and then move forward with higher velocity. There’s a ton of unedited posts in my backlog waiting to be published.

FatSidebar View Component for macOS Released

One important user interface part of my latest top-secret project involves a sidebar of buttons. Like a regular toolbar, but taking up less space for chrome, looking more flat, and the user should be able to create toolbar buttons herself.

So while I was mostly sick at home for the last couple weeks, I spent my time cobbling this together. With drag and drop reordering and all.

logo

Find FatSadebar on GitHub!

I have never written my own custom view component from scratch before. I helped improve KPCTabsControl for Swift 3 when I created TableFlip last year. And of course I participated in a lot of smaller open source projects, too. But I never started from scratch, and that was cool.

Also fun: creating the library’s own “logo”. Made it feel so much more official.

What’s cool about writing a new thing from nothingness is that I had no clue what to do and how to start. This component turned out as rather adventurous mental gymnastics because I had to leave the paths of application development I know so well. I still don’t know all the answers; what are best practices? Get something colorful on screen? Customize drawRect and draw boxes and placeholders? Partition the view into sub-components using Auto Layout from the get go? Is drawing text better handled by NSTextField labels than NSAttributedString.draw(in:) or is the overhead too much? (I still don’t know the best answer for this.)

Anyway! I ended up putting this together as a library with sample app. There are some unit tests for inserting items into the “fat sidebar”, but otherwise I find the drawing and layout related code to be absolutely hideous. Cannot come up with improvements on that front that go beyond cosmetics, though. Maybe later, with more experience.

Non-Obvious Swift: Defer

The following code works as expected:

class FooCollection {
    private var items = [Foo]()

    func removeAllItems() -> [Foo] {

        defer { items.removeAll() }
        return items
    }
}

But do you know what “expected” means in this case?

As a reader, you assume the author had an intention. You look for the mens auctoris and are an overall benevolent reader, I hope. Presupposing said intention, you may assume that it does something special if you put the call to items.removeAll() in a defer block.

The removeAllItems method returns an array of items. If the internal collection was empty when the return statement is reached, that’d be pointless, wouldn’t it? Since your benevolent, you assume that the author isn’t stupid and that it does indeed return a non-empty collection in some cases.

Say the author had added a documentation line:

/// - returns: Array of items that were removed.
func removeAllItems() -> [Foo] { // ... }

Now that should tip the scale! So the internal collection of items is returned and afterwards emptied. Aha! How clever!

Ze true connoisseur of Swift appreciates ze brevity of defer

As a critical reader, you should be able to solve the puzzle and call the author names. Because, why make it so non-obvious to the reader? Why the guessing that either requires manual (or unit-) testing or the Swift doc (and trust in its truth) to verify the assumptions?

I was curious about the outcome of this approach so I just tried it before writing this up. Then I deleted the “”“clever”“” code and replaced it with what I had before:

class FooCollection {
    private var items = [Foo]()

    func removeAllItems() -> [Foo] {
        let removedItems = items
        items.removeAll()
        return removedItems
    }
}

I prefer this any day. I hope you do, too.

Setting the Line Height of a NSTextView

NSTextView (and UITextView for that matter) have a defaultParagraphStyle attribute where you can set the text’s line height. That works swell – if you display text statically. Once the user can enter something, you can run into trouble:

  • If the user adds text in the middle of an already laid-out line of text, the paragraph style is retained.
  • If the user writes at the beginning of the line, the line height info gets lost.
GIF of the process
This is what happens when you type at the beginning of a line

It’s your usual RTF nightmare. I know this behavior from rich text editors; and I developed my own way to make sense of it in the process. It might not be what is really going on, but it’s a good heuristic: it’s just like the opposite of making a word bold, placing your cursor after that word, type, and get more bold text. There, the “bold text” information is carried on. The cursor inherits this info from the character left to it. But if you start at the beginning of a line, your cursor will not inherit what comes afterward. And since there is nothing before its position, it starts with empty info, and thus empty line height settings. Since the whole paragraph is affected by this, the latest change wins. Beginning to type at the beginning of a paragraph with empty paragraph settings removes them from what comes afterwards.

So this might not be The Truth, but it helps me deal with shitty software. I don’t want to write shitty software, though, so I look for ways out of this. I don’t intend the user to change paragraph settings; I want the text view to have a certain look and feel no matter what gets pasted or typed in it.

Hunting for Core Text/TextKit callbacks, NSTextStorageDelegate seems to provide a good customization point:

func textStorage(
    _ textStorage: NSTextStorage, 
    didProcessEditing editedMask: NSTextStorageEditActions, 
    range editedRange: NSRange, 
    changeInLength delta: Int
) {
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.lineHeightMultiple = 2.0
    textStorage.addAttributes([NSParagraphStyleAttributeName : paragraphStyle], range: editedRange)
}

Of course it makes sense to store the global paragraphStyle once and re-apply it here. I don’t know if this is the best place to put it, though. Re-applying all the NSAttributedString settings while typing might not perform best.

Also, this is affecting the “rich text representation” of the text. If you copy the result and paste it into TextEdit, say, the text will look the same, line height settings and all.

You can override the pasteboard representation to be “plain text” only in order to remove the style info and thus have it behave like the “Paste and Match Style” command from the “Edit” menu automatically.

Again, I don’t know if this is the best way to do this.

What I’d expect to create instead:

  • a text view with a “plain text” representation by default (the model)
  • typesetting customizations that only affect what is visible on screen (the view)

I imagine this to be like HTML code/browser rendering, not like WYSIWYG. What you type is not what you see. Just what you’d expect a source code editor to be like.

I’ll keep you posed as I dive deeper into TextKit and stuff.

How to Make ReSwift Actions Undoable with NSUndoManager

I wrote about using NSUndoManager + ReSwift with a Middleware. You could think of the resulting UndoMiddleware as some kind of observer of events that pass the ReSwift.Store and which puts the opposites of incoming actions on the undo stack. Why a Middleware? Because making an action undoable is a side effect.

The Middleware wasn’t exactly straightforward. It took a bit of type wrapping and boilerplate code. A “context provider” made parts of the current app state and model available to compute the opposite action. Without this context, the Middleware couldn’t know what was expected.

Today I came up with something shorter: an action creator. It works well because the actions in my current project are simple and easily reversed.

Instead of dispatching an action like this:

store.dispatch(CreateFoo(id: 1))

… you create the opposite of the action where you already do have knowledge about the context:

let creation     = CreateFoo(id: 1, content: "...")
let undoCreation = DeleteFoo(id : 1)
store.dispatch(undoable(creation, opposite: undoCreation))

Of course it takes more effort to compute it the other way around because you have to fetch the current content before you delete (that’s what the context provider of my Middleware did, too):

let id = // ...
let oldContent = foo(withId: id).content
let deletion =     DeleteFoo(id: id)
let undoDeletion = CreateFoo(id: id, content: content)
store.dispatch(undoable(deletion, opposite: undoDeletion))

Chances are you have a lot of the pieces of the puzzle to assemble the “undo” action in the service object that dispatches the original action. In contrast, the Middleware knew nothing and thus had to depend on another source of information for everything.

The undoable action creator is very simple, after all:

public func undoable(_ action: Action, opposite: Action?)
    -> (AppState, DefaultStore)
    -> Action?
{
    return { (appState: AppState, store: DefaultStore) -> Action? in
    
        if let undoAction = opposite,
            
            // Replace this with something useful in your app:
            let undoManager = getUndoManagerFromSomewhere() {
            
            undoManager.registerUndo(withTarget: store) { store in
                store.dispatch(undoable(undoAction, opposite: action))
            }
        }
    
        return action
    }
}

This assumes symmetry of actions: inside the registerUndo block, undoable is called again, only the other way around, to setup what “redo” should be like. If your original action is not what you want to use for redoing, then you need to put that in somehow.

This is the case in my app where DeleteFoo creates a pending file change that ends with dispatching DeleteFooCompleted. The opposite of DeleteFooCompleted is CreateFoo – but the opposite of CreateFoo is DeleteFoo, not DeleteFooCompleted, so I put a mapping in between:

public func redoable(basedOn action: Action) -> Action {
    switch action {
    case let action as DeleteFooCompleted:
        return DeletingFoo(id: action.id)

    default:
        return action
    }
}

public func undoable(_ action: Action, opposite: Action?)
    -> (AppState, DefaultStore)
    -> Action?
{
    return { (appState: AppState, store: DefaultStore) -> Action? in
    
        if let undoAction = opposite,
            let undoManager = getUndoManagerFromSomewhere() {

            // Original trigger might be a 'completion' event that has to be
            // "redone" using a 'starting' event.
            let redoAction = redoable(basedOn: action)

            undoRegistrar.registerUndo(withTarget: store) { store in
                store.dispatch(undoable(undoAction, opposite: redoAction))
            }
        }
    
        return action
    }
}

And that’s it!

The biggest problem is to obtain a reference to the main window’s UndoManager instance. You can inject it once but not replace it later, which I would have needed, so I end up with a getter here, which I don’t like a lot; you might be fine with providing a custom UndoManager instance during setup, though! In NSDocument-based apps, where each document has its own window with its own undo manager and probably its own store instance, it’ll be pretty easy.


→ Blog Archive