Auto completion for tag values

Hi,

why is there no autocompletion for (existing) tag values when typing the tag and an opening bracket?

CME

I’m not sure what you mean… there is autocomplete for tags generally. Is that working for you? If that’s not working then make sure that you have “Editor: Auto-complete tags as you type” selected in preference.

If you are talking about something different can you give an example of exactly what you mean. Thanks!

My file contained tags for places.

Let’s say

@Berlin
@Dublin
@London

When I type @Du it proposes @Dublin

I renamed the tags to

@place(Berlin)
@place(Dublin)
@place(London)

When typing @p it proposes @place. When typing @place( it does not recommend anything (Berlin, Dublin, …)

Thanks!

Sorry for the delay here, been thinking about this, but I don’t see an easy solution that doesn’t also have drawbacks. In your case it seems good, but in cases where there are many potential values (such as @done(data)) or where multiple tags are used @tag(1, 2, 3) I see a lot of edge cases that might cause irritation.

Is there an existing app that you think does this well?

Obsidian can autocomplete nested tags, though the underlying mechanism for nested tags might be deeply different from tags with values (though functionally they seem interchangeable).

You might be able to use a script like this to cycle through the tags.

Brand new to Taskpaper and was looking for exactly the same thing

Perhaps the simplest way to start this is based off the global tags list? So if I have @status(next) in the global tags list only then it’ll autocomplete. This way you won’t run into the @done(date) problem

@taskSloth 's script will cycle through @CME 's places as an excellent alternative to autocompletion:

function TaskPaperContextScript(editor, options) {
    let outline = editor.outline
    let selection = editor.selection
	
    outline.groupUndoAndChanges(() => {
        editor.selection.selectedItems.forEach((item) => {
		if (item.getAttribute("data-place") === "Berlin"){
			item.setAttribute("data-place", "Dublin")
        } else if (item.getAttribute("data-place") === "Dublin"){
			item.setAttribute("data-place", "London")
        } else if (item.getAttribute("data-place") === "London"){
			item.setAttribute("data-place", "Paris")
		} else {
			item.setAttribute("data-place", "Berlin")
		}});
	})
	
	editor.moveSelectionToItems(selection)
}

Application("TaskPaper").documents[0].evaluate({
    script: TaskPaperContextScript.toString()
});

To generalize tag-value cycling a bit, we could specify:

  • one or more tag names, and
  • a list of values for each tag name

in the withOptions key-value dictionary passed to document.evaluate

in this kind of pattern:

e.g. for @place or @grade or @sprint

docs.at(0).evaluate({
    script: `${TaskPaperContext}`,
    withOptions: {
        "place": [
            "Berlin", "Dublin",
            "London", "Paris"
        ],
        "grade": "ABCD",
        "sprint": [10, 25, 55]
    }
})

Generalized tag-value cycling:

Expand disclosure triangle to view JS Source
/* eslint-disable max-lines-per-function */
(() => {
    "use strict";

    // Cycle tag value of:
    // - First tag in selection(s) which matches a key in
    //   options,
    // - to the next value in the list given in options
    //   for that key.

    // Rob Trew @2021

    // Ver 0.05
    // (Where a line has more than one of the tags with
    //  cycle lists given in options, the *leftmost* of
    //  these tags in the selected line will be cycled)

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

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

        return 0 < docs.length ? (
            docs.at(0).evaluate({
                script: `${TaskPaperContext}`,
                withOptions: {
                    "place": [
                        "Berlin", "Dublin",
                        "London", "Paris"
                    ],
                    "grade": "ABCD",
                    "sprint": [10, 25, 55]
                }
            })
        ) : "No documents open in TaskPaper.";
    };

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

    const TaskPaperContext = (editor, options) => {
        const tp3Main = () => {
            const
                selection = editor.selection,
                items = selection.selectedItems,
                startLocn = selection.location,
                endItem = selection.endItem,
                endOffset = selection.endOffset,
                cyclableNames = Object.keys(options || {})
                .map(k => `data-${k}`);

            let report = "";

            return 0 < items.length ? (
                0 < cyclableNames.length ? (
                    editor.outline.groupUndoAndChanges(() => {
                        report = items.filter(
                                x => Boolean(
                                    x.bodyString.trim()
                                )
                            )
                            .map(valueCycled(cyclableNames))
                            .join("\n");
                    }),
                    editor.moveSelectionToRange(
                        startLocn,
                        editor.getLocationForItemOffset(
                            endItem, endOffset
                        )
                    ),
                    report
                ) : "No name list pairs supplied in options."
            ) : "No TaskPaper items selected";
        };

        // ---------------- VALUE CYCLING ----------------

        // valueCycled :: [String] -> Item -> IO String
        const valueCycled = cyclableNames =>
            item => {
                const
                    overlap = intersect(
                        item.attributeNames
                    )(
                        cyclableNames
                    ),
                    tagName = 0 < overlap.length ? (
                        overlap[0]
                    ) : cyclableNames[0],
                    shortName = tagName.slice(5),
                    newValue = nextValue(
                        // Values as [String] array.
                        Array.from(options[shortName])
                        .map(x => `${x}`)
                    )(
                        item.getAttribute(tagName)
                    );

                return (
                    item.setAttribute(tagName, newValue),
                    `@${shortName} -> ${newValue}`
                );
            };


        // nextValue :: [String] -> String -> String
        const nextValue = ks =>
            k => {
                const iPosn = ks.findIndex(x => k === x);

                return -1 !== iPosn ? (
                    ks[(1 + iPosn) % ks.length]
                ) : ks[0];
            };

        // ------------------- GENERIC -------------------

        // intersect :: (Eq a) => [a] -> [a] -> [a]
        const intersect = xs =>
            // The intersection of lists xs and ys.
            ys => xs.filter(x => ys.includes(x));

        return tp3Main();
    };

    // JXA main
    return main();
})();
2 Likes

Slightly updated above FWIW ( to 0.3, behind disclosure triangle ) for more delicate preservation of selection start and and end when length of lines is changed by value cycling.

1 Like

Awesome. Added to wiki!

2 Likes

I’m using the tag-value cycling script for my task statuses, but would still love to have autocomplete for @person(firstSurname) tags that I add to tasks. Would it be possible to add this autocompletion for tags predefined in the tag list? I’d then just predefine the list of @people tags.

FYI same usecase here: Nesting tags

If autocomplete is a huge desire, I’d recommend using a text expansion utility. They are faster than autocomplete, and they offer a number of other useful features.

I use Keyboard Maestro and find it to be worth every penny (I use it for much more than text expansion). TextExpander and aText are worth trying as well.

Here is an example of a simple text expansion, using Keyboard Maestro. My triggers, in this instance, is a number, typed three times in a row (the trigger could be anything). The first macro took me less than a minute to set up, and the other three were copied and adjusted in seconds.

I have over a hundred text expansions set up, and I couldn’t be happier with them. They save a lot of time, and I don’t have to rely on the implementation of autocomplete. Mine work in any text field of any app.

3 Likes

Hey @Jim - thanks for the great suggestion. I too own and use KM, but didn’t actually consider using it for this use case! I’ll look at putting something together over the weekend and share it for others to use :slight_smile: . I guess sometimes the answer is staring you in the face all along

1 Like

You’re welcome @taskSloth !

Here is how I have it set up:

cool here is what I put together for others to use.

Notes:

  • Macro uses a built-in list, but this can be changed to use a text file with one name per line
  • The macro supports multi select
  • The macro deletes whitespaces for each of the selected names, but leaves dashes
  • The tag used is @person but can be changed in the 2nd to last step
  • The result is pasted at the cursor position with a space character

Autocomplete tag people.zip (1.6 KB)

2 Likes