TaskPaper 3.7.6 Preview (310)


TaskPaper 3.7.6 adds support for lists of tag values.

- Feed the dog @job(kid1)
- Feed the cat @job(kid2)
- Rake the yard @job(kid1,kid2)

For example, in the above list you can now click on “kid1” in @job(kid1,kid2) and your list is filtered to show each @job that contains “kid1”. Previously TaskPaper considered that all tags had a single optional value, now you can have lists.

This feature is made possible through a new [l] modifier in TaskPaper’s query language. When that modifier is present both sides of the predicate a first converted to lists (separating items by commas) and then the lists are compared. Here are some examples:

// Matches first and last tasks
@job contains[l] kid1

// Matches last task
@job contains[l] kid1,kid2

// Also matches last task (order isn't important) when contains[l]
@job contains[l] kid2,kid1

You can also combine the l modifier with other modifiers like this:

// Matches no items because of the `s` (case sensitive) modifier
@job contains[sl] KID1

// Matches first and last items because of `i` (case insensitive, which is also default) modifier.
@job contains[il] KID1

Issue Tracker

If you don’t see your problem in the issue tracker then I’ve missed it. Please help by adding an issue or sending me a reminder. Issues in Ready and In Progress states are for v3.5.


Proposal to support querying attributes with multiple values

Wonderful, But how do I do < and >, like:

  • todo01 @gm2(90,100,200,300)
  • todo01 @gm2(100,200,300)

And I want to search for @gm2 < than 100?

I can do a “matches” :
@gm2 < 100 should show me the first one:

  • todo01 @gm2(90,100,200,300)
    but neither “<” or “>” are working like

Many thanks for making this! I’m already thinking of new power lists with it! :wink:


I am not sure if this has anything to do with the actual update or if this bug was present before, but I just noticed that when one uses the editor.outline/evaluateItemPath(), some projects do not come out. It is as if they had disappeared, although their task children are still there. I am going to give you a simple code example.

(() => {
    'use strict';

    // ------------------------- TASKPAPER 3 CONTEXT --------------------------
    const taskpaperContext = editor => {

        return editor.outline.evaluateItemPath(
                '(@type/..* union task///*) except (( //@private/..* union @private///*) union (archive///* union @done))'
            .map(x => x.bodyString)

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

    // writeFile :: FilePath -> String -> IO ()
    const writeFile = (strPath, strText) =>
            .stringByStandardizingPath, false,
            $.NSUTF8StringEncoding, null

        ds = Application('TaskPaper')

    const strReturnValue = ds.length > 0 ? ds.at(0)
            script: taskpaperContext.toString()
        }) : 'No document open in TaskPaper 3';

    return (
        writeFile('~/Desktop/test.taskpaper', strReturnValue),

I tried running a simpler query and the results are the same. Some projects just disappear from the result. This doesn’t happen if I run the Query in the program itself instead of using JavaScript. Any ideas why?

@complexpoint, have you realized this before now?

If I run the following in the code I provided above,

    return editor.outline.evaluateItemPath(
            'task///* except (@private///* union archive///*)'

I get no projects whatsoever. Was that the intended result?


I think you would might have to show me a source text before its very clear what you are looking at.

The script and the screen filter display are doing two different things:

  1. The script is gathering the matching nodes, and showing only their body text (no ancestors or descendants)
  2. The screen filter display shows both matches and their ancestors, to provide visual context.

If you want can give me a sample text I can try to have a quick look over the next couple of days.


I think I figured it out. It was hard to get it because the problem is with the tags I am trying to hide.

Here is the bug… If the last task or project within the project you want to show up is tagged with @private, the whole project will disappear. I think the problem is with how the evaluateItemPath interprets the paths I am using to not show projects that are empty or that all of their tasks are tagged @private.

Or the

(//@private/..* union @private///*)

of my query

(@type/..* union task///*) except ((//@private/..* union @private///*) union (archive///* union @done))

I am not sure why this is the case here. I am temporarily fixing this problem by making sure that the last task or project within the project is not tagged @private so that they appear in the result. Again, this is not something that I ever had an issue by using the Editor Search and the same query.

I just put something together really quick using information from a book in order to give you an example.

	Hebrews opens with a discussion of Christ as the selected one and the superior one.
	CHRIST, THE SELECTED ONE (1:1–3): The Father has chosen his Son to minister in four all-important areas:
		- Revelation (1:1–2a)
			- In the Old Testament, God revealed himself through his messengers (1:1).
			- In the New Testament, God revealed himself through his Messiah (1:2a).
		- Creation (1:2b–3)
			- The Son made the universe (1:2b).
			- The Son maintains the universe (1:3b).
		- Representation (1:3a): Jesus is the radiance of God’s glory and the exact representation of God’s being.
		- Purification (1:3c): Jesus died to cleanse us from our sins.
	CHRIST, THE SUPERIOR ONE (1:4–14): Christ is superior to the angels in three important ways:
		- In regard to his relationship (1:4–7): The Father has declared Jesus to be his unique Son. @private
		- In regard to his reign (1:8–12) @private
			- It will be a righteous reign (1:8–9). @private
			- It will be an eternal reign (1:10–12). @private
		- In regard to his reward (1:13–14): The Father has promised to make Jesus’ enemies his footstool. @private

	This chapter contains a warning from Christ against drifting away from the faith and a discussion of the work of Christ.
	THE WARNING FROM CHRIST (2:1–4): This warning has to do with God’s salvation:
		- The command (2:1–2)
			- Don’t drift from God’s message of truth (2:1).
			- Don’t disobey God’s message of truth (2:2).
		- The communicators (2:3): This salvation was preached by both Jesus and his apostles. @private
		- The confirmation (2:4): The gospel message was confirmed by signs and wonders. @private
		- His sovereign ministry (2:5–8a)
			- Christ created all people (2:5–6a).
			- Christ cares for all people (2:6b–7).
			- Christ commissioned all people (2:8a): Adam was put in charge of God’s original creation.
		- His submissive ministry (2:9a): Christ agreed to come to earth and become “lower than the angels.”
		- His saving ministry (2:8b–10)
			- The rebellion (2:8b): Sin caused people to forfeit their control over nature.
			- The redemption (2:9b–10): Christ died on the cross for everyone.
		- His sanctifying ministry (2:11–13): Christ now lives to make us holy.
		- His subduing ministry (2:14–15): By his death Jesus broke the power of Satan, who once held the power of death.
		- His sympathizing ministry (2:16–18): Having once suffered, Jesus is now able and willing to help those who are suffering.

	Jesus is compared to Moses and is declared to be greater than Moses. A warning is given from the Holy Spirit against the sin of unbelief.
	THE WORTHINESS OF THE SAVIOR (3:1–6): Jesus is compared and contrasted to Moses:
		- The comparison (3:2)
			- Jesus was faithful to God (3:2a).
			- Moses was faithful to God (3:2b).
		- The contrast (3:3–6)
			- Moses was a faithful servant in God’s house (3:5).
			- Jesus is the faithful son over God’s house (3:3–4, 6).
		- The conclusion (3:1): Jesus is greater, so fix your eyes on him. @private
	THE WARNING BY THE SPIRIT (3:7–19): This warning has to do with the terrible sin of unbelief.:
		- The example of unfaithfulness (3:9–11, 16–19)
			- Israel’s sin in the wilderness (3:9–10): They allowed unbelief to turn their hearts against God.
			- Israel’s sentence in the wilderness (3:11, 16–19): An entire generation died in the desert and did not enter the Promised Land.
		- The exhortation to faithfulness (3:7–8, 12–15)
			- When you hear God’s Word, heed God’s Word (3:7–8, 15).
			- Encourage one another daily (3:12–14).

	God promises rest for his people.
	THE PROMISE OF GOD (4:1–11): This promise involves the rest that God has prepared for his people:
		- The whereabouts (4:3b, 5–6, 8–10)
			- God’s Old Testament rest (4:3b, 5–6): This was the Promised Land, which Israel failed to enter due to unbelief.
			- God’s New Testament rest (4:8–10): This is the place of his perfect will, which is available for all believers.
		- The way (4:2–3a): “Only we who believe can enter his place of rest.”
		- The witnesses (4:4, 7)
			- Moses spoke of these rests (Gen. 2:2) (4:4).
			- David spoke of these rests (Ps. 95:11) (4:7).
		- The wisdom (4:1, 11): Guided by godly fear, we are to do our utmost to enter into this rest.
	THE POWER OF GOD (4:12–13):
		- What it is (4:12a-b)
			- Its definition (4:12a): It is the spoken and written Word of God.
			- Its description (4:12b): It is living, active, and sharper than any double-edged sword.
		- What it does (4:12c–13)
			- It exposes all thoughts and desires (4:12c).
			- It exposes all humankind (4:13). @private
	THE PRIEST OF GOD (4:14–16):
		- Who he is (4:14a): He is Jesus, the Son of God.
		- What he is (4:14b–15): He is our great High Priest.
			- He once was tempted in all areas (4:15).
			- He now can help us in any area (4:14b).
		- Where he is (4:16): At the very throne of grace.

	Christ, the great High Priest, is compared to Aaron, the first high priest.
	THE REQUIREMENTS IN REGARD TO THE PRIESTS (5:1–10): The author of Hebrews compares and contrasts the high priestly ministries of both Aaron and Christ:
		- Comparisons (5:1–4)
			- Both were selected by God from among men (5:1a, 4).
			- Both were appointed to represent people before God (5:1b).
			- Both were to pray and offer up sacrifices (5:1c).
			- Both were to demonstrate compassion (5:2a).
			- Both experienced infirmities of the flesh (5:2b–3).
		- Contrasts (5:5–10)
			- Only Christ is called God’s Son (5:5).
			- Only Christ was given an everlasting priesthood (5:6a).
			- Only Christ was made a priest after the order of Melchizedek (5:6b, 9–10).
			- Only Christ cried out to God in Gethsemane “with a loud cry and tears, to the one who could deliver him out of death” (5:7–8). @private
		- The frustration (5:11–12a)
			- The author has much to say, but his readers are slow to learn (5:11).
			- They should be teachers but instead need to be taught (5:12a).
		- The food (5:12b–14)
			- Baby believers can be fed only milk (5:12b–13).
			- Mature believers can easily digest solid food (5:14).

	The author of Hebrews challenges his readers to strive for spiritual maturity and writes about how such maturity may be obtained.
		- The author’s challenge (6:1–3): The writer of Hebrews issues a twofold challenge to his readers.
			- Don’t go backward (6:1–2): He urges them to stop going over the same old ground again and again.
				- In the importance of turning from sin and toward God (6:1)
				- In the importance of baptism, the laying on of hands, the resurrection, and judgment (6:2)
			- Do go forward (6:3): Push on to maturity in Christ.
		- The author’s concern (6:4–8): He warns in regard to a dreadful situation.
			- The impossibility in this situation (6:4–6)
				- The who (6:4b–5): Those who have tasted the heavenly gift, who have shared in the Holy Spirit and have tasted of God’s Word.
				- The what (6:6a): After experiencing this, they turn from God.
				- The why (6:4a, 6b): These people cannot be brought back to repentance, for they crucify the Son of God all over again.
			- The illustration for this situation (6:7–8): The author refers to a piece of land to illustrate his point.
				- When the land is fruitful, it is blessed (6:7).
				- When the land is fruitless, it is cursed (6:8).
		- The author’s confidence (6:9–12): He is confident his warning does not apply to his readers.
	THE ANCHOR FOR SPIRITUAL MATURITY (6:13–20): This desired maturity is assured:
		- Because of the Father’s promise (6:13–18)
			- God promised to bless Abraham, and he did (6:13–15).
			- God promised to bless us, and he will (6:16–18).
		- Because of the Savior’s priesthood (6:19–20)

	The author identifies and equates the priesthood of Jesus with that of Melchizedek.
		- The person of Melchizedek (7:1a, 2b–3)
			- Who he was (7:2b): His name means “king of justice,” and he was also the “king of peace.”
			- What he did (7:1a): He was both priest and king over the city of Salem.
			- Where he came from (7:3): There is no record of either his birth or his death.
		- The preeminence of Melchizedek (7:1b–2a)
			- The battle (7:2a): Following the defeat of his enemies, Abraham met Melchizedek and paid tithes to him.
			- The blessing (7:1b): Melchizedek blessed Abraham.
	A THEOLOGICAL PERSPECTIVE (7:4–28): The author lists the various characteristics of Jesus, who, according to the Father’s decree, is to be a priest after the order of Melchizedek (see Ps. 110:4). Thus, his priesthood would be:
		- Royal (as was that of Melchizedek) (see 7:1)
		- Superior (7:4–10)
			- To whom? (7:5–7): To Levi, founder of the levitical priesthood.
			- Why? (7:4, 8–10)
				- Abraham was the ancestor of Levi (7:9).
				- The yet unborn Levi thus tithed to Melchizedek while still in the loins of Abraham (7:4, 8, 10).
		- Independent (7:11–15)
			- Independent of the law (7:11–12).
			- Independent of the tribe of Levi (7:13–15): Christ came from the tribe of Judah.
		- Everlasting (7:16–17)
		- Guaranteed (7:20–22): The Father himself took an oath concerning this.
		- Continuous (7:23)
		- Permanent (7:24)
		- Holy (7:26)
		- All-sufficient (7:18–19, 25, 27)
		- Flawless (7:28)

Run the query I showed you and you will see how some Projects disappear.


Okay, I am now sure what is the problem…

I have used paths to eliminate empty folders. It works like this,

(//@private/..* union @private///*)

That not only hides the tasks that have tags and its children, but it also hides those parents that have nothing after the tags are eliminated.

This is super useful when one has a file that is used for long term tasks. I have found that certain projects are used constantly and even if the tasks inside are finished, the project tend to be used later on. The best thing is to just keep the empty projects and then filled the tasks are they come up.


Good question, I haven’t implement <, <=, >, >=, or matches for the [l] modifier yet. It wasn’t entirely clear to me how it should work.

When you use [l] modifier both sides of the comparison are first converted into arrays. I think the vast majority of the time the value on the right will be a single element array, but it might have multiple elements as in the @job contains[l] kid2,kid1 example. And often the list on the right will be empty, for the case when you specify a tag with no associated value.

So the question (considering a sampling of < at the moment) is what the results of these list comparisons should be:

(1,2,3) < (1)
(1,2,3) < (2)
(1,2,3) < (2,0)
(0) < (2,1,3)
() < (1)

In the case of the contains[l] relationship true is returned if each item in the right side list is contained in the left side list. I think that makes sense from users point of view.

It’s not quite as clear for other comparison operators. But going from the contains logic I guess that means that for other relations the logic should be “for each item in the right side list there must be at least one item in the left side list for which the comparison holds”? I’ll do it this way unless I get some feedback to convince me otherwise.

The other option would be that the relation has to hold for all items in the left side list.


This is a pattern that will be familiar to users of languages like Haskell and Purescript – the (<) operator is being lifted into a relationship between lists of numbers rather than between atomic numbers.

If you try this at https://www.tryhaskell.org/

with the and function applied to the resulting [Bool] list:




compare, for example, with the result of:

and $ liftM2 (<) [1,2,3] [4,5]


for example (FWIW):

(() => {
    'use strict';

    // and :: [Bool] -> Bool
    const and = xs => {
        let i = xs.length;
        while (i--)
            if (!xs[i]) return false;
        return true;

    // e.g. [(*2),(/2), sqrt] <*> [1,2,3]
    // -->  ap([dbl, hlf, root], [1, 2, 3])
    // -->  [2,4,6,0.5,1,1.5,1,1.4142135623730951,1.7320508075688772]

    // A list of functions applied to a list of arguments
    // ap (<*>) :: [(a -> b)] -> [a] -> [b]
    const ap = (fs, xs) => //
        [].concat.apply([], fs.map(f => //
            [].concat.apply([], xs.map(x => [f(x)]))));

    // liftM2 f xs ys = [f] <*> xs <*> ys
    // liftM2 :: (a -> b -> c) -> [a] -> [b] -> [c]
    const liftM2 = (f, a, b) =>
        Array.isArray(a) ? (
            ap(map((g => x => y => g(x, y))(f), a), b)
        ) : Object.keys(a)
        .indexOf('nothing') !== -1 ? (
            a.nothing || b.nothing ? a : pureMay(f(a.just, b.just))
        ) : undefined;

    // map :: (a -> b) -> [a] -> [b]
    const map = (f, xs) => xs.map(f);

    // plus :: Num -> Num -> Num
    const plus = (a, b) => a + b;

    // less :: Ord a => a -> a -> Bool
    const less = (x, y) => x < y;

    // TEST ------------------------------------------------------------------

    liftM2(plus, [0, 1], [0, 2])
    // -> [0,2,1,3]

    return and(liftM2(less, [1,2,3], [2,0]));

Tho, on reflection I guess that and is probably too blunt an instrument for a useful comparison between two lists of Bool values :- )


I’ve just posted a new release (311) that I think solves the problem. For example to get “@gm2 < than 100” you would type:

@gm2 <[dl] 100

The d modifier is needed because you want to compare numbers. And then the l modifier means you want to convert to lists before compare.


Wonderful. Many thanks for that. Super helpful.