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
2 changes: 1 addition & 1 deletion .claude/hooks/cleanup-cs.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ if (-not $changed) { exit 0 }
# --include narrows the work to changed files.
$includes = ($changed | ForEach-Object { "--include=$_" }) -join ' '
if ($includes) {
& dotnet jb cleanupcode Terminal.Gui.Text.slnx --profile="Built-in: Full Cleanup" $includes.Split(' ') --no-build *> $null
& dotnet jb cleanupcode Terminal.Gui.Text.slnx --profile="TG.Text Full Cleanup" $includes.Split(' ') --no-build *> $null
}

# Surface the net effect so the agent sees its own drift.
Expand Down
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ csharp_space_between_square_brackets = false
# discoverability, but don't block the build. Enforcement happens in two other places:
#
# 1. `dotnet format Terminal.Gui.Text.slnx --verify-no-changes` in CI, and
# 2. `dotnet jb cleanupcode Terminal.Gui.Text.slnx --profile="Full Cleanup"` in CI
# 2. `dotnet jb cleanupcode Terminal.Gui.Text.slnx --profile="TG.Text Full Cleanup"` in CI
# (and the Stop hook in .claude/settings.json before each agent turn ends).
#
# Both tools auto-fix what they can and surface a clean diff. The `:warning` posture
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,17 @@ jobs:
- name: Verify code style — ReSharper cleanupcode
if: matrix.os == 'ubuntu-latest'
run: |
# Use the built-in profile name (jb cleanupcode does not always discover custom
# profile names from team-shared .DotSettings files reliably). Our .DotSettings
# still ships its own profile + style settings (var, expression-bodied, etc.) for
# Rider users; the CLI just runs the default-shaped cleanup using those settings.
# Use our custom profile name defined in the team-shared .DotSettings file.
# Previous attempts used "Built-in: Full Cleanup" which worked but didn't pick
# up our tweaks (CSUseAutoProperty OFF, etc.). The custom-named profile resolves
# reliably from the .DotSettings file.
dotnet jb cleanupcode Terminal.Gui.Text.slnx \
--profile="Built-in: Full Cleanup" \
--profile="TG.Text Full Cleanup" \
--no-build \
--exclude="third_party/**/*" \
|| true
if ! git diff --exit-code; then
echo "::error::ReSharper code cleanup found style drift. Run 'dotnet jb cleanupcode Terminal.Gui.Text.slnx --profile=\"Built-in: Full Cleanup\"' locally and commit the result."
echo "::error::ReSharper code cleanup found style drift. Run 'dotnet jb cleanupcode Terminal.Gui.Text.slnx --profile=\"TG.Text Full Cleanup\"' locally and commit the result."
exit 1
fi

Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ The fork is **hard** — re-syncs are manual and deliberate, triggered only by u
Adopts Terminal.Gui's house style. Three enforcement layers:

1. **`.editorconfig` + `dotnet format`** — formatting, var, expression-bodied, collection expressions, modern syntax preferences. CI runs `dotnet format --verify-no-changes`.
2. **`Terminal.Gui.Text.slnx.DotSettings` + `dotnet jb cleanupcode`** — ReSharper-driven cleanup ("Full Cleanup" profile). Catches what `dotnet format` misses (XML doc spacing, using sorting, name qualifier removal, expression-bodied conversions). CI runs `dotnet jb cleanupcode` and fails on any diff.
2. **`Terminal.Gui.Text.slnx.DotSettings` + `dotnet jb cleanupcode`** — ReSharper-driven cleanup ("TG.Text Full Cleanup" profile). Catches what `dotnet format` misses (XML doc spacing, using sorting, name qualifier removal, expression-bodied conversions). CI runs `dotnet jb cleanupcode` and fails on any diff.
3. **A Stop hook in `.claude/settings.json`** that runs both tools on .cs files modified during the session before the agent reports done. Output is suppressed unless the cleanup actually changed something.

**Before declaring work complete, an agent must run `dotnet tool restore && dotnet format Terminal.Gui.Text.slnx --exclude third_party/ && dotnet jb cleanupcode Terminal.Gui.Text.slnx --profile="Full Cleanup"` (the Stop hook does this automatically). If the cleanup adjusts files, those changes are part of the work — re-stage and continue.**
**Before declaring work complete, an agent must run `dotnet tool restore && dotnet format Terminal.Gui.Text.slnx --exclude third_party/ && dotnet jb cleanupcode Terminal.Gui.Text.slnx --profile="TG.Text Full Cleanup"` (the Stop hook does this automatically). If the cleanup adjusts files, those changes are part of the work — re-stage and continue.**

### Formatting and spacing

Expand Down
67 changes: 36 additions & 31 deletions Terminal.Gui.Text.slnx.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,45 @@
-->

<!-- =============================================================== -->
<!-- Cleanup profile: "Full Cleanup" — used by `jb cleanupcode`, -->
<!-- the Claude Code Stop hook, and CI verify step. -->
<!-- Cleanup profile: "TG.Text Full Cleanup" -->
<!-- Used by `jb cleanupcode`, the Claude Code Stop hook, and CI. -->
<!-- -->
<!-- This is a copy of the built-in "Full Cleanup" with our tweaks -->
<!-- (CSUseAutoProperty OFF, etc.). We use a custom name because -->
<!-- `jb cleanupcode --profile="Full Cleanup"` fails to resolve the -->
<!-- built-in name when it's overridden in team-shared settings. -->
<!-- =============================================================== -->
<s:String x:Key="/Default/CodeStyle/CodeCleanup/RecentlyUsedProfile/@EntryValue">Full Cleanup</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue">Full Cleanup</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/RecentlyUsedProfile/@EntryValue">TG.Text Full Cleanup</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue">TG.Text Full Cleanup</s:String>

<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/Description/@EntryValue">gui-cs/Text house cleanup. Keep aligned with CLAUDE.md.</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/Name/@EntryValue">Full Cleanup</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/Description/@EntryValue">gui-cs/Text house cleanup. Keep aligned with CLAUDE.md.</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/Name/@EntryValue">TG.Text Full Cleanup</s:String>

<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSArrangeAccessorOwnerBody/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSArrangeArgumentsStyle/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSArrangeAttributes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSArrangeBraces/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSArrangeCodeBodyStyle/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSArrangeDefaultBody/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSArrangeMethodOrOperatorBody/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSArrangeQualifiers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSArrangeThisQualifier/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSArrangeTrailingCommas/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSArrangeTypeMemberModifiers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSArrangeTypeModifiers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSFixBuiltinTypeReferences/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSOptimizeUsings/OptimizeUsings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSReformatCode/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSReorderTypeMembers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSShortenReferences/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSStaticnessAnalysis/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSUpdateFileHeader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSUseAutoProperty/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSUseVar/BehavourStyle/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CSDeclareLocalsAsType/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/CsRemoveCodeRedundancies/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Full_0020Cleanup/RemoveCodeRedundanciesVB/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSArrangeAccessorOwnerBody/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSArrangeArgumentsStyle/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSArrangeAttributes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSArrangeBraces/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSArrangeCodeBodyStyle/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSArrangeDefaultBody/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSArrangeMethodOrOperatorBody/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSArrangeQualifiers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSArrangeThisQualifier/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSArrangeTrailingCommas/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSArrangeTypeMemberModifiers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSArrangeTypeModifiers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSFixBuiltinTypeReferences/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSOptimizeUsings/OptimizeUsings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSReformatCode/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSReorderTypeMembers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSShortenReferences/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSStaticnessAnalysis/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSUpdateFileHeader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSUseAutoProperty/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSUseVar/BehavourStyle/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CSDeclareLocalsAsType/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/CsRemoveCodeRedundancies/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TG_002EText_0020Full_0020Cleanup/RemoveCodeRedundanciesVB/@EntryIndexedValue">True</s:Boolean>

<!-- IMPORTANT: CSUseAutoProperty is OFF. -->
<!-- ReSharper has bugs around the C# 13 `field` keyword: it offers / applies -->
Expand Down
2 changes: 1 addition & 1 deletion examples/ted/TedApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ private Key KeyFor (Command command)
private void ShowAboutDialog ()
{
Dialog dialog = new ()
{ Title = "About ted", Buttons = [new Button { Title = Strings.btnOk, IsDefault = true }] };
{ Title = "About ted", Buttons = [new Button { Title = Strings.btnOk, IsDefault = true }] };

dialog.Border.Settings &= ~BorderSettings.Title;

Expand Down
31 changes: 29 additions & 2 deletions src/Terminal.Gui.Editor/Editor.Drawing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,37 @@ private void DrawVisibleLines (Rectangle viewport, Attribute normal, Attribute s
}
}

// Syntax highlighter state optimization: tracks how far we've prepared so incremental
// scrolling doesn't re-highlight from line 0 every frame.
private int _highlighterPreparedUpToLine = -1;
private ISyntaxHighlighter? _highlighterPreparedInstance;

private void PrepareSyntaxHighlighter (ISyntaxHighlighter? syntaxHighlighter, int firstVisibleLineIndex)
{
if (syntaxHighlighter is null || _document is null)
{
return;
}

syntaxHighlighter.ResetState ();
// If the highlighter instance changed or viewport scrolled backward, reset from scratch.
if (!ReferenceEquals (syntaxHighlighter, _highlighterPreparedInstance)
|| firstVisibleLineIndex < _highlighterPreparedUpToLine)
{
Comment thread
tig marked this conversation as resolved.
syntaxHighlighter.ResetState ();
_highlighterPreparedInstance = syntaxHighlighter;
_highlighterPreparedUpToLine = 0;
}

for (var lineIndex = 0; lineIndex < firstVisibleLineIndex && lineIndex < _document.LineCount; lineIndex++)
// Incrementally highlight from where we left off to the first visible line.
for (var lineIndex = _highlighterPreparedUpToLine; lineIndex < firstVisibleLineIndex && lineIndex < _document.LineCount; lineIndex++)
{
DocumentLine line = _document.GetLineByNumber (lineIndex + 1);
#pragma warning disable CS0618 // Type or member is obsolete — see note in OnDrawingContent.
syntaxHighlighter.Highlight (_document.GetText (line), SyntaxLanguage);
#pragma warning restore CS0618 // Type or member is obsolete
}

_highlighterPreparedUpToLine = firstVisibleLineIndex;
}

private void DrawVisualLine (
Expand All @@ -106,6 +121,18 @@ private void DrawVisualLine (

foreach (CellVisualLineElement element in visualLine.Elements)
{
// Elements are ordered by visual column. Once we pass the visible end,
// all remaining elements are off-screen — skip them entirely.
if (element.VisualColumn >= visibleEnd)
{
break;
}

if (element.VisualEndColumn <= visibleStart)
{
continue;
}

element.Draw (this, 0, row, visibleStart, visibleEnd);
}
}
Expand Down
Loading
Loading