Place the cursor at the end of the first task that is not @done

Here’s a draft of:

// First not @done selected in project containing cursor
// or first not @done in document, if project complete.
Expand disclosure triangle to view JS source
(() => {
    "use strict";

    // Rob Trew @2021, 2024

    // First not @done selected in project containing cursor
    // or first not @done in document, if project complete.

    // Ver 2.07

    const enclosingProject = "ancestor-or-self::project[-1]";

    const firstNotDone = "//task not @done[0]";

    // ------------------- JXA CONTEXT -------------------

    // main :: IO ()
    const main = () => {
        const doc = Application("TaskPaper").documents.at(0);

        return doc.exists()
            ? doc.evaluate({
                script: `${TaskPaperContext}`,
                withOptions: {
                    enclosingProject,
                    firstNotDone
                }
            })
            : "No documents open in TaskPaper";
    };

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

    // TaskPaperContext :: Editor -> IO ()
    const TaskPaperContext = (editor, options) => {
        const
            outline = editor.outline,

            { enclosingProject, firstNotDone } = options,

            match = outline.evaluateItemPath(
                enclosingProject + firstNotDone,
                editor.selection.startItem
            )[0] || outline.evaluateItemPath(
                firstNotDone
            )[0];

        return match
            ? (
                editor.moveSelectionToItems(
                    match, match.bodyString.length
                ),
                `${match.bodyContentString}`
            )
            : "No remaining tasks in document.";
    };

    // MAIN ---
    return main();
})();
1 Like

The weekend arrives early in your timezone! :slight_smile:

Colour me impressed! The code is brilliant, and I will have to put time into studying it.

I hereby dub you: Honorable Knight of JavaScript!

The work is all Jesse’s :slight_smile:

( I had just been slow to grasp the use of that contextItem argument )

1 Like

I’ll respectfully disagree with that, but thanks :slight_smile:

2 Likes

Regardless of the specifics, both of you are wonderful!

1 Like

Can the above script be adjusted so that the cursor is placed at the end of the last task (as opposed to the first one) that is not @done? Preferably only including the current project and its ancestors.

Example:

Project One:

  • Task 1A @done(2026-02-07)
  • Task 1B
  • Task 1C ← (to here)

Project Two:@done(2026-02-07)

  • Task 2A @done(2026-02-07)
  • Task 2A@done(2026-02-07)
  • Task 2A@done(2026-02-07)| ← (cursor goes from here)

Experimenting with:

    const firstNotDone = "//task not @done[-1]";
1 Like

( In a southern hemisphere airport without macOS - hope to be back at a desk c. Tuesday :slight_smile:

1 Like

No urgency Rob—thanks!

Scope update:

  • I only want the cursor to go upwards.
  • It should also go to project names that are not done.
1 Like

Re

Preferably only including the current project and its ancestors.

I may be misunderstanding that formulation – it looks in your from → to example, as if the from is in the current project but the to is in a preceding (rather than ancestral or identical) project.

i.e. if nothing is found in the current project or its ancestors, we still continue back upwards ?

( Forgive me if I’m stumbling a bit – feet already in Europe, but soul only partially arrived )

1 Like

I didn’t explain it well. Your soul may have arrived in the midst of a poorly written scope :grinning_face:

Yes, on:

if nothing is found in the current project or its ancestors, we still continue back upwards ?

All the way up to the end of the first undone task or project in the entire document.

1 Like

Another Example:

Project One:

  • Task 1A @done(2026-02-14)

  • Task 1B

  • Task 1C

Project Two: ← (to here)

  • Task 2A @done(2026-02-14)

  • Task 2A@done(2026-02-14)

  • Task 2A@done(2026-02-14)| ← (cursor goes from here)

And Another:

Project One:

  • Task 1A ← (to here)

  • Task 1B @done(2026-02-14)

  • Task 1C @done(2026-02-14)

Project Two:

  • Task 2A @done(2026-02-14)

  • Task 2A@done(2026-02-14)

  • Task 2A@done(2026-02-14)| ← (cursor goes from here)

The only part of that example that seems unclear is the question of whether project names are visited or not.

(The example appears to skip the latter Project Two: – jumping from the bottom,
but visits, I think, the earlier Project Two:)

In the meanwhile, here’s a first rough draft of something:

Expand disclosure triangle to view JS source
(() => {
    "use strict";

    // Selection moved to:
    // First incomplete non-empty row before current selection

    // Rob Trew @2026
    // Ver 0.03

    const
        neitherDoneNorBlank = '(not @done and @text != "")',
        precedingNotDone = `preceding::${neitherDoneNorBlank}`;

    // ------------------- JXA CONTEXT -------------------

    // main :: IO ()
    const main = () => {
        const doc = Application("TaskPaper").documents.at(0);

        return doc.exists()
            ? doc.evaluate({
                script: `${TaskPaperContext}`,
                withOptions: { precedingNotDone }
            })
            : "No documents open in TaskPaper";
    };

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

    // TaskPaperContext :: Editor -> IO ()
    const TaskPaperContext = (editor, { precedingNotDone }) => {
        const
            matches = editor.outline.evaluateItemPath(
                `${precedingNotDone}[-1]`,
                editor.selection.startItem
            ),
            match = matches && (0 < matches.length)
                ? matches[0]
                : null;

        return match
            ? (
                editor.moveSelectionToItems(
                    match, match.bodyString.length
                ),
                match.bodyContentString
            )
            : "No remaining incomplete tasks in document.";
    };

    // MAIN ---
    return main();
})();

Let me know where it diverges from what you have in mind.

(and have a good weekend)

The only part of that example that seems unclear is the question of whether project names are visited or not.

This will teach me to make up examples late at night—my apologies! :man_shrugging:t2:

In the last example, the project should have should be marked as done:

Project Two: @done(2026-02-14)

•••

It looks like your rough draft does exactly what I want.

Your skill at interpreting my bad examples is amazing. Kudos!

I just used the script during my weekly review and it worked flawlessly.

Thank you again for your help! May your weekend be great!

1 Like