A minor tag searching glitch

First up, I really like minimalist software curiously capable of more than it appears. Taskpaper fits that category. Thank you!

There are situations where searches don’t work with 100% accuracy. Here’s how to replicate that.

Create some tasks and tag with with @ peskyusers. Note that I’m putting a space between @ and peskyusers to avoid creating forum tags.

Start an editor search for not @ peskyusers - all is well (probably).

Now add a blank line under one of those peskusers lines. Put a tab on it, and nothing else, or anything other than than a task. Refresh the search, and the peskyusers task will remain in a not @ peskyusers search.

This may be linked to the logic for tasks and subtasks that don’t all fit a search.

Searching for a tag shows the non-qualifying branches collapsed. Searching for not-tag will include a tagged parent if it has non-tagged subtasks.

The workaround is to keep blank lines blank.

The thing to bear in mind is that the parents and ancestors of matched items are displayed on the screen, with the found items, as outline context.

An editor search:

  • filters down to the matching items,
  • displaying them in full ancestral context.

You can, however, define any kind of customized search-based focus and display pattern, using the scripting API.


1 Like

To hide / show the ancestral outline context of matched items, for example, you could assign a keystroke to a toggling script


of which a draft might look something like:

Click triangle to reveal JS source
(() => {
    'use strict';

    // Toggle the visibility of match ancestors in an
    // Editor Search filtered TaskPaper 3 view.

    // Rob Trew @2020

    // --------- JAVASCRIPT FOR AUTOMATION CONTEXT ---------

    // main :: IO ()
    const main = () => {
            ds = Application('TaskPaper')
        return 0 < ds.length ? (
                script: TaskPaperContext.toString(),
                options: {}
        ) : 'No documents open in TaskPaper 3';

    // --------------- TASKPAPER JS CONTEXT ----------------

    // Evaluated in the TaskPaper JS interpreter, rather
    // than the JXA interpreter.
    const TaskPaperContext = (editor, options) => {
        const strPath = editor.itemPathFilter;
        if (!strPath) {
            return 'No path filter applied';
            outline = editor.outline,
            matches = outline.evaluateItemPath(
            visibles = editor.displayedItems,
            legend = visibles.length > matches.length ? (
                'Hide ancestors'
            ) : 'Show ancestors';

        outline.groupUndoAndChanges(() => {
            if (legend.startsWith('Hide')) {
                const matchSet = new Set(matches);

                    // Hidden unless a direct match.
                    x => !matchSet.has(x) && (
                        editor.forceHidden(x, false)
            } else {
                // Default view restored.
                editor.itemPathFilter = strPath;
        return legend.startsWith('Hide') ? (
            'Ancestors hidden'
        ) : 'Ancestors shown';

    // main ---
    return main();

As a Keyboard Maestro macro assigned to ⌥F

Hide : Show search match ancestors.kmmacros.zip (10.5 KB)

Nice! Thank you for the time you took. Learning Javascript is a priority right now. I have uses for it in Taskpaper and I also want to be able to get more out of Tap Forms, another Javascript-enabled application.

Your example is yet more learning material for me as well as an answer to my quibble.

Another complication in determining if something is a descendent or sibling of an item is blank lines embedded to break a comment into paragraphs. The blank lines in that case should be considered part of a single comment.

Not sure what that is exactly in your usage – TaskPaper is an outliner, and the central model is a set of relationships between \n delimited lines.

Part of its beauty, and the enormously varied range of ways in which it used, is that the model is kept very clean and very simple. There is only one way to group lines (or nodes or items to use the terminology of the API) – you make them children of a common parent.

(that may suggest a way of representing the comment entity in your particular workflow – a comment child line, with a set of its own children).

Experimenting with the Scripting API is a good way to build intuition about the architecture and how to work with it.