diff --git a/examples/ted/TedApp.cs b/examples/ted/TedApp.cs index d48d40a3..3d77a4d4 100644 --- a/examples/ted/TedApp.cs +++ b/examples/ted/TedApp.cs @@ -74,6 +74,14 @@ public TedApp (bool readOnly = false) Editor.ConvertTabsToSpaces = e.NewValue == CheckState.Checked; }; + CheckBox useThemeBackgroundCheckBox = new () + { + AllowCheckStateNone = false, + CanFocus = false, + Text = "Use _Theme Background", + Value = Editor.UseThemeBackground ? CheckState.Checked : CheckState.UnChecked + }; + ThemeDropDown = new DropDownList { Value = ThemeName.DarkPlus, @@ -191,6 +199,15 @@ public TedApp (bool readOnly = false) { CommandView = convertTabsToSpacesCheckBox, HelpText = "Insert spaces when Tab is pressed" + }, + new MenuItem + { + Action = () => + { + Editor.UseThemeBackground = useThemeBackgroundCheckBox.Value == CheckState.Checked; + }, + CommandView = useThemeBackgroundCheckBox, + HelpText = "Use theme background for highlighted text" } ]), new MenuBarItem (Strings.menuHelp, diff --git a/src/Terminal.Gui.Editor/Editor.Drawing.cs b/src/Terminal.Gui.Editor/Editor.Drawing.cs index d27c4ddb..174c9d62 100644 --- a/src/Terminal.Gui.Editor/Editor.Drawing.cs +++ b/src/Terminal.Gui.Editor/Editor.Drawing.cs @@ -22,6 +22,7 @@ protected override bool OnDrawingContent (DrawContext? context) Attribute normal = GetAttributeForRole (VisualRole.Normal); Attribute selected = GetAttributeForRole (VisualRole.Active); + FillViewportBackground (viewport, normal); DrawVisibleLines (viewport, normal, selected); SetAttribute (normal); UpdateCursor (); @@ -29,6 +30,38 @@ protected override bool OnDrawingContent (DrawContext? context) return true; } + /// + /// When is and a syntax highlighter + /// provides a , fills the viewport with + /// that background so empty cells match per-token backgrounds. + /// + private void FillViewportBackground (Rectangle viewport, Attribute normal) + { + if (!UseThemeBackground) + { + return; + } + +#pragma warning disable CS0618 // Type or member is obsolete + Terminal.Gui.Drawing.Color? themeBg = SyntaxHighlighter?.DefaultBackground; +#pragma warning restore CS0618 // Type or member is obsolete + + if (themeBg is not { } bg) + { + return; + } + + Attribute fillAttr = new (normal.Foreground, bg); + SetAttribute (fillAttr); + + var spaces = new string (' ', viewport.Width); + + for (var row = 0; row < viewport.Height; row++) + { + AddStr (0, row, spaces); + } + } + private void DrawVisibleLines (Rectangle viewport, Attribute normal, Attribute selected) { // The CS0618 here is the API's purpose: SyntaxHighlighter is [Obsolete] to warn diff --git a/src/Terminal.Gui.Editor/Editor.cs b/src/Terminal.Gui.Editor/Editor.cs index 1a446c2e..624a2001 100644 --- a/src/Terminal.Gui.Editor/Editor.cs +++ b/src/Terminal.Gui.Editor/Editor.cs @@ -231,6 +231,29 @@ public int IndentationSize /// Whether pressing Tab inserts spaces instead of a tab character. public bool ConvertTabsToSpaces { get; set; } + /// + /// When (the default), syntax-highlighted tokens keep both their + /// foreground and background from the syntax highlighting theme (e.g. Dark Plus's + /// #1E1E1E background). Set to to override the + /// highlighter's background with the TG scheme's + /// background, matching the active application theme. + /// + public bool UseThemeBackground + { + get; + set + { + if (field == value) + { + return; + } + + field = value; + ClearVisualLineCaches (); + SetNeedsDraw (); + } + } = true; + /// Whether tab characters render with a visible glyph in their first cell. public bool ShowTabs { @@ -731,7 +754,8 @@ private CellVisualLine BuildVisualLine ( styledSegments, selectionStart, selectionEnd, - LineTransformers); + LineTransformers, + UseThemeBackground); return _visualLineBuilder.Build (line, context); } diff --git a/src/Terminal.Gui.Editor/Rendering/VisualLineBuildContext.cs b/src/Terminal.Gui.Editor/Rendering/VisualLineBuildContext.cs index 31e29ae7..01cfac2f 100644 --- a/src/Terminal.Gui.Editor/Rendering/VisualLineBuildContext.cs +++ b/src/Terminal.Gui.Editor/Rendering/VisualLineBuildContext.cs @@ -14,7 +14,8 @@ public sealed class VisualLineBuildContext ( IReadOnlyList? styledSegments, int selectionStart, int selectionEnd, - IEnumerable lineTransformers) + IEnumerable lineTransformers, + bool useThemeBackground = true) { public TextDocument Document { get; } = document; @@ -34,5 +35,12 @@ public sealed class VisualLineBuildContext ( public IEnumerable LineTransformers { get; } = lineTransformers; + /// + /// When , styled-segment backgrounds are preserved from the + /// syntax highlighting theme. When , backgrounds are replaced + /// with 's background so text blends into the TG scheme. + /// + public bool UseThemeBackground { get; } = useThemeBackground; + public bool HasSelection => SelectionStart < SelectionEnd; } diff --git a/src/Terminal.Gui.Editor/Rendering/VisualLineBuilder.cs b/src/Terminal.Gui.Editor/Rendering/VisualLineBuilder.cs index df1eae41..0feed13d 100644 --- a/src/Terminal.Gui.Editor/Rendering/VisualLineBuilder.cs +++ b/src/Terminal.Gui.Editor/Rendering/VisualLineBuilder.cs @@ -157,8 +157,15 @@ private static Attribute GetAttribute (VisualLineBuildContext context, int segme return context.NormalAttribute; } - return context.StyledSegments[Math.Min (segmentIndex, context.StyledSegments.Count - 1)].Attribute - ?? context.NormalAttribute; + Attribute segmentAttribute = context.StyledSegments[Math.Min (segmentIndex, context.StyledSegments.Count - 1)].Attribute + ?? context.NormalAttribute; + + if (!context.UseThemeBackground) + { + return new Attribute (segmentAttribute.Foreground, context.NormalAttribute.Background); + } + + return segmentAttribute; } private static int GetSegmentEnd (IReadOnlyList? segments, int segmentIndex) diff --git a/tests/Terminal.Gui.Editor.IntegrationTests/EditorRenderingTests.cs b/tests/Terminal.Gui.Editor.IntegrationTests/EditorRenderingTests.cs index 4d0a09d4..052876b8 100644 --- a/tests/Terminal.Gui.Editor.IntegrationTests/EditorRenderingTests.cs +++ b/tests/Terminal.Gui.Editor.IntegrationTests/EditorRenderingTests.cs @@ -328,4 +328,57 @@ public async Task Cursor_Position_After_Tab_Uses_Expanded_Tab_Columns () Assert.Equal (new Point (4, 0), fx.Top.Editor.Cursor.Position); } + + [Fact] + public async Task UseThemeBackground_True_Preserves_Highlighter_Background () + { + const string text = "public class C"; + + await using AppFixture fx = new (() => new EditorTestHost (text)); +#pragma warning disable CS0618 // Type or member is obsolete + fx.Top.Editor.SyntaxHighlighter = new TextMateSyntaxHighlighter (); +#pragma warning restore CS0618 // Type or member is obsolete + fx.Top.Editor.UseThemeBackground = true; + fx.Render (); + + TextMateSyntaxHighlighter highlighter = new (); + Attribute expected = highlighter.Highlight (text, "csharp")[0].Attribute!.Value; + + Cell cell = fx.Driver.Contents![0, 0]; + Assert.Equal ("p", cell.Grapheme); + Assert.Equal (expected, cell.Attribute); + } + + [Fact] + public async Task UseThemeBackground_False_Overrides_Highlighter_Background () + { + const string text = "public class C"; + + await using AppFixture fx = new (() => new EditorTestHost (text)); +#pragma warning disable CS0618 // Type or member is obsolete + fx.Top.Editor.SyntaxHighlighter = new TextMateSyntaxHighlighter (); +#pragma warning restore CS0618 // Type or member is obsolete + fx.Top.Editor.UseThemeBackground = false; + fx.Render (); + + Attribute normal = fx.Top.Editor.GetAttributeForRole (VisualRole.Normal); + Cell cell = fx.Driver.Contents![0, 0]; + Assert.Equal ("p", cell.Grapheme); + + // The foreground should come from the highlighter (different from Normal's foreground). + TextMateSyntaxHighlighter highlighter = new (); + Attribute highlighterAttr = highlighter.Highlight (text, "csharp")[0].Attribute!.Value; + Assert.Equal (highlighterAttr.Foreground, cell.Attribute!.Value.Foreground); + + // The background must match the TG theme's Normal background, not the highlighter's. + Assert.Equal (normal.Background, cell.Attribute!.Value.Background); + } + + [Fact] + public async Task UseThemeBackground_Defaults_To_True () + { + await using AppFixture fx = new (() => new EditorTestHost ("Hello")); + + Assert.True (fx.Top.Editor.UseThemeBackground); + } }