Skip to content

Commit ae9ce4e

Browse files
authored
Merge pull request #224 from gui-cs/release/v2.2.6
Release v2.2.6
2 parents 47367f4 + e435518 commit ae9ce4e

21 files changed

Lines changed: 376 additions & 113 deletions

File tree

.claude/hooks/cleanup-cs.ps1

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,61 @@
77
# 2. dotnet jb cleanupcode (slower, ReSharper-driven; catches what dotnet format misses —
88
# var preferences, expression-bodied members, XML doc spacing, using sorting)
99
#
10-
# Skips itself entirely if the working tree has no modified .cs files outside third_party/.
10+
# Skips itself entirely if the working tree has no modified .cs files outside third_party/
11+
# and lifted AvaloniaEdit folders.
1112

1213
$ErrorActionPreference = 'Stop'
1314

1415
$repo = & git rev-parse --show-toplevel 2>$null
1516
if (-not $repo) { exit 0 }
1617
Set-Location $repo
1718

19+
$liftedPrefixes = @(
20+
'src/Terminal.Gui.Editor/Document/',
21+
'src/Terminal.Gui.Editor/Extensions/',
22+
'src/Terminal.Gui.Editor/Folding/',
23+
'src/Terminal.Gui.Editor/Highlighting/',
24+
'src/Terminal.Gui.Editor/Indentation/',
25+
'src/Terminal.Gui.Editor/Search/',
26+
'src/Terminal.Gui.Editor/Utils/'
27+
)
28+
29+
function Test-IsLiftedPath ([string] $path) {
30+
$normalized = $path -replace '\\', '/'
31+
if ($normalized -like 'third_party/*') { return $true }
32+
33+
foreach ($prefix in $liftedPrefixes) {
34+
if ($normalized.StartsWith($prefix, [StringComparison]::Ordinal)) {
35+
return $true
36+
}
37+
}
38+
39+
return $false
40+
}
41+
1842
# Modified .cs files (staged + unstaged + untracked), excluding lifted upstream code.
1943
$changed = & git status --porcelain |
2044
ForEach-Object { ($_ -replace '^...', '').Trim('"') } |
21-
Where-Object { $_ -like '*.cs' -and $_ -notlike 'third_party/*' }
45+
Where-Object { $_ -like '*.cs' -and -not (Test-IsLiftedPath $_) }
2246

2347
if (-not $changed) { exit 0 }
2448

2549
# Restore tools quietly. Idempotent; first run downloads jb.
2650
& dotnet tool restore --tool-manifest .config/dotnet-tools.json *> $null
2751

2852
# dotnet format (whitespace + style + analyzers) on the whole solution. Single pass is faster
29-
# than per-file invocations because the workspace loads once.
30-
& dotnet format Terminal.Gui.Editor.slnx --no-restore --exclude third_party/ *> $null
53+
# than per-file invocations because the workspace loads once. Exclude lifted code so the
54+
# formatter cannot create upstream-merge-hostile churn as a side effect.
55+
& dotnet format Terminal.Gui.Editor.slnx `
56+
--no-restore `
57+
--exclude third_party/ `
58+
--exclude src/Terminal.Gui.Editor/Document/ `
59+
--exclude src/Terminal.Gui.Editor/Extensions/ `
60+
--exclude src/Terminal.Gui.Editor/Folding/ `
61+
--exclude src/Terminal.Gui.Editor/Highlighting/ `
62+
--exclude src/Terminal.Gui.Editor/Indentation/ `
63+
--exclude src/Terminal.Gui.Editor/Search/ `
64+
--exclude src/Terminal.Gui.Editor/Utils/ *> $null
3165

3266
# ReSharper code cleanup. Uses the built-in profile name because jb cleanupcode does not
3367
# always discover custom profile names from team-shared .DotSettings files reliably; the

.github/workflows/ci.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,22 @@ jobs:
4444
# Style gate runs once (Linux). dotnet format catches whitespace/analyzer drift;
4545
# jb cleanupcode catches what dotnet format misses (var, expression-bodied members,
4646
# XML doc spacing, using sorting). Both must produce a clean working tree.
47+
#
48+
# Keep lifted AvaloniaEdit code out of broad format/cleanup passes. Those files preserve
49+
# upstream formatting for manual re-syncs; formatting-only churn there is not actionable
50+
# style drift. Keep this exclude list in sync with .claude/hooks/cleanup-cs.ps1.
4751
- name: Verify code style — dotnet format
4852
if: matrix.os == 'ubuntu-latest'
4953
run: |
50-
dotnet format Terminal.Gui.Editor.slnx --no-restore --exclude third_party/ --verify-no-changes
54+
dotnet format Terminal.Gui.Editor.slnx --no-restore --verify-no-changes \
55+
--exclude third_party/ \
56+
--exclude src/Terminal.Gui.Editor/Document/ \
57+
--exclude src/Terminal.Gui.Editor/Extensions/ \
58+
--exclude src/Terminal.Gui.Editor/Folding/ \
59+
--exclude src/Terminal.Gui.Editor/Highlighting/ \
60+
--exclude src/Terminal.Gui.Editor/Indentation/ \
61+
--exclude src/Terminal.Gui.Editor/Search/ \
62+
--exclude src/Terminal.Gui.Editor/Utils/
5163
5264
- name: Verify code style — ReSharper cleanupcode
5365
if: matrix.os == 'ubuntu-latest'
@@ -59,10 +71,10 @@ jobs:
5971
dotnet jb cleanupcode Terminal.Gui.Editor.slnx \
6072
--profile="TG.Editor Full Cleanup" \
6173
--no-build \
62-
--exclude="third_party/**/*;src/Terminal.Gui.Editor/Document/**/*;src/Terminal.Gui.Editor/Utils/**/*;src/Terminal.Gui.Editor/Search/**/*" \
74+
--exclude="third_party/**/*;src/Terminal.Gui.Editor/Document/**/*;src/Terminal.Gui.Editor/Extensions/**/*;src/Terminal.Gui.Editor/Folding/**/*;src/Terminal.Gui.Editor/Highlighting/**/*;src/Terminal.Gui.Editor/Indentation/**/*;src/Terminal.Gui.Editor/Search/**/*;src/Terminal.Gui.Editor/Utils/**/*" \
6375
|| true
6476
if ! git diff --exit-code; then
65-
echo "::error::ReSharper code cleanup found style drift. Run 'dotnet jb cleanupcode Terminal.Gui.Editor.slnx --profile=\"TG.Editor Full Cleanup\" --no-build --exclude=\"third_party/**/*;src/Terminal.Gui.Editor/Document/**/*;src/Terminal.Gui.Editor/Utils/**/*;src/Terminal.Gui.Editor/Search/**/*\"' locally and commit the result."
77+
echo "::error::ReSharper code cleanup found style drift outside lifted AvaloniaEdit code. Run '.claude/hooks/cleanup-cs.ps1' locally and commit the result."
6678
exit 1
6779
fi
6880
@@ -81,4 +93,3 @@ jobs:
8193
# NOTE: Performance smoke tests and the BenchmarkDotNet baseline compare live in a
8294
# dedicated workflow: .github/workflows/perf.yml (ubuntu-latest only). They were split
8395
# out so this CI workflow stays purely correctness-focused and fast across all three OSes.
84-

.github/workflows/release.yml

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,13 @@ jobs:
6565
strategy:
6666
fail-fast: false
6767
matrix:
68-
os: [ubuntu-latest, macos-latest, windows-latest]
68+
include:
69+
- os: ubuntu-latest
70+
rid: linux-x64
71+
- os: macos-latest
72+
rid: osx-arm64
73+
- os: windows-latest
74+
rid: win-x64
6975
runs-on: ${{ matrix.os }}
7076
env:
7177
VERSION: ${{ needs.resolve-version.outputs.version }}
@@ -93,6 +99,22 @@ jobs:
9399
- name: Terminal.Gui.Editor.IntegrationTests
94100
run: dotnet run --project tests/Terminal.Gui.Editor.IntegrationTests --no-build -c Release
95101

102+
- name: Publish ted AOT
103+
run: >
104+
dotnet publish examples/ted -c Release
105+
-r ${{ matrix.rid }}
106+
--self-contained
107+
-p:PublishAot=true
108+
-p:Version=${{ env.VERSION }}
109+
-o publish/${{ matrix.rid }}
110+
111+
- name: Upload ted artifact
112+
uses: actions/upload-artifact@v4
113+
with:
114+
name: ted-${{ matrix.rid }}
115+
path: publish/${{ matrix.rid }}/
116+
retention-days: 30
117+
96118
pack-and-publish:
97119
needs: [resolve-version, build-and-test]
98120
runs-on: ubuntu-latest
@@ -124,6 +146,71 @@ jobs:
124146
--source https://api.nuget.org/v3/index.json
125147
--skip-duplicate
126148
149+
# Attach ted AOT binaries to the GitHub Release (tag pushes only).
150+
# The Release is created by finalize-release.yml before the tag push triggers this workflow.
151+
release-assets:
152+
if: github.ref_type == 'tag'
153+
needs: [resolve-version, build-and-test]
154+
runs-on: ubuntu-latest
155+
env:
156+
VERSION: ${{ needs.resolve-version.outputs.version }}
157+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
158+
GH_REPO: ${{ github.repository }}
159+
steps:
160+
- uses: actions/checkout@v5
161+
162+
- name: Download all ted artifacts
163+
uses: actions/download-artifact@v4
164+
with:
165+
pattern: ted-*
166+
path: artifacts/
167+
168+
- name: Package release archives
169+
shell: bash
170+
run: |
171+
set -euo pipefail
172+
mkdir -p dist
173+
174+
package_unix() {
175+
local rid="$1"
176+
local src="artifacts/ted-${rid}"
177+
if [ ! -d "$src" ]; then
178+
echo "::warning::No artifact for ${rid}, skipping"
179+
return 0
180+
fi
181+
chmod +x "$src/ted" 2>/dev/null || true
182+
tar -czf "dist/ted-${VERSION}-${rid}.tar.gz" -C "$src" .
183+
echo "Created dist/ted-${VERSION}-${rid}.tar.gz"
184+
}
185+
186+
package_windows() {
187+
local rid="$1"
188+
local src="artifacts/ted-${rid}"
189+
if [ ! -d "$src" ]; then
190+
echo "::warning::No artifact for ${rid}, skipping"
191+
return 0
192+
fi
193+
(cd "$src" && zip -r "../../dist/ted-${VERSION}-${rid}.zip" .)
194+
echo "Created dist/ted-${VERSION}-${rid}.zip"
195+
}
196+
197+
package_unix osx-arm64
198+
package_unix linux-x64
199+
package_windows win-x64
200+
201+
ls -la dist/
202+
203+
- name: Upload to GitHub Release
204+
shell: bash
205+
run: |
206+
TAG="${GITHUB_REF_NAME}"
207+
if gh release view "$TAG" >/dev/null 2>&1; then
208+
gh release upload "$TAG" dist/* --clobber
209+
else
210+
echo "::warning::Release $TAG not found; creating one."
211+
gh release create "$TAG" --title "ted $TAG" --generate-notes dist/*
212+
fi
213+
127214
# Notify downstream repos (gui-cs/clet) so they can rebuild against the new Editor version.
128215
# Uses a PAT stored as CLET_DISPATCH_TOKEN with `repo` scope on gui-cs/clet.
129216
notify-downstream:

CLAUDE.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Run a single test by passing xUnit.v3 filter args after `--`:
5151
dotnet run --project tests/Terminal.Gui.Editor.Tests -- -method "*MyTestName*"
5252
```
5353

54-
CI verifies formatting with `dotnet format Terminal.Gui.Editor.slnx --verify-no-changes --exclude third_party/`. Run the same locally before pushing if you've touched C# files outside `third_party/`.
54+
CI verifies formatting with `dotnet format Terminal.Gui.Editor.slnx --verify-no-changes` while excluding `third_party/` and AvaloniaEdit-lifted folders. Run `.claude/hooks/cleanup-cs.ps1` locally before pushing if you've touched C# files outside lifted code.
5555

5656
### Verifying the *look* (ANSI snapshots) — MANDATORY for render changes
5757

@@ -84,15 +84,22 @@ The boundary matters: anything that takes a dependency on `Terminal.Gui` types b
8484

8585
See `specs/00-plan.md` §6 for the planned pipeline and full `Editor` public API sketch.
8686

87-
## AvaloniaEdit fork policy
87+
## AvaloniaEdit fork policy — do not format lifted code
8888

89-
Code is lifted from AvaloniaEdit into the relevant `src/Terminal.Gui.Editor/` subfolders (`Document/`, `Utils/` so far; `Folding/`, `Search/`, `Indentation/`, `Highlighting/` to follow). The pinned upstream commit and per-file modification log live in `third_party/AvaloniaEdit/UPSTREAM.md` (with the upstream MIT `LICENSE` alongside).
89+
Code is lifted from AvaloniaEdit into the relevant `src/Terminal.Gui.Editor/` subfolders (`Document/`, `Utils/`, `Folding/`, `Search/`, `Indentation/`, `Highlighting/`, and supporting lifted helpers). The pinned upstream commit and per-file modification log live in `third_party/AvaloniaEdit/UPSTREAM.md` (with the upstream MIT `LICENSE` alongside).
90+
91+
**Do not run broad formatting or cleanup over lifted files.** Treat any file containing
92+
`// Adapted for Terminal.Gui from AvaloniaEdit ...` as upstream-owned formatting, even when it
93+
lives outside `third_party/`. `dotnet format`, ReSharper cleanup, Rider "cleanup on save", and
94+
"format document" can create huge no-op diffs in these files; those diffs make future upstream
95+
re-syncs painful and must be reverted before finishing work.
9096

9197
For lifted files:
9298

9399
- **Preserve original formatting and copyright headers.** House-style reformatting defeats the merge story.
94100
- Add the line `// Adapted for Terminal.Gui from AvaloniaEdit <commit-sha>` under the original header.
95101
- Targeted edits only: strip `using Avalonia.*`, remove `Dispatcher.UIThread.VerifyAccess ()` calls, replace `IBrush`/`Avalonia.Media.Color` with `Terminal.Gui.Color`, drop typeface/font-size from `HighlightingColor`.
102+
- If a formatter or cleanup tool touches lifted files as a side effect, immediately revert those unrelated formatting-only hunks. Keep only the narrow semantic changes required by the task.
96103
- Log every modification in `third_party/AvaloniaEdit/UPSTREAM.md` along with the pinned upstream commit.
97104

98105
The fork is **hard** — re-syncs are manual and deliberate, triggered only by upstream fixes we want.
@@ -105,7 +112,7 @@ Adopts Terminal.Gui's house style. Three enforcement layers:
105112
2. **`Terminal.Gui.Editor.sln.DotSettings` + `dotnet jb cleanupcode`** — ReSharper-driven cleanup ("TG.Editor 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. The file is named `*.sln.DotSettings` (not `*.slnx.DotSettings`) even though the solution is `.slnx`: ReSharper/Rider/`jb` resolve the team-shared layer using the `.sln.` infix (the IDE writes its companion `.sln.DotSettings.user`). The cleanup profile **must** be stored in the modern single-string serialized-`<Profile>` format — ReSharper/`jb` 2024+ silently ignore the old per-task `…/=Name/CSReformatCode/@EntryIndexedValue` key layout, which makes the profile vanish from the IDE and unresolvable by `jb` (and CI's `|| true` then makes the cleanup gate a silent no-op). Edit the profile only via Rider → Settings | Code Cleanup → "Save to: This Solution Team-Shared".
106113
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.
107114

108-
**Before declaring work complete, an agent must run `dotnet tool restore && dotnet format Terminal.Gui.Editor.slnx --exclude third_party/ && dotnet jb cleanupcode Terminal.Gui.Editor.slnx --profile="TG.Editor Full Cleanup"` (the Stop hook does this automatically). If the cleanup adjusts files, those changes are part of the work — re-stage and continue.**
115+
**Before declaring work complete, an agent must run `.claude/hooks/cleanup-cs.ps1` (the Stop hook does this automatically). Then inspect the diff. If cleanup adjusted lifted AvaloniaEdit files (files with the `Adapted for Terminal.Gui from AvaloniaEdit` marker), revert those formatting-only changes before reporting done and fix the cleanup exclude list. Cleanup changes in non-lifted files are part of the work — re-stage and continue.**
109116

110117
### Formatting and spacing
111118

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
A reusable text-editing `View` for [Terminal.Gui](https://github.com/gui-cs/Terminal.Gui). Drop it into your TUI app and you get the editing experience users already expect (caret movement, selection, clipboard, undo/redo, search & replace, folding, syntax highlighting, word wrap) without writing any of it yourself.
66

7-
Ships as a single NuGet package: **[`Terminal.Gui.Editor`](https://www.nuget.org/packages/Terminal.Gui.Editor)**.
7+
Ships as a single NuGet package: **[`Terminal.Gui.Editor`](https://www.nuget.org/packages/Terminal.Gui.Editor)**. API documentation is published on the [Terminal.Gui DocFX site](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui.Editor.html).
88

99
## What this is, and what it isn't
1010

examples/ted/EditorSettingsDialog.cs

Lines changed: 10 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
using Terminal.Gui.Editor;
22
using Terminal.Gui.Resources;
3-
using Terminal.Gui.Text.Indentation;
43
using Terminal.Gui.ViewBase;
54
using Terminal.Gui.Views;
65

76
namespace Ted;
87

8+
/// <summary>
9+
/// Settings dialog for the editor clet. Provides tabs for Config and Tab Settings.
10+
/// </summary>
911
internal sealed class EditorSettingsDialog : Dialog
1012
{
1113
private readonly CheckBox _autoCompleteCheck;
12-
private readonly NumericUpDown<int> _indentSize;
13-
private readonly CheckBox _convertTabsCheck;
14-
private readonly CheckBox _autoIndentCheck;
14+
private readonly EditorTabSettingsTab _tabSettingsTab;
1515

1616
internal EditorSettingsDialog (Editor editor)
1717
{
@@ -20,44 +20,7 @@ internal EditorSettingsDialog (Editor editor)
2020
Height = 13;
2121

2222
// --- Tab Settings tab ---
23-
View tabSettingsTab = new ()
24-
{
25-
Title = "_Tab Settings"
26-
};
27-
28-
View label = new Label { Text = "_Indent size:" };
29-
_indentSize = new NumericUpDown<int>
30-
{
31-
X = Pos.Right (label) + 1,
32-
Value = editor.IndentationSize
33-
};
34-
_indentSize.ValueChanging += (_, e) =>
35-
{
36-
if (e.NewValue < 1)
37-
{
38-
e.Handled = true;
39-
}
40-
};
41-
42-
_convertTabsCheck = new CheckBox
43-
{
44-
Y = Pos.Bottom (_indentSize),
45-
Title = "Con_vert Tabs to Spaces",
46-
Value = editor.ConvertTabsToSpaces ? CheckState.Checked : CheckState.UnChecked
47-
};
48-
49-
_autoIndentCheck = new CheckBox
50-
{
51-
Y = Pos.Bottom (_convertTabsCheck),
52-
Title = "_Auto Indent",
53-
Value = editor.IndentationStrategy is not null ? CheckState.Checked : CheckState.UnChecked
54-
};
55-
56-
tabSettingsTab.Add (
57-
label,
58-
_indentSize,
59-
_convertTabsCheck,
60-
_autoIndentCheck);
23+
_tabSettingsTab = new EditorTabSettingsTab (editor);
6124

6225
_autoCompleteCheck = new CheckBox
6326
{
@@ -77,7 +40,7 @@ internal EditorSettingsDialog (Editor editor)
7740
Tabs tabs = new ();
7841

7942
tabs.InsertTab (0, configTab);
80-
tabs.InsertTab (1, tabSettingsTab);
43+
tabs.InsertTab (1, _tabSettingsTab);
8144

8245
Button okBtn = new ()
8346
{
@@ -104,13 +67,12 @@ internal EditorSettingsDialog (Editor editor)
10467

10568
internal bool WasAccepted { get; private set; }
10669

70+
/// <summary>
71+
/// Applies the accepted settings to the editor. Call only when <see cref="WasAccepted" /> is true.
72+
/// </summary>
10773
internal void ApplyTo (Editor editor)
10874
{
109-
editor.IndentationSize = Math.Max (1, _indentSize.Value);
110-
editor.ConvertTabsToSpaces = _convertTabsCheck.Value == CheckState.Checked;
111-
editor.IndentationStrategy = _autoIndentCheck.Value == CheckState.Checked
112-
? new DefaultIndentationStrategy ()
113-
: null;
75+
_tabSettingsTab.ApplyTo (editor);
11476
editor.CompletionProvider = _autoCompleteCheck.Value == CheckState.Checked
11577
? new WordCompletionProvider ()
11678
: null;

examples/ted/TedApp.FileOperations.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ internal void SetDocument (string text, string? filePath)
263263
/// <summary>Creates the <see cref="SaveDialog" /> used by <see cref="ShowDefaultSaveDialog" />.</summary>
264264
internal SaveDialog CreateSaveDialog ()
265265
{
266-
return new SaveDialog { Title = "Save File As", AllowsMultipleSelection = false, OpenMode = OpenMode.File };
266+
return new SaveDialog { Title = Strings.fdSaveAs, AllowsMultipleSelection = false, OpenMode = OpenMode.File };
267267
}
268268

269269
private string? ShowDefaultSaveDialog ()
@@ -292,8 +292,8 @@ private SaveChangesChoice ShowDefaultSaveChangesDialog ()
292292
"Save changes?",
293293
"The document has unsaved changes. Save before quitting?",
294294
Strings.btnCancel,
295-
"Do_n't Save",
296-
Strings.btnSave);
295+
Strings.btnNo,
296+
Strings.btnYes);
297297

298298
return result switch
299299
{

examples/ted/TedApp.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public TedApp (bool readOnly = false, string? configPath = null)
128128
Menu.ViewMenu.PopoverMenu!.Root!.Add (_previewMarkdownMenuItem);
129129
Menu.Add (new MenuBarItem ("_Options",
130130
[new MenuItem ("_Settings...", string.Empty, ShowSettingsDialog)]));
131-
Menu.Add (new MenuBarItem ("_Help",
131+
Menu.Add (new MenuBarItem (Strings.menuHelp,
132132
[new MenuItem ("_About", "About ted", ShowAboutDialog)]));
133133
_fileNameShortcut = new Shortcut (Key.Empty, "<untitled>", Open)
134134
{

0 commit comments

Comments
 (0)