Be more aggressive in determining suggested indent positions…

…at ends of transactions — instead of falling back to a transaction-wide auto-indent quite so easily.
This commit is contained in:
Andrew Dupont 2024-02-17 22:19:44 -08:00
parent 2827e6a4c4
commit afe6e1e7bf
2 changed files with 112 additions and 41 deletions

View File

@ -4120,43 +4120,93 @@ describe('WASMTreeSitterLanguageMode', () => {
await grammar.setQueryForTest('indentsQuery', `
["{"] @indent
["}"] @dedent
`);
`);
let emptyClassText = dedent`
class Example {
let emptyClassText = dedent`
class Example {
}
`;
}
`;
buffer.setText(emptyClassText);
buffer.setText(emptyClassText);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
buffer.setLanguageMode(languageMode);
await languageMode.ready;
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
buffer.setLanguageMode(languageMode);
await languageMode.ready;
editor.setCursorBufferPosition([1, 0]);
editor.indent();
await languageMode.atTransactionEnd();
editor.insertText('// this is a comment', { autoIndent: true });
await languageMode.atTransactionEnd();
expect(editor.lineTextForBufferRow(1)).toEqual(' // this is a comment');
editor.setCursorBufferPosition([1, 0]);
editor.indent();
await languageMode.atTransactionEnd();
editor.insertText('// this is a comment', { autoIndent: true });
await languageMode.atTransactionEnd();
expect(editor.lineTextForBufferRow(1)).toEqual(' // this is a comment');
editor.insertNewline();
await languageMode.atTransactionEnd();
await wait(0);
expect(editor.lineTextForBufferRow(2)).toEqual(' ');
editor.insertNewline();
await languageMode.atTransactionEnd();
await wait(0);
expect(editor.lineTextForBufferRow(2)).toEqual(' ');
editor.insertNewline();
await languageMode.atTransactionEnd();
await wait(0);
expect(editor.lineTextForBufferRow(3)).toEqual(' ');
editor.insertNewline();
await languageMode.atTransactionEnd();
await wait(0);
expect(editor.lineTextForBufferRow(3)).toEqual(' ');
editor.insertNewline();
await languageMode.atTransactionEnd();
await wait(0);
expect(editor.lineTextForBufferRow(4)).toEqual(' ');
editor.insertNewline();
await languageMode.atTransactionEnd();
await wait(0);
expect(editor.lineTextForBufferRow(4)).toEqual(' ');
});
it(`can indent properly in a multi-cursor environment without auto-indenting large ranges of the buffer`, async () => {
jasmine.useRealClock();
const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig);
expect(editor.getUndoGroupingInterval()).toBe(300);
await grammar.setQueryForTest('indentsQuery', `
["{"] @indent
["}"] @dedent
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
buffer.setLanguageMode(languageMode);
await languageMode.ready;
// No spaces after the `{`s in these examples so that we can more easily
// compare expected output to actual output.
buffer.setText(dedent`
function test () {return }
function test () {return }
function test () {return }
`);
editor.setCursorBufferPosition([0, 18])
editor.addCursorAtBufferPosition([2, 18])
editor.addCursorAtBufferPosition([4, 18])
editor.insertNewline({
autoIndent: true,
autoIndentNewline: true,
autoDecreaseIndent: true
})
await wait(0);
expect(buffer.getText()).toBe(dedent`
function test () {
return }
function test () {
return }
function test () {
return }
`)
})
});
});

View File

@ -1270,6 +1270,7 @@ class WASMTreeSitterLanguageMode {
if (!controllingLayer.treeIsDirty || options.forceTreeParse || !this.useAsyncParsing || !this.useAsyncIndent) {
indentTree = controllingLayer.getOrParseTree();
} else {
let lineForRow = this.buffer.lineForRow(row)
// We can't answer this yet because we don't yet have a new syntax
// tree. Return a promise that will fulfill once we can determine the
// right indent level.
@ -1278,23 +1279,31 @@ class WASMTreeSitterLanguageMode {
// preliminary indent level and then follow up later with a more
// accurate one. It's a bit disorienting that the editor falls back to
// an indent level of `0` when a newline is inserted.
return this.atTransactionEnd().then(({ changeCount }) => {
if (changeCount > 1) {
// There were multiple changes in this transaction, so it's not
// safe to assume that the original row still needs its indentation
// adjusted. The row could've been shifted up or down by other
// edits, or it could've been deleted entirely.
return this.atTransactionEnd().then(() => {
if (lineForRow !== this.buffer.lineForRow(row)) {
// We're now revisiting this indentation question at the end of the
// transaction. Other changes may have taken place since we were
// first asked what the indent level should be for this line. So
// how do we know if the question is still relevant? After all, the
// text that was on this row earlier might be on some other row
// now.
//
// Instead, we return `undefined` here, and the `TextEditor` will
// understand that its only recourse is to auto-indent the whole
// extent of the transaction instead.
// So we compare the text that was on the row when we were first
// called… to the text that is on the row now that the transaction
// is over. If they're the same, that's a _strong_ indicator that
// the result we return will still be relevant.
//
// If not, as is the case in this code path, we return `undefined`,
// signalling to the `TextEditor` that its only recourse is to
// auto-indent the whole extent of the transaction instead.
return undefined;
}
// Otherwise, it's safe to auto-indent this line alone, because it
// was the only change in this transaction. But we've retained the
// original values for `comparisonRow` and `comparisonRowIndent`
// because that's the proper basis from which to determine the given
// row's indent level.
// If we get this far, it's safe to auto-indent this line. Either it
// was the only change in its transaction or the other changes
// happened on different lines. But we've retained the original
// values for `comparisonRow` and `comparisonRowIndent` because
// that's the proper basis from which to determine the given row's
// indent level.
let result = this.suggestedIndentForBufferRow(row, tabLength, {
...rawOptions,
comparisonRow: comparisonRow,
@ -1379,6 +1388,18 @@ class WASMTreeSitterLanguageMode {
continue;
}
indentDelta--;
if (indentDelta < 0) {
// In the _indent_ phase, the delta won't ever go lower than `0`.
// This is because we assume that the previous line is correctly
// indented! The only function that `dedent` has for us is canceling
// out any `indent` and preventing false positives.
//
// So no matter how many `dedent` tokens we see on a particular line…
// if the _last_ token we see is an `indent` token, then it hints
// that the next line should be indented by one level.
indentDelta = 0;
}
}
}