Skip to content
Merged
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
27 changes: 27 additions & 0 deletions src/XTerm.NET.Tests/DeviceReportTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,31 @@ public void Decrqtsr_WithNoParameterAsksForNothing()
terminal.Write(Esc + "[$u");
Assert.Empty(replies);
}

[Fact]
public void Decrqcra_IsSilentBelowLevel64()
{
var (terminal, replies) = Create();
terminal.Write(Esc + "[64\"p");
terminal.Write(Esc + "[1;0;1;1;1;1*y");
Assert.Single(replies); // answered at VT400

replies.Clear();
terminal.Write(Esc + "[62\"p"); // DECSCL: VT200
terminal.Write(Esc + "[1;0;1;1;1;1*y");
Assert.Empty(replies);
}

[Fact]
public void Decrqtsr_IsSilentBelowLevel64()
{
// The control is VT320 vintage, but the capability the primary DA offers for it --
// attribute 17, terminal state interrogation -- is advertised only from level 64.
// Declining a request the DA reply has already said the terminal does not take is the
// terminal contradicting itself.
var (terminal, replies) = Create();
terminal.Write(Esc + "[62\"p");
terminal.Write(Esc + "[1$u");
Assert.Empty(replies);
}
}
61 changes: 61 additions & 0 deletions src/XTerm.NET.Tests/Graphics/ImageCellLifetimeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -386,4 +386,65 @@ public void The_alternate_buffer_keeps_its_own_images()
terminal.Write($"{Esc}[?1049l"); // and back
Assert.Equal(8, ImageCellCount(terminal));
}

/// <summary>
/// The counterpart to every test above: a control that changes RENDITION and not text must
/// leave the picture alone.
/// </summary>
/// <remarks>
/// DECCARA sets attributes over an area and never touches a character, so a picture inside that
/// area is not being overwritten and must not be split. Going through the ordinary text-write
/// path made it look like one -- that path splits the placement at the column being written,
/// which is right for printing over a picture and wrong for recolouring the cell under it.
/// </remarks>
[Fact]
public void An_attribute_change_over_a_picture_leaves_it_whole()
{
var terminal = Fresh();
WriteSixel(terminal);
var image = ImageAssertions.ImageAt(terminal, 0, 0);
Assert.NotNull(image);
Assert.Equal(8, ImageCellCount(terminal));

// Bold over the whole picture and then some.
terminal.Write($"{Esc}[2*x{Esc}[1;1;4;4;1$r");

Assert.Equal(8, ImageCellCount(terminal));
Assert.True(ReferenceEquals(ImageAssertions.ImageAt(terminal, 0, 0), image));
Assert.True(ReferenceEquals(ImageAssertions.ImageAt(terminal, 1, 3), image));
Assert.True(terminal.Buffer.Lines[terminal.Buffer.YBase]![0].Attributes.IsBold());
}

/// <summary>
/// And a request that changes nothing must not even write the cells back, because writing a
/// cell back unchanged is still a write as far as the placements are concerned.
/// </summary>
[Fact]
public void An_attribute_change_naming_nothing_we_implement_touches_no_cell()
{
var terminal = Fresh();
WriteSixel(terminal);
var image = ImageAssertions.ImageAt(terminal, 0, 0);
Assert.Equal(8, ImageCellCount(terminal));

// 31 is a colour, which DECCARA does not carry; the request names nothing this implements.
terminal.Write($"{Esc}[2*x{Esc}[1;1;4;4;31$r");

Assert.Equal(8, ImageCellCount(terminal));
Assert.True(ReferenceEquals(ImageAssertions.ImageAt(terminal, 0, 0), image));
}

/// <summary>A DECRARA whose toggles cancel each other is the same nothing.</summary>
[Fact]
public void Toggles_that_cancel_leave_the_picture_and_the_rendition_alone()
{
var terminal = Fresh();
WriteSixel(terminal);
Assert.Equal(8, ImageCellCount(terminal));

terminal.Write($"{Esc}[2*x{Esc}[1;1;4;4;1;1$t");

Assert.Equal(8, ImageCellCount(terminal));
Assert.False(terminal.Buffer.Lines[terminal.Buffer.YBase]![0].Attributes.IsBold());
}
}
56 changes: 56 additions & 0 deletions src/XTerm.NET.Tests/RectangleOpsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,4 +301,60 @@ public void Decsace_survives_a_soft_reset_and_not_a_hard_one()
terminal.Write($"{Esc}[2;3;3;6;1$r");
Assert.True(AttrAt(terminal, 1, 9).IsBold()); // back to a stream
}

/// <summary>
/// The whole family is VT400, which is the gate xterm puts on each of them and what this
/// terminal's own primary DA already says by advertising attribute 28 only from level 64.
/// A program that lowered the level with DECSCL asked to be treated as older hardware.
/// </summary>
[Theory]
[InlineData("[42;1;1;2;4$x")] // DECFRA
[InlineData("[1;1;2;4$z")] // DECERA
[InlineData("[1;1;2;4${")] // DECSERA
[InlineData("[1;1;1;4;2;1$v")] // DECCRA
[InlineData("[1;1;2;4;1$r")] // DECCARA
[InlineData("[1;1;2;4;1$t")] // DECRARA
public void The_rectangle_family_is_refused_below_level_64(string sequence)
{
var terminal = NewTerminal();
terminal.Write("abcdefghij");
var before = Row(terminal, 0, 10);
var boldBefore = AttrAt(terminal, 0, 0).IsBold();

terminal.Write($"{Esc}[62\"p"); // DECSCL: VT200
terminal.Write($"{Esc}{sequence}");

Assert.Equal(before, Row(terminal, 0, 10));
Assert.Equal(boldBefore, AttrAt(terminal, 0, 0).IsBold());
}

[Fact]
public void The_rectangle_family_works_again_at_level_64()
{
var terminal = NewTerminal();
terminal.Write($"{Esc}[62\"p{Esc}[64\"p"); // down to VT200 and back up to VT400
terminal.Write($"{Esc}[42;1;1;1;3$x");

Assert.Equal("*** ", Row(terminal, 0, 10));
}

/// <summary>
/// DECSACE is the family's one exception, and it is xterm's asymmetry rather than an oversight:
/// its handler has no level test where every neighbour has one. Storing which extent a program
/// would prefer changes nothing by itself -- the two controls that read it are gated -- so
/// there is nothing to refuse.
/// </summary>
[Fact]
public void Decsace_is_stored_at_every_level()
{
var terminal = NewTerminal();
var replies = new List<string>();
terminal.DataReceived += (_, e) => replies.Add(e.Data);

terminal.Write($"{Esc}[62\"p"); // VT200
terminal.Write($"{Esc}[2*x"); // DECSACE 2
terminal.Write($"{Esc}P$q*x{Esc}\\"); // DECRQSS

Assert.Equal($"{Esc}P1$r2*x{Esc}\\", Assert.Single(replies));
}
}
15 changes: 15 additions & 0 deletions src/XTerm.NET/InputHandler.Csi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,15 @@ private void RequestUserPreferredSupplementalSet()
/// </remarks>
private void RequestTerminalStateReport(Params parameters)
{
// The refusal is still a report, and this terminal's primary DA offers attribute 17,
// terminal state interrogation, only from level 64. Answering below that would have the
// terminal declining a request it had already said it does not take -- so a program that
// lowered the level with DECSCL gets the same silence every other unavailable control
// gives it. The control itself is VT320 vintage; the capability it belongs to is what the
// DA reply gates, and matching the reply is what keeps the two from disagreeing.
if (_terminal.ConformanceLevel < 64)
return;

if (parameters.GetParam(0, 0) == 0)
return;

Expand Down Expand Up @@ -1744,6 +1753,12 @@ private void RestoreCursor()
/// </remarks>
private void RequestChecksumRectangularArea(Params parameters)
{
// VT400 and up, with the rest of the rectangle family. esctest asserts the level before it
// reads a single cell back through this -- AssertVTLevel(4, "checksum") -- so the gate is
// one the conformance suite already assumes is here.
if (!RectangularEditingAvailable)
return;

var id = parameters.GetParam(0, 0);
// parameters[1] is the page, ignored. Coordinates are read in the ORIGIN MODE system,
// like a cursor address and like every rectangle operation: a program that addresses its
Expand Down
Loading
Loading