Type of pasted row defers to selection row type?

This may be by design (and may not be relevant to ongoing Bike 2 work) but I wondered what the row type intention is when we:

  1. Select an empty existing line (of some row type) in Bike 1, and
  2. paste from a com.hogbaysoftware.bike.xml NSPasteBoard

It seems that:

  • the clipboard type of the first pasted row is ignored, and
  • the type of empty selection line is retained (if that type is not "body")

To summarise, the XML "data-type" of the first incoming line is preserved if

  1. we use the osascript interface’s document.import{from: XMLString as:"bike format"}, or
  2. simply open the XML file

but put aside (in favour of the existing type of the selected empty row) if we paste.

Test code (just copies XML to com.hogbaysoftware.bike.xml NSPasteBoard – try pasting to an empty row, with its own type, in a Bike document)

Test :: expand disclosure triangle to view JS source
(() => {
    "use strict";

    ObjC.import("AppKit");

    // ---------------------- MAIN -----------------------
    const main = () => {
        const xml = bikeFromForest(
            // All imported rows have type "heading"
            // for this test.
            // All rows but the first seem to preserve that
            // type on pasting to a selected empty row
            // with a different type.
            () => "data-type=\"heading\""
        )(
            x => x.root
        )(
            forest()
        );

        return (
            copyTypedString(true)(
                "com.hogbaysoftware.bike.xml"
            )(
                xml
            ),
            "XML Copied to `com.hogbaysoftware.bike.xml` NSPasteBoard."
        );
    };

    // bikeFromForest :: (a -> String) -> (a -> String) ->
    // Forest a -> XML String
    const bikeFromForest = fAttribs =>
        // A com.hogbaysoftware.bike.xml encoding of a
        // Forest of Any (a) values, given two (a -> String):
        // 1. A function returning a [k=v] attribute(s)
        //    string for an <li> tag
        // 2. A function returning contents for a <p> tag.
        fInline => trees => {
            const d = "  ";
            const go = indent => x => {
                const
                    xs = nest(x),
                    dent = d + indent,
                    ddent = d + dent,
                    attribs = fAttribs(x),
                    liTag = 0 < attribs.length
                        ? `<li ${attribs}>`
                        : "<li>";

                return [
                    indent + liTag,
                    `${dent}<p>${fInline(x)}</p>`,
                    ...0 < xs.length
                        ? [
                            `${dent}<ul>`,
                            ...xs.flatMap(go(ddent)),
                            `${dent}</ul>`
                        ]
                        : [],
                    `${indent}</li>`
                ];
            };

            return [
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
                "<html xmlns=\"http://www.w3.org/1999/xhtml\">",
                "  <head>",
                "    <meta charset=\"utf-8\"/>",
                "  </head>",
                "  <body>",
                "    <ul>",
                trees.flatMap(
                    go(d.repeat(3))
                )
                .join("\n"),
                "    </ul>",
                "  </body>",
                "</html>"
            ]
            .join("\n");
        };

    // Test sample
    const forest = () => [
        Node("Alpha")([
            Node("Beta")([
                Node("Gamma")([]),
                Node("Delta")([]),
                Node("Epsilon")([])
            ]),
            Node("Zeta")([Node("Eta")([])]),
            Node("Theta")([
                Node("Iota")([]),
                Node("Kappa")([]),
                Node("Lambda")([])
            ])
        ])
    ];

    // ----------------------- JXA -----------------------

    // copyTypedString :: Bool -> String -> String -> IO ()
    const copyTypedString = blnClear =>
        // public.html, public.rtf, public.utf8-plain-text
        pbType => s => {
            const pb = $.NSPasteboard.generalPasteboard;

            return (
                blnClear && pb.clearContents,
                pb.setStringForType(
                    $(s),
                    $(pbType)
                )
            );
        };

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

    // Node :: a -> [Tree a] -> Tree a
    const Node = v =>
        // Constructor for a Tree node which connects a
        // value of some kind to a list of zero or
        // more child trees.
        xs => ({
            root: v,
            nest: xs || []
        });

    // nest :: Tree a -> [a]
    const nest = tree => {
        // Allowing for lazy (on-demand) evaluation.
        // If the nest turns out to be a function –
        // rather than a list – that function is applied
        // here to the root, and returns a list.
        const xs = tree.nest;

        return "function" !== typeof xs
            ? xs
            : xs(root(tree));
    };

    // root :: Tree a -> a
    const root = tree =>
        // The value attached to a tree node.
        tree.root;

    return main();
})();

I think the behavior is coming from the rule: Paste text into an existing row, then keep existing row type. I think generally this behavior makes sense, but maybe should be special cased for empty rows… but actually now with more testing I’m not sure.

For example if you have this:

heading: One
body:

And you select and copy the e. Then what is written to the pasteboard is heading:e. Then if you paste that into the empty body row you will just see a plain e with the current behavior… which is what you would want I think. It would be odd in that case to have that empty line turn into a heading:e.

It could be argued that when the e is copied it shouldn’t also take the heading type, but I hesitate to change that at this point.

So I agree it’s odd, but I think trying to improve is complicated and not worth it with 2.0 in progress.

What you can do to get consistent behavior is paste while in block mode (1.0 or 2,0). Then the pasted rows will always be pasted as new and will retain types.

Also I hadn’t really been trying to fix this, but 2.0’s selection model seems to have made these issues less problematic and more consistent. In 2.0 if you have multiple rows copied then it will always just insert them instead of try to join them with existing row content… now that I’m thinking about it I see a few things that I can do to make case when you have single line of text on pasteboard more consistent… will look into fixing that now for 2.0.

1 Like

This is a case where I think 2.0 selection model can make things much clearer. What I am doing is:

  1. Text selections don’t include row type.
  2. Block selections do include row type.

When you paste a text selection in text mode the text pasted normally. When you paste text selection in block mode a new body row with text is inserted.

When you paste a block selection in either block or text mode a new row is always inserted, retaining original row type.

1 Like