Skip to content

Applying custom line heights after the edits are processed#297999

Merged
alexdima merged 20 commits intomainfrom
defeated-dingo
Feb 26, 2026
Merged

Applying custom line heights after the edits are processed#297999
alexdima merged 20 commits intomainfrom
defeated-dingo

Conversation

@aiday-mar
Copy link
Contributor

@aiday-mar aiday-mar commented Feb 26, 2026

fixes #297959
fixes #289877

Copilot AI review requested due to automatic review settings February 26, 2026 13:37
@aiday-mar aiday-mar self-assigned this Feb 26, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR changes how variable/custom line heights are applied during structural model edits (insert/delete), deferring custom line-height computation until after the view model has processed the full edit batch and projections/coordinate conversion are stable.

Changes:

  • Removed “lineHeightsAdded” plumbing from onLinesInserted across ViewLayoutLinesLayoutLineHeightsManager.
  • Deferred recomputation/application of custom line heights in ViewModel.onDidChangeContentOrInjectedText until after all raw changes are applied.
  • Extended ModelRawLinesDeleted to carry lastUntouchedLinePostEdit, and updated relevant tests.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/vs/editor/test/common/viewLayout/linesLayout.test.ts Updates tests to match the new onLinesInserted(from,to) signature.
src/vs/editor/test/common/viewLayout/lineHeights.test.ts Updates tests for new signature and adds a new deletion-related test (currently problematic).
src/vs/editor/test/common/model/model.test.ts Updates expected ModelRawLinesDeleted constructor args.
src/vs/editor/common/viewModel/viewModelLines.ts Adds debug console.log statements inside model line insertion handling.
src/vs/editor/common/viewModel/viewModelImpl.ts Defers custom line height application until projections are stable after batch processing.
src/vs/editor/common/viewLayout/viewLayout.ts Updates onLinesInserted API to drop the custom-line-height payload.
src/vs/editor/common/viewLayout/linesLayout.ts Updates onLinesInserted and forwards to LineHeightsManager without payload.
src/vs/editor/common/viewLayout/lineHeights.ts Removes inserted-line height payload handling; insertion now assumes default height until recomputed later.
src/vs/editor/common/textModelEvents.ts Extends ModelRawLinesDeleted with lastUntouchedLinePostEdit.
src/vs/editor/common/model/tokens/tokenizationFontDecorationsProvider.ts Adds debug console.log statements (including logging full buffer content).
src/vs/editor/common/model/textModel.ts Computes and emits lastUntouchedLinePostEdit when generating ModelRawLinesDeleted.

Comment on lines 358 to 434
@@ -370,6 +375,7 @@ export class ViewModel extends Disposable implements IViewModel {
if (linesDeletedEvent !== null) {
eventsCollector.emitViewEvent(linesDeletedEvent);
this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber);
deferredCustomLineHeightRanges.push({ fromLineNumber: change.lastUntouchedLinePostEdit, toLineNumber: change.lastUntouchedLinePostEdit });
}
hadOtherModelChange = true;
break;
@@ -379,7 +385,8 @@ export class ViewModel extends Disposable implements IViewModel {
const linesInsertedEvent = this._lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLineBreaks);
if (linesInsertedEvent !== null) {
eventsCollector.emitViewEvent(linesInsertedEvent);
this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber, this._getCustomLineHeightsForLines(change.fromLineNumberPostEdit, change.toLineNumberPostEdit));
this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber);
deferredCustomLineHeightRanges.push({ fromLineNumber: change.fromLineNumberPostEdit, toLineNumber: change.toLineNumberPostEdit });
}
hadOtherModelChange = true;
break;
@@ -394,11 +401,13 @@ export class ViewModel extends Disposable implements IViewModel {
}
if (linesInsertedEvent) {
eventsCollector.emitViewEvent(linesInsertedEvent);
this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber, this._getCustomLineHeightsForLines(change.lineNumberPostEdit, change.lineNumberPostEdit));
this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber);
deferredCustomLineHeightRanges.push({ fromLineNumber: change.lineNumberPostEdit, toLineNumber: change.lineNumberPostEdit });
}
if (linesDeletedEvent) {
eventsCollector.emitViewEvent(linesDeletedEvent);
this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber);
deferredCustomLineHeightRanges.push({ fromLineNumber: change.lineNumberPostEdit, toLineNumber: change.lineNumberPostEdit });
}
break;
}
@@ -412,6 +421,19 @@ export class ViewModel extends Disposable implements IViewModel {
if (versionId !== null) {
this._lines.acceptVersionId(versionId);
}

// Apply deferred custom line heights now that projections are stable
if (deferredCustomLineHeightRanges.length > 0) {
this.viewLayout.changeSpecialLineHeights((accessor: ILineHeightChangeAccessor) => {
for (const range of deferredCustomLineHeightRanges) {
const customLineHeights = this._getCustomLineHeightsForLines(range.fromLineNumber, range.toLineNumber);
for (const data of customLineHeights) {
accessor.insertOrChangeCustomLineHeight(data.decorationId, data.startLineNumber, data.endLineNumber, data.lineHeight);
}
}
});
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deferredCustomLineHeightRanges can accumulate duplicate/overlapping ranges in a single edit batch, causing repeated getCustomLineHeightsDecorationsInRange queries and repeated insertOrChangeCustomLineHeight calls for the same decoration IDs. Consider deduplicating/merging ranges (or collecting decoration IDs) before querying/applying to avoid avoidable work on large edit batches.

Copilot uses AI. Check for mistakes.
Comment on lines 526 to 531
test('deleting line 2 with lineHeightsRemoved re-adding at line 1 moves special line to line 1', () => {
const manager = new LineHeightsManager(10, []);
manager.insertOrChangeCustomLineHeight('dec1', 2, 2, 20);
assert.strictEqual(manager.heightForLineNumber(2), 20);
manager.onLinesDeleted(2, 2);
assert.strictEqual(manager.heightForLineNumber(1), 20);
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new test’s expectation doesn’t match the current LineHeightsManager.onLinesDeleted behavior: deleting line 2 will not move an existing custom height from line 2 to line 1 (it collapses to fromLineNumber in existing tests/comments in this file). As written, this test should fail. If you intend to verify the “remove + re-add at a different line” scenario, the test needs to explicitly simulate that (e.g., remove the decoration ID and then insert/change it at line 1 after the delete), or adjust the expected line number.

Suggested change
test('deleting line 2 with lineHeightsRemoved re-adding at line 1 moves special line to line 1', () => {
const manager = new LineHeightsManager(10, []);
manager.insertOrChangeCustomLineHeight('dec1', 2, 2, 20);
assert.strictEqual(manager.heightForLineNumber(2), 20);
manager.onLinesDeleted(2, 2);
assert.strictEqual(manager.heightForLineNumber(1), 20);
test('deleting line 2 keeps special line height at fromLineNumber (line 2)', () => {
const manager = new LineHeightsManager(10, []);
manager.insertOrChangeCustomLineHeight('dec1', 2, 2, 20);
assert.strictEqual(manager.heightForLineNumber(2), 20);
manager.onLinesDeleted(2, 2);
// After deletion, the decoration collapses to fromLineNumber (2), not moved to line 1
assert.strictEqual(manager.heightForLineNumber(1), 10);
assert.strictEqual(manager.heightForLineNumber(2), 20);

Copilot uses AI. Check for mistakes.
assert.strictEqual(manager.getAccumulatedLineHeightsIncludingLineNumber(6), 110);
});

test('deleting line 2 with lineHeightsRemoved re-adding at line 1 moves special line to line 1', () => {
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new test title is hard to parse and grammatically off ("with lineHeightsRemoved re-adding..."). Please reword it to clearly describe the scenario and expected outcome (e.g. mention deletion, then decoration removal/re-add, and what line number should end up special).

Suggested change
test('deleting line 2 with lineHeightsRemoved re-adding at line 1 moves special line to line 1', () => {
test('deleting line 2 and re-adding its custom height on line 1 makes line 1 special', () => {

Copilot uses AI. Check for mistakes.
@alexdima alexdima merged commit 4dfa19f into main Feb 26, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants