Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions packages/diffs/src/renderers/DiffHunksRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,11 +281,21 @@ export class DiffHunksRenderer<LAnnotation = undefined> {
* Enter edit-session mode: hunk updates preserve the current region
* skeleton instead of recomputing hunks, and rendering happens locally
* with the token transformer forced on (worker-pool requests/results are
* suspended for this renderer). Called on every editor attach, including
* a re-attach after recycle.
* suspended for this renderer). An empty additions document gets one row so
* the editor has a line for its caret. Called on every editor attach,
* including a re-attach after recycle.
*/
public beginEditSession(): void {
this.editSessionActive = true;
const diff = this.diffCache;
if (diff != null && !diff.isPartial && diff.additionLines.length === 0) {
Object.assign(
diff,
recomputeEmptyDocumentDiff(diff, this.options.parseDiffOptions)
);
this.markEditSessionPass(diff);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid dirtying attach-only empty sessions

When an editor is attached to a diff whose new file starts empty and is then detached without any user edit, this shim sets editSessionDirty. The detach path calls finishEditSessionForDiff, which recomputes through the edit pipeline and preserves the synthetic [''] addition line, so a previously deletion-only/empty-new diff keeps rendering a green blank addition row after editing ends. Only mark the session dirty after an actual document change, or restore the original zero-line addition shape on session exit.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

fixed in 4aa6f2a, empty caret-host rows are now removed during session exit, with split and unified regression coverage.

this.clearRenderCache();
}
}

/** Leave edit-session mode. The exit recompute is the host's concern. */
Expand Down Expand Up @@ -652,7 +662,7 @@ export class DiffHunksRenderer<LAnnotation = undefined> {
);
result.code.additionLines[0] = createPlainAdditionLineElement(
0,
textDocument
textDocument.getLineText(0)
);
this.markEditSessionPass(diff);
} else if (this.editSessionActive) {
Expand Down Expand Up @@ -1110,6 +1120,14 @@ export class DiffHunksRenderer<LAnnotation = undefined> {
expandedHunks: forcePlainText ? true : undefined,
collapsedContextThreshold,
});
if (
this.editSessionActive &&
diff.additionLines.length === 1 &&
diff.additionLines[0] === '' &&
result.code.additionLines[0] == null
) {
result.code.additionLines[0] = createPlainAdditionLineElement(0, '');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the real unified row index

When this fallback runs for an empty-new diff in unified mode with deleted lines, the synthetic row is created with the helper's default data-line-index of 0,0; because this initial render cache is not dirty, processDiffResult does not rewrite it to the actual unified/split indexes (for example 1,0 after one deletion row). VirtualizedFileDiff parses data-line-index for height deltas and scroll anchors, so the empty addition row can be measured/cached under the deletion row's index and corrupt virtualized layout; pass the current rendered indexes into the synthetic element instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

fixed in 4aa6f2a

}
return { result, options };
}

Expand Down Expand Up @@ -2339,14 +2357,17 @@ function realignAdditionHastLines(
realigned[index] ??= hastLines[index];
}
for (let index = prefix; index < nextLines.length; index++) {
realigned[index] ??= createPlainAdditionLineElement(index, textDocument);
realigned[index] ??= createPlainAdditionLineElement(
index,
textDocument.getLineText(index)
);
}
return realigned;
}

function createPlainAdditionLineElement(
lineIndex: number,
textDocument: DiffsTextDocument
lineText: string
): HASTElement {
return {
type: 'element',
Expand All @@ -2366,7 +2387,7 @@ function createPlainAdditionLineElement(
children: [
{
type: 'text',
value: textDocument.getLineText(lineIndex),
value: lineText,
},
],
},
Expand Down
32 changes: 31 additions & 1 deletion packages/diffs/test/editorDiffEmptyDocument.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,38 @@ function replaceAll(editor: Editor<undefined>, newText: string): void {
);
}

describe('diff editor: select-all then delete', () => {
describe('diff editor: empty document', () => {
for (const diffStyle of ['split', 'unified'] as const) {
test(`renders line 1 and a caret when the new file starts empty (${diffStyle})`, async () => {
const fixture = await createDiffEditorFixture(diffStyle, 'removed\n', '');
const { editor, container } = fixture;

try {
const content = findAdditionContent(container);
expect(content).toBeDefined();
if (content == null) return;
expect(countEditableLineEls(content)).toBe(1);
expect(
[...content.children].some(
(child) => (child as HTMLElement).dataset.line === '1'
)
).toBe(true);

editor.setSelections([
{
start: { line: 0, character: 0 },
end: { line: 0, character: 0 },
direction: 'none',
},
]);
expect(
container.shadowRoot?.querySelector('[data-caret]') != null
).toBe(true);
} finally {
await fixture.cleanup();
}
});

test(`keeps an editable line, accepts typing, and undoes (${diffStyle})`, async () => {
const fixture = await createDiffEditorFixture(
diffStyle,
Expand Down