Maintain frame when file externally edited

let’s say you’ve got a text file and you are focused on a branch in taskpaper like this as they would correspond to the text file on the right.

first of all, impressed with the resilience of TP when u edit stuff outside TP on the same txt file and it just maintains “frame” smoothly. for example, adding “hello” in a new line between lines 5 and 6. very nice!

my use case is that i have an AI system I collaborate with it, and its kind of like google docs where we are both updating a particular scoped project like “test project for taskpaper”.

it (seems like it) would be awesome if it could silently update the text within the current frame (per screenshot) and insert stuff and then taskpaper smoothly maintain frame and update it and show it.

is there a reason this isn’t supported?

I manually simulated this by using sublime (on the right) to add “cabbage” on the right in sublime between lines 10 and 11, and taskpaper pretty immediately loses frame and just renders the entire file normally rather than being in that one project scope.

curious to hear thoughts and not yet a formal request. i wonder if it’s also possible to update the text file in a non-obvious way as to accommodate this. currently, it just reads the file, updates content, and overwrites the entire file so there might be finer ways to do it on my part.

thoughts around any of this or how to make this possible right now? ideally, without having it go through taskpaper (like api) but I imagine worst case that is also viable

The TaskPaper documentation uses the term focus, as in the menu items Outline > Focus > (In | Out)

Is that what we are discussing here ?

You need to use another app to insert between lines outside the active TaskPaper editor focus, without disrupting that focus ?


Perhaps the external application is overwriting or losing the extended attributes (as in man xattr) which TaskPaper uses to serialize focus state into a document text file.

See, for example, this thread:

Yes, we are discussing the “Focus”.

I want to use another app to insert/update/delete between lines inside the active TaskPaper editor focus, without disrupting that focus

Will take a look at your reference

1 Like

I think it’s unlikely that this will change. Generally reloading the file when edited externally is a nice ability that TaskPaper has, but not really a core feature, and it’s not really mean to work in a collaborative way with AI and you going at same time.

@complexpoint’s suggestion about xattr is good and might solve the problem, but I think you’ll still run into rough edges this way.

Maybe too much work, but I think better way to do this would be to have the AI insert changes via TaskPaper’s AppleScript api. That would be better for undo stack, and focus, and generally that’s how external changes should come in.

3 Likes

I do something similar – triggering an external process which adds material to a focused outline, without losing the editor’s outline-focus state,

but, I do it in Bike rather than TaskPaper.

Why ? One simple and sufficient reason is that each element in a Bike outline has a unique and persistent ID, findable not only at run-time in the editor, but also as XML attribute values, in the serialised .bike file.

A problem for both focus retention and insertion/update targeting in TaskPaper, is that outline node ids are used in the TaskPaper editor at run-time, but are not to be found in the file, and don’t persist between sessions.

i.e. if I were choosing a tool from scratch for what you describe, I would probably go for Bike.

1 Like

Having said that, if you wanted to experiment with:

  1. Obtaining, from TaskPaper, before updating the text file, the ID of the focused row (if any) in the front document, and
  2. after updating the text file, trying to restore the TaskPaper editor focus to the row with a given ID.

Then a pre-update script to get the current focus ID might look like:

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

    const TaskPaperContext = editor => {
        const focusedItem = editor.focusedItem;

        return null !== focusedItem
            ? (() => {
                const { id, bodyString } = focusedItem;

                return { Right: { id, bodyString } };
            })()
            : { Left: "Editor not focused." };
    };

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

        return document.exists()
            ? document.evaluate({
                script: `${TaskPaperContext}`
            })
            : "No document open in TaskPaper."
    };

    return JSON.stringify(main(), null, 2);
})();

and a post-update script to restore focus to the node (if any) with a given ID might have the general shape:

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

    const focusID = "XyOxLkosMD-l";

    const TaskPaperContext = (editor, options) => {
        const { focusID } = options;

        return "string" === typeof focusID && 0 < focusID.length
            ? (() => {
                const maybeItem = editor.outline.getItemForID(focusID);

                return undefined !== maybeItem
                    ? (
                        editor.focusedItem = maybeItem,
                        { Right: `Focus restored to: "${maybeItem.bodyString}"` }
                    )
                    : { Left: `Node not found with ID: '${focusID}'` }
            })()
            : { Left: "No id string supplied" };
    };

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

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

    return JSON.stringify(main(), null, 2);
})();

( but I haven’t experimented to test when/if parse model ids can persist across external file updates. It seems a long shot. You may have to pass the whole of the updated text to a script which looks for diffs, without reloading and losing the parse model, and inserts things into the model more intelligently )

1 Like

and to simply look for the first line which has a particular .bodyString text, and restore focus to it, perhaps something like:

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

    // FOR EXAMPLE: (focusing on line with this bodyString, if found)
    const focusText = "To Organize Items:"

    const TaskPaperContext = (editor, options) => {

        const { focusText } = options;

        const rowFocusedLR = s => {
            const
                items = editor.outline.items,
                i = items.findIndex(x => s === x.bodyString);

            return -1 !== i
                ? (
                    editor.focusedItem = items[i],
                    { Right: `Focus restored to: '${s}'` }
                )
                : { Left: `Row not found as spelled: '${s}'` };
        };

        return "string" === typeof focusText && 0 < focusText.length
            ? rowFocusedLR(focusText)
            : { Left: "No focal row supplied" };
    };

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

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

    return JSON.stringify(main(), null, 2);
})();