0

I want to execute a series of edits to a document in the VSCode API. The function that makes it all happen is Workspace.applyEdit which returns a Thennable. This is my first time working with those, and the one being returned by this function is does not work as I expect.

Snippet 1:

import { window, workspace, WorkspaceEdit, Position } from 'vscode';

//doesn't work, only makes first insertion, althouh all info text prints
export function applyEditReprex() {
    let text = "\ntest\n";
    let target = window.activeTextEditor.document.uri;
    let positions = [
        new Position(10, 1),
        new Position(15, 1),
        new Position(20, 1)
    ];
    positions.reduce((applyThennable, position) => {
        return (
            applyThennable.then(() => {
                console.info("Making new edit");
                let edit = new WorkspaceEdit();
                edit.insert(target, position, text);
                workspace.applyEdit(edit);
            }))
    },
        Promise.resolve()
    ).then(() => {
        console.info("Finished edits.");
    })
}

Only a single instance of "test" appears in the target document on line 12. The log reports:

Making new edit
Making new edit
Making new edit
Finished edits.

Snippet 2:

My attempt to unroll above into straight chained calls:

import { window, workspace, WorkspaceEdit, Position } from 'vscode';

export function applyEditReprex2() {
    let text = "\ntest\n";
    let target = window.activeTextEditor.document.uri;
    let positions = [
        new Position(10, 1),
        new Position(15, 1),
        new Position(20, 1)
    ];
    console.info("Making new edit");
    let edit = new WorkspaceEdit();
    edit.insert(target, positions[0], text);
    workspace.applyEdit(edit).then(() => {
        console.info("Making new edit");
        let edit = new WorkspaceEdit();
        edit.insert(target, positions[1], text);
        workspace.applyEdit(edit).then(() => {
            console.info("Making new edit");
            let edit = new WorkspaceEdit();
            edit.insert(target, positions[2], text);
            workspace.applyEdit(edit).then(() => {
                console.info("Finished edits.");
            })
        })
    })
}

3 instances of "test" appear in the target file, on lines 12, 17, 22.

The log reports:

Making new edit
Making new edit
Making new edit
Finished edits.

Question

Are there any intricacies of reduce or fat arrow functions that that I may be unaware of that could be causing the first snippet to behave differently from the unrolled version? Or another way: is the unrolled version not equivalent to the reduce in some important way?

MilesMcBain
  • 1,115
  • 10
  • 12

1 Answers1

2

You forgot to return the thenable object from the promise's .then() callback, which is essential for promise chaining:

positions.reduce((prevPromise, position) => {
    return prevPromise.then(() => {
        console.info("Making new edit");
        const edit = new WorkspaceEdit();
        edit.insert(target, position, text);
        const applyThenable = workspace.applyEdit(edit);
        return applyThenable;
//      ^^^^^^^^^^^^^^^^^^^^
    });
}, Promise.resolve())

Btw, my understanding of the API from the documentation you linked is that you should only make a single WorkspaceEdit with multiple insertions:

const positions = [
    new Position(10, 1),
    new Position(15, 1),
    new Position(20, 1)
];
const edit = new WorkspaceEdit();
for (const position in positions) {
    edit.insert(target, position, text);
}
workspace.applyEdit(edit).then(() => {
    console.info("Finished multi-edit.");
})
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks. I really appreciate the answer and the link. I don't normally program JS and use a lang with implicit returns. Re your last comment I am emulating the API of another editor with VSCode so that its plugins will work with VSCode. It has slightly different behavior, which stops me using the multi-edit feature. – MilesMcBain Sep 19 '20 at 14:11