Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
36 changes: 36 additions & 0 deletions FIXES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,39 @@
# Resize reflow for the normal buffer

## Summary

This change ports xterm.js 5.5.0 resize reflow into XTerm.NET. Shrinking column width re-wraps long logical lines onto additional `IsWrapped` rows instead of truncating them; growing width merges wrapped groups back. The alternate buffer is excluded via an explicit `hasScrollback: false` constructor flag.

A related latent bug is fixed: when the buffer was at capacity and row count shrank, `CircularList.Resize` ran before trimming and kept the oldest lines, silently discarding the live screen bottom. Resize now trims from the top (raising `Trimmed`) before shrinking capacity.

## Why

Without reflow, shrinking a terminal window and growing it back left every long line permanently truncated at the narrowest width — the primary defect tracked as ISS-007. Scrollback lines were also lost on capacity shrink because `CircularList.Resize` preserved the wrong end of the buffer.

## Files changed

- `src/XTerm.NET/Buffer/BufferReflow.cs` — pure reflow functions ported from `BufferReflow.ts`
- `src/XTerm.NET/Buffer/TerminalBuffer.cs` — `Resize` restructure, `ReflowLarger`/`ReflowSmaller`, `hasScrollback` flag
- `src/XTerm.NET/Buffer/BufferLine.cs` — `GetWidth`, `HasContent`, `ReplaceCells`; `GetTrimmedLength` wide-char width
- `src/XTerm.NET/Buffer/CircularList.cs` — `SetLength` for reflow batching
- `src/XTerm.NET/Terminal.cs` — alt buffer `hasScrollback: false`
- `src/XTerm.NET.Tests/Buffer/BufferReflowTests.cs` — pure-function tests
- `src/XTerm.NET.Tests/Buffer/BufferTests.cs` — reflow integration tests

## Validation

```powershell
dotnet test src/XTerm.NET.slnx
```

Result on this branch:

```text
Passed: 624
Failed: 0
Skipped: 0
```

# Docker progress rendering fixes

## Summary
Expand Down
112 changes: 112 additions & 0 deletions src/XTerm.NET.Tests/Buffer/BufferReflowTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using XTerm.Buffer;

namespace XTerm.Tests.Buffer;

public class BufferReflowTests
{
[Fact]
public void ReflowSmallerGetNewLineLengths_SmallLineWithWideCharacters()
{
var line = new BufferLine(4);
SetCell(line, 0, "汉", 2);
SetCell(line, 1, "", 0);
SetCell(line, 2, "语", 2);
SetCell(line, 3, "", 0);

Assert.Equal("汉语", line.TranslateToString(trimRight: true));
Assert.Equal(new[] { 2, 2 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 4, 3));
Assert.Equal(new[] { 2, 2 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 4, 2));
}

[Fact]
public void ReflowSmallerGetNewLineLengths_LargeLineWithWideCharacters()
{
var line = new BufferLine(12);
for (var i = 0; i < 12; i += 4)
{
SetCell(line, i, "汉", 2);
SetCell(line, i + 2, "语", 2);
}
for (var i = 1; i < 12; i += 2)
{
SetCell(line, i, "", 0);
}

Assert.Equal("汉语汉语汉语", line.TranslateToString());
Assert.Equal(new[] { 10, 2 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 12, 11));
Assert.Equal(new[] { 10, 2 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 12, 10));
Assert.Equal(new[] { 8, 4 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 12, 9));
Assert.Equal(new[] { 8, 4 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 12, 8));
Assert.Equal(new[] { 6, 6 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 12, 7));
Assert.Equal(new[] { 6, 6 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 12, 6));
Assert.Equal(new[] { 4, 4, 4 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 12, 5));
Assert.Equal(new[] { 4, 4, 4 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 12, 4));
Assert.Equal(new[] { 2, 2, 2, 2, 2, 2 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 12, 3));
Assert.Equal(new[] { 2, 2, 2, 2, 2, 2 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 12, 2));
}

[Fact]
public void ReflowSmallerGetNewLineLengths_MixedWideAndSingleCharacters()
{
var line = new BufferLine(6);
SetCell(line, 0, "a", 1);
SetCell(line, 1, "汉", 2);
SetCell(line, 2, "", 0);
SetCell(line, 3, "语", 2);
SetCell(line, 4, "", 0);
SetCell(line, 5, "b", 1);

Assert.Equal("a汉语b", line.TranslateToString());
Assert.Equal(new[] { 5, 1 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 6, 5));
Assert.Equal(new[] { 3, 3 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 6, 4));
Assert.Equal(new[] { 3, 3 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 6, 3));
Assert.Equal(new[] { 1, 2, 2, 1 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 6, 2));
}

[Fact]
public void ReflowSmallerGetNewLineLengths_WrappedLineWithWideAndSingleCharacters()
{
var line1 = new BufferLine(6);
SetCell(line1, 0, "a", 1);
SetCell(line1, 1, "汉", 2);
SetCell(line1, 2, "", 0);
SetCell(line1, 3, "语", 2);
SetCell(line1, 4, "", 0);
SetCell(line1, 5, "b", 1);

var line2 = new BufferLine(6) { IsWrapped = true };
SetCell(line2, 0, "a", 1);
SetCell(line2, 1, "汉", 2);
SetCell(line2, 2, "", 0);
SetCell(line2, 3, "语", 2);
SetCell(line2, 4, "", 0);
SetCell(line2, 5, "b", 1);

Assert.Equal(new[] { 5, 4, 3 }, BufferReflow.ReflowSmallerGetNewLineLengths([line1, line2], 6, 5));
Assert.Equal(new[] { 3, 4, 4, 1 }, BufferReflow.ReflowSmallerGetNewLineLengths([line1, line2], 6, 4));
Assert.Equal(new[] { 3, 3, 3, 3 }, BufferReflow.ReflowSmallerGetNewLineLengths([line1, line2], 6, 3));
Assert.Equal(new[] { 1, 2, 2, 2, 2, 2, 1 }, BufferReflow.ReflowSmallerGetNewLineLengths([line1, line2], 6, 2));
}

[Fact]
public void ReflowSmallerGetNewLineLengths_LinesEndingInNullSpace()
{
var line = new BufferLine(5);
SetCell(line, 0, "汉", 2);
SetCell(line, 1, "", 0);
SetCell(line, 2, "语", 2);
SetCell(line, 3, "", 0);
var empty = BufferCell.Empty;
line.SetCell(4, ref empty);

Assert.Equal("汉语", line.TranslateToString(trimRight: true));
Assert.Equal(new[] { 2, 2 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 4, 3));
Assert.Equal(new[] { 2, 2 }, BufferReflow.ReflowSmallerGetNewLineLengths([line], 4, 2));
}

private static void SetCell(BufferLine line, int col, string content, int width = 1)
{
var cell = content == "" ? BufferCell.Empty : new BufferCell(content, width, AttributeData.Default);
line.SetCell(col, ref cell);
}
}
Loading