Skip to content

feat(terminal): colors themes - #85

Merged
keyldev merged 2 commits into
mainfrom
feat/terminal-colors-themes
Oct 5, 2026
Merged

keyldev merged 2 commits into
mainfrom
feat/terminal-colors-themes

Conversation

@keyldev

@keyldev keyldev commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Related

Type

  • Bug fix
  • Feature
  • Refactor / cleanup
  • Docs / CI
  • Other:

Checklist

  • Builds with 0 warnings (TreatWarningsAsErrors=true).
  • Tests pass (dotnet test); new Core logic has unit tests.
  • No layering violation — App references Core only (Infrastructure / Video / Devices wired via DI in a head).
  • Scope stays within one phase (didn't pull work from a later phase's "Не входит").
  • README / docs updated if public commands, options, or setup changed.

Platforms tested

  • Windows
  • Linux
  • macOS
  • Android
  • iOS
  • CI build only

Screenshots / notes

- Emulator keeps 256-color and truecolor SGR (both ; and : spellings), plus dim, italic, underline, inverse, hidden, strikethrough and cursor visibility
- Six built-in terminal themes resolved at draw time; pixel-snapped background runs
- Settings: theme, cursor style, blinking cursor, history length, with a live preview
- Alternate screen (?47/1047/1049), scroll regions, origin mode, IL/DL/ICH/DCH/ECH, SU/SD, REP, autowrap toggle
- Application cursor keys, DEC line-drawing charset, DSR/DA replies; ESC ( B no longer prints a stray B
- Box-drawing and block characters drawn per cell so frames join; one key table adds Delete, PgUp/PgDn, F1-F12
- Wheel and swipe on the alternate screen send arrow keys
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add terminal themes and full-screen SSH support

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add six terminal themes, configurable cursors and scrollback, and a live settings preview.
• Preserve 256-color and truecolor output and support full-screen SSH applications.
• Expand keyboard and touch input for application cursor modes and add emulator tests.
Diagram

graph TD
  Shell["SSH Shell"] --> Emulator["Terminal Emulator"] --> Cells["Styled Cells"] --> Theme["Theme Resolution"] --> View["Terminal View"]
  Settings["User Settings"] --> Preview["Settings Preview"] --> View
  Settings --> Theme
  View --> Shell
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Adopt an established VT emulator
  • ➕ Broader escape-sequence coverage
  • ➕ Less custom parser behavior to maintain
  • ➖ Adds a dependency
  • ➖ Requires integration with the existing Avalonia renderer, selection, and mobile input

Recommendation: Extend the existing emulator for this scoped set of full-screen features: it preserves the current rendering and mobile interactions without replacing the terminal stack. Consider an established emulator if broader VT compatibility becomes a requirement.

Files changed (13) +1914 / -212

Enhancement (9) +1496 / -210
TerminalView.csRender themed terminal cells and expand input handling +501/-59

Render themed terminal cells and expand input handling

• Adds theme and cursor properties, blinking, and sizing for the settings preview. Renders extended colors, attributes, and joined box-drawing characters, while handling alternate-screen scrolling and additional keys.

src/OpenIPC.Viewer.App/Controls/TerminalView.cs

Localizer.csLocalize terminal appearance settings +28/-0

Localize terminal appearance settings

• Adds English and Russian labels for themes, cursor options, blinking, and history length.

src/OpenIPC.Viewer.App/Services/Localizer.cs

SettingsPageViewModel.csProvide terminal options and a live preview +69/-1

Provide terminal options and a live preview

• Loads and persists the new terminal preferences, exposes localized options, and creates a sample emulator for previewing colors and text attributes.

src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs

SshTerminalViewModel.csApply terminal preferences to SSH sessions +16/-1

Apply terminal preferences to SSH sessions

• Constructs the emulator with the configured scrollback limit, exposes appearance settings to the view, and sends emulator status replies through the shell.

src/OpenIPC.Viewer.App/ViewModels/SshTerminalViewModel.cs

SshTerminalContent.axaml.csWire themed rendering and mode-aware keys +23/-22

Wire themed rendering and mode-aware keys

• Applies the selected theme and cursor settings to the terminal. Mobile key-bar arrows follow the emulator's cursor mode, and proxy keyboard input shares the terminal's expanded key table.

src/OpenIPC.Viewer.App/Views/Dialogs/SshTerminalContent.axaml.cs

SettingsPage.axamlAdd terminal controls and a sample preview +48/-0

Add terminal controls and a sample preview

• Adds theme, cursor, blink, and scrollback controls alongside a non-interactive preview drawn by TerminalView.

src/OpenIPC.Viewer.App/Views/Pages/SettingsPage.axaml

TerminalCell.csRepresent indexed and RGB colors with cell attributes +79/-7

Represent indexed and RGB colors with cell attributes

• Replaces byte color fields with a color type that retains theme defaults, palette indices, and RGB values. Adds rendition flags and xterm 256-color palette resolution.

src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalCell.cs

TerminalEmulator.csSupport richer SGR and full-screen terminal modes +596/-120

Support richer SGR and full-screen terminal modes

• Extends escape parsing for alternate screens, scroll regions, editing commands, character sets, cursor modes, and status replies. Retains extended colors and attributes in cells and makes scrollback configurable.

src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs

TerminalTheme.csDefine six terminal palettes and cursor styles +136/-0

Define six terminal palettes and cursor styles

• Adds built-in themes that resolve cell colors at draw time, including inverse, dim, and hidden attributes. Defines the available cursor styles and falls back to the default theme for unknown IDs.

src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalTheme.cs

Tests (3) +412 / -2
TerminalEmulatorTests.csAdapt color assertions to the new cell model +2/-2

Adapt color assertions to the new cell model

• Updates existing SGR assertions to check indexed and default TerminalColor values.

tests/OpenIPC.Viewer.Core.Tests/Ssh/TerminalEmulatorTests.cs

TerminalFullScreenTests.csTest full-screen terminal behavior +232/-0

Test full-screen terminal behavior

• Adds coverage for alternate screens, resizing, scroll regions, editing commands, character sets, cursor-key modes, status replies, and resets.

tests/OpenIPC.Viewer.Core.Tests/Ssh/TerminalFullScreenTests.cs

TerminalRenditionTests.csTest colors, attributes, themes, and scrollback +178/-0

Test colors, attributes, themes, and scrollback

• Covers both extended-color syntaxes, rendition flags, palette and theme resolution, cursor visibility, and configurable history limits.

tests/OpenIPC.Viewer.Core.Tests/Ssh/TerminalRenditionTests.cs

Other (1) +6 / -0
UserSettings.csPersist terminal appearance and history preferences +6/-0

Persist terminal appearance and history preferences

• Adds defaulted settings for the terminal theme, cursor style, cursor blinking, and scrollback limit.

src/OpenIPC.Viewer.App/Services/UserSettings.cs

@keyldev
keyldev merged commit 51cb065 into main Oct 5, 2026
3 of 5 checks passed
@keyldev
keyldev deleted the feat/terminal-colors-themes branch October 5, 2026 20:00
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. One-column terminals crash on backspace 🐞 Bug ≡ Correctness
Description
ProcessGround subtracts one after clamping a pending-wrap column to Columns - 1, producing
column -1 in a one-column grid. After a character fills that grid, a backspace followed by
printable output indexes the row at -1; wider grids also move back one cell too far at the right
edge.
Code

src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs[301]

+            case '\b': if (_cursorCol > 0) _cursorCol = Math.Min(_cursorCol, Columns - 1) - 1; break;
Evidence
PutChar increments the column after writing, leaving it equal to Columns when the last cell is
filled. The changed backspace expression then yields -1 when Columns is one, and the next
printable character writes through that index.

src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs[296-307]
src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs[636-657]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Backspace after a pending wrap computes an incorrect column, including `-1` in a one-column terminal.
## Fix Focus Areas
- src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs[296-306]
- src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs[636-657]
## Recommended Fix
Subtract from the pending column before clamping to the valid cell range. Add tests for backspace after filling both one-column and wider rows.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Cursor movement stops at wrong margins 🐞 Bug ≡ Correctness
Description
CursorUp and CursorDown choose a scroll margin based on only one side of the cursor position, so
each also constrains movement that starts outside the region. With a region spanning rows 3–5,
moving down from row 1 stops at row 5 instead of reaching the screen bottom, and moving up from
below the region has the symmetric failure.
Code

src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs[R811-812]

+        var limit = _cursorRow <= _scrollBottom ? _scrollBottom : Rows - 1;
+        _cursorRow = Math.Min(limit, _cursorRow + n);
Evidence
MoveTo permits positions anywhere on the screen when origin mode is off, and CSI A/B call these
movement methods. The new limit conditions treat a position above the region as inside it for
downward movement and a position below it as inside it for upward movement.

src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs[439-451]
src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs[782-812]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Cursor-up and cursor-down incorrectly stop at scroll margins when movement starts outside the scroll region.
## Fix Focus Areas
- src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs[802-812]
## Recommended Fix
Apply margin limits only when the starting row lies between both margins; otherwise use screen limits. Test movements starting above, inside, and below a partial scroll region.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Theme labels stay in the old language 🐞 Bug ≡ Correctness
Description
TerminalThemeOptions and TerminalCursorStyleOptions store localized display strings when the
settings view model is constructed and never rebuild them. Changing the application language while
that page remains open updates localizer-backed labels, but both new terminal pickers keep their
previous-language entries until the page is recreated.
Code

src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs[R266-268]

+        TerminalThemeOptions = TerminalTheme.BuiltIn
+            .Select(t => new TerminalThemeOption(Localizer.Instance[$"Settings.Ssh.Theme.{t.Id}"], t))
+            .ToList();
Evidence
The new options materialize Localizer.Instance results into record Display strings once.
Changing the language raises a localizer property change, but the pickers bind to those stored
strings rather than live localizer values.

src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs[266-275]
src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs[345-346]
src/OpenIPC.Viewer.App/Services/Localizer.cs[34-50]
src/OpenIPC.Viewer.Desktop/Composition.cs[82-94]
src/OpenIPC.Viewer.App/Views/Pages/SettingsPage.axaml[409-427]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new terminal option lists retain their constructor-time language after an in-page language change.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs[266-275]
- src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs[345-346]
## Recommended Fix
Refresh the terminal theme and cursor option displays when the localizer changes, preserving the selected theme and style, or bind their labels directly to live localization.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Block cursor hides the character on highlighted cells 🐞 Bug ≡ Correctness
Description
In DrawCursor’s Block case, the character is redrawn in theme.ColorsOf(cell).Background over
the theme.Cursor fill, and the redraw does not check TerminalAttributes.Hidden. On inverse cells
in most built-in themes, that background matches the cursor colour and the character disappears; on
SGR 8 cells, text suppressed by DrawRow becomes visible while the cursor rests there.
Code

src/OpenIPC.Viewer.App/Controls/TerminalView.cs[R1177-1181]

+                if (cell.Char > ' ')
+                {
+                    var (_, bg) = theme.ColorsOf(cell);
+                    DrawCellChar(context, cell.Char, bg, cell.Attributes, x, y);
+                }
Evidence
ColorsOf swaps foreground and background for inverse cells, making the resolved background equal
to theme.Foreground; in most built-in themes, that colour matches theme.Cursor, so the redrawn
glyph has the same colour as the block fill. DrawRow checks TerminalAttributes.Hidden before
drawing a glyph, but the block-cursor branch checks only whether the character is above a space, and
DrawCellChar does not filter hidden characters.

src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalTheme.cs[49-61]
src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalTheme.cs[76-77]
src/OpenIPC.Viewer.App/Controls/TerminalView.cs[1169-1183]
src/OpenIPC.Viewer.App/Controls/TerminalView.cs[943-944]
src/OpenIPC.Viewer.App/Controls/TerminalView.cs[937-947]
src/OpenIPC.Viewer.App/Controls/TerminalView.cs[967-970]
src/OpenIPC.Viewer.App/Controls/TerminalView.cs[1169-1181]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The block cursor redraws the cell character in its resolved background colour. On inverse cells, that colour can match the cursor fill, making the character disappear; the redraw also exposes characters hidden with SGR 8.

## Fix Focus Areas
- src/OpenIPC.Viewer.App/Controls/TerminalView.cs[1169-1183]

## Recommended Fix
Redraw visible characters in a colour that contrasts with `theme.Cursor`: use `theme.Background`, as suggested by the “redrawn in the screen color” comment, or choose whichever resolved cell colour contrasts more with the cursor fill. Skip the character redraw when `(cell.Attributes & TerminalAttributes.Hidden) != 0`, while keeping the cursor fill visible.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: Auto: 🧠 Deep: Large, bug-dense terminal, rendering, parser, and settings changes span independent paths.

Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

case '\b': if (_cursorCol > 0) _cursorCol--; break;
// VT and FF are line feeds on every terminal that matters.
case '\n' or '\v' or '\f': LineFeed(); break;
case '\b': if (_cursorCol > 0) _cursorCol = Math.Min(_cursorCol, Columns - 1) - 1; break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. One-column terminals crash on backspace 🐞 Bug ≡ Correctness

ProcessGround subtracts one after clamping a pending-wrap column to Columns - 1, producing
column -1 in a one-column grid. After a character fills that grid, a backspace followed by
printable output indexes the row at -1; wider grids also move back one cell too far at the right
edge.
Agent Prompt
## Issue description
Backspace after a pending wrap computes an incorrect column, including `-1` in a one-column terminal.
## Fix Focus Areas
- src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs[296-306]
- src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs[636-657]
## Recommended Fix
Subtract from the pending column before clamping to the valid cell range. Add tests for backspace after filling both one-column and wider rows.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +811 to +812
var limit = _cursorRow <= _scrollBottom ? _scrollBottom : Rows - 1;
_cursorRow = Math.Min(limit, _cursorRow + n);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Cursor movement stops at wrong margins 🐞 Bug ≡ Correctness

CursorUp and CursorDown choose a scroll margin based on only one side of the cursor position, so
each also constrains movement that starts outside the region. With a region spanning rows 3–5,
moving down from row 1 stops at row 5 instead of reaching the screen bottom, and moving up from
below the region has the symmetric failure.
Agent Prompt
## Issue description
Cursor-up and cursor-down incorrectly stop at scroll margins when movement starts outside the scroll region.
## Fix Focus Areas
- src/OpenIPC.Viewer.Core/Ssh/Terminal/TerminalEmulator.cs[802-812]
## Recommended Fix
Apply margin limits only when the starting row lies between both margins; otherwise use screen limits. Test movements starting above, inside, and below a partial scroll region.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +266 to +268
TerminalThemeOptions = TerminalTheme.BuiltIn
.Select(t => new TerminalThemeOption(Localizer.Instance[$"Settings.Ssh.Theme.{t.Id}"], t))
.ToList();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. Theme labels stay in the old language 🐞 Bug ≡ Correctness

TerminalThemeOptions and TerminalCursorStyleOptions store localized display strings when the
settings view model is constructed and never rebuild them. Changing the application language while
that page remains open updates localizer-backed labels, but both new terminal pickers keep their
previous-language entries until the page is recreated.
Agent Prompt
## Issue description
The new terminal option lists retain their constructor-time language after an in-page language change.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs[266-275]
- src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs[345-346]
## Recommended Fix
Refresh the terminal theme and cursor option displays when the localizer changes, preserving the selected theme and style, or bind their labels directly to live localization.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +1177 to +1181
if (cell.Char > ' ')
{
var (_, bg) = theme.ColorsOf(cell);
DrawCellChar(context, cell.Char, bg, cell.Attributes, x, y);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

4. Block cursor hides the character on highlighted cells 🐞 Bug ≡ Correctness

In DrawCursor’s Block case, the character is redrawn in theme.ColorsOf(cell).Background over
the theme.Cursor fill, and the redraw does not check TerminalAttributes.Hidden. On inverse cells
in most built-in themes, that background matches the cursor colour and the character disappears; on
SGR 8 cells, text suppressed by DrawRow becomes visible while the cursor rests there.
Agent Prompt
## Issue description
The block cursor redraws the cell character in its resolved background colour. On inverse cells, that colour can match the cursor fill, making the character disappear; the redraw also exposes characters hidden with SGR 8.

## Fix Focus Areas
- src/OpenIPC.Viewer.App/Controls/TerminalView.cs[1169-1183]

## Recommended Fix
Redraw visible characters in a colour that contrasts with `theme.Cursor`: use `theme.Background`, as suggested by the “redrawn in the screen color” comment, or choose whichever resolved cell colour contrasts more with the cursor fill. Skip the character redraw when `(cell.Attributes & TerminalAttributes.Hidden) != 0`, while keeping the cursor fill visible.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant