Skip to content

Commit feff7e6

Browse files
committed
Gate DEC private modes by capabilities
1 parent d786de6 commit feff7e6

File tree

5 files changed

+52
-23
lines changed

5 files changed

+52
-23
lines changed

doc/readme.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,15 @@ var caps = AnsiCapabilities.Default with
6363
AnsiEnabled = true,
6464
ColorLevel = AnsiColorLevel.Colors256,
6565
SupportsOsc8 = false,
66+
SupportsPrivateModes = false,
6667
};
6768

6869
using var builder = new AnsiBuilder();
6970
var w = new AnsiWriter(builder, caps);
7071
```
7172

7273
If `AnsiEnabled` is `false`, style/cursor/hyperlink methods become no-ops (text still writes).
74+
If `SupportsPrivateModes` is `false`, DEC private-mode helpers (for example `CursorVisible(...)`, `AlternateScreen(...)`, `PrivateMode(...)`) become no-ops.
7375

7476
### Colors and decorations
7577

@@ -363,6 +365,8 @@ Decoration parameters used by `Decorate(...)` / `Undecorate(...)`:
363365

364366
#### DEC private modes (CSI with `?`)
365367

368+
These helpers emit sequences only when `AnsiCapabilities.SupportsPrivateModes` is `true`.
369+
366370
| API | Emits | Notes |
367371
|---|---|---|
368372
| `CursorVisible(true)` / `ShowCursor(true)` | `ESC[?25h` | Show cursor |

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ XenoAtom.Ansi is a fast, allocation-friendly .NET library for building rich ANSI
1212
- `AnsiWriter` fluent API (writes to `TextWriter` or `IBufferWriter<char>`)
1313
- `AnsiMarkup` for markup strings, including interpolated strings (formatted values are escaped)
1414
- SGR styling: colors (basic-16, 256-color, truecolor RGB), decorations (bold/dim/italic/underline/etc), reset
15-
- Capability-aware output (`AnsiCapabilities`) including color downgrading and optional safe-mode behavior
15+
- Capability-aware output (`AnsiCapabilities`) including color downgrading, private-mode gating, and optional safe-mode behavior
1616
- Cursor/screen helpers (ANSI/DEC/xterm/Windows Terminal): move/position, save/restore, erase (incl. scrollback), insert/delete chars/lines, scrolling + scroll regions, cursor style, mode toggles, tabs, alternate screen, soft reset
1717
- OSC helpers with configurable terminator (BEL or ST): window title (OSC 0/2), palette edits (OSC 4), hyperlinks (OSC 8)
1818
- **Parsing**

src/XenoAtom.Ansi.Tests/AnsiWriterTerminalControlTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,39 @@ public void ModeControls_ParseAsCsiWithPrivateMarkerWhenDec()
119119
Assert.AreEqual('?', decrst.PrivateMarker);
120120
CollectionAssert.AreEqual(new[] { 2004 }, decrst.Parameters);
121121
}
122+
123+
[TestMethod]
124+
public void SupportsPrivateModes_DefaultsToTrue()
125+
{
126+
Assert.IsTrue(AnsiCapabilities.Default.SupportsPrivateModes);
127+
Assert.IsTrue(new AnsiCapabilities().SupportsPrivateModes);
128+
}
129+
130+
[TestMethod]
131+
public void PrivateModeControls_AreNoOp_WhenPrivateModesAreNotSupported()
132+
{
133+
var caps = AnsiCapabilities.Default with { SupportsPrivateModes = false };
134+
135+
var output = AnsiRoundTrip.Emit(w =>
136+
{
137+
w.PrivateMode(2004, enabled: true);
138+
w.PrivateMode(2004, enabled: false);
139+
w.CursorKeysApplicationMode(enabled: true);
140+
w.CursorKeysApplicationMode(enabled: false);
141+
w.CursorBlinking(enabled: true);
142+
w.CursorBlinking(enabled: false);
143+
w.Columns132(enabled: true);
144+
w.Columns132(enabled: false);
145+
w.ShowCursor(visible: false);
146+
w.ShowCursor(visible: true);
147+
w.CursorVisible(visible: false);
148+
w.CursorVisible(visible: true);
149+
w.EnterAlternateScreen();
150+
w.LeaveAlternateScreen();
151+
w.AlternateScreen(enabled: true);
152+
w.AlternateScreen(enabled: false);
153+
}, caps);
154+
155+
Assert.AreEqual(string.Empty, output);
156+
}
122157
}

src/XenoAtom.Ansi/AnsiCapabilities.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public record AnsiCapabilities
2222
AnsiEnabled = true,
2323
ColorLevel = AnsiColorLevel.TrueColor,
2424
SupportsOsc8 = true,
25+
SupportsPrivateModes = true,
2526
Prefer7BitC1 = true,
2627
SafeMode = false,
2728
OscTermination = AnsiOscTermination.StringTerminator,
@@ -42,6 +43,11 @@ public record AnsiCapabilities
4243
/// </summary>
4344
public bool SupportsOsc8 { get; init; }
4445

46+
/// <summary>
47+
/// Gets a value indicating whether DEC private mode sequences (<c>CSI ? ...</c>) should be emitted.
48+
/// </summary>
49+
public bool SupportsPrivateModes { get; init; } = true;
50+
4551
/// <summary>
4652
/// Gets a value indicating whether the output should prefer 7-bit escape sequences (e.g. <c>ESC [</c>)
4753
/// instead of 8-bit C1 control codes (e.g. CSI as a single byte).

src/XenoAtom.Ansi/AnsiWriter.cs

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,7 @@ public AnsiWriter SetMode(int mode, bool enabled)
877877
/// <returns>This writer, for fluent chaining.</returns>
878878
public AnsiWriter PrivateMode(int mode, bool enabled)
879879
{
880-
if (!Capabilities.AnsiEnabled)
880+
if (!CanEmitPrivateModes())
881881
{
882882
return this;
883883
}
@@ -931,13 +931,7 @@ public AnsiWriter CursorStyle(AnsiCursorStyle style)
931931
/// <returns>This writer, for fluent chaining.</returns>
932932
public AnsiWriter ShowCursor(bool visible)
933933
{
934-
if (!Capabilities.AnsiEnabled)
935-
{
936-
return this;
937-
}
938-
939-
Write(visible ? "\x1b[?25h" : "\x1b[?25l");
940-
return this;
934+
return PrivateMode(25, visible);
941935
}
942936

943937
/// <summary>
@@ -953,13 +947,7 @@ public AnsiWriter ShowCursor(bool visible)
953947
/// <returns>This writer, for fluent chaining.</returns>
954948
public AnsiWriter EnterAlternateScreen()
955949
{
956-
if (!Capabilities.AnsiEnabled)
957-
{
958-
return this;
959-
}
960-
961-
Write("\x1b[?1049h");
962-
return this;
950+
return PrivateMode(1049, enabled: true);
963951
}
964952

965953
/// <summary>
@@ -968,13 +956,7 @@ public AnsiWriter EnterAlternateScreen()
968956
/// <returns>This writer, for fluent chaining.</returns>
969957
public AnsiWriter LeaveAlternateScreen()
970958
{
971-
if (!Capabilities.AnsiEnabled)
972-
{
973-
return this;
974-
}
975-
976-
Write("\x1b[?1049l");
977-
return this;
959+
return PrivateMode(1049, enabled: false);
978960
}
979961

980962
/// <summary>
@@ -1184,6 +1166,8 @@ private void WriteCsiWithInt(int n, char final, bool allowZeroToOmit = false)
11841166
WriteChar(final);
11851167
}
11861168

1169+
private bool CanEmitPrivateModes() => Capabilities.AnsiEnabled && Capabilities.SupportsPrivateModes;
1170+
11871171
private void WriteSgr(string parameters)
11881172
{
11891173
if (!Capabilities.AnsiEnabled)

0 commit comments

Comments
 (0)