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
24 changes: 24 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,29 @@ public int IndentationSize

Do **not** introduce a separate `private int _indentationSize;` field. The initializer trails the close-brace as shown.

**When an explicit backing field is unavoidable, declare it immediately above the property it backs — never in a field block at the top of the type.** The `field` keyword is the default and removes the question entirely; but some properties still need a real field (a lifted/forked file that preserves upstream style and does not use `field`; a field shared by several members; a field touched by `ref`/`out` or interlocked ops). In those cases the field/property pair stays visually together:

```csharp
private VisualRole? _role;

/// <summary>...</summary>
public VisualRole? Role
{
get => _role;
set
{
if (IsFrozen)
{
throw new InvalidOperationException ();
}

_role = value;
}
}
```

Fork-policy exception: do **not** reflow a lifted file's *existing* upstream field block to satisfy this — that churns the merge story (see the AvaloniaEdit fork policy). The rule binds *new* fields you add, even inside lifted files.

> **ReSharper bug warning.** ReSharper / Rider's "Convert to auto-property" and "Use auto-property" inspections are unreliable around `field` and may rewrite intentional `field`-backed properties into broken auto-properties. The team-shared `.DotSettings` disables these inspections (`ConvertToAutoProperty`, `ConvertToAutoPropertyWhenPossible`, `ConvertToAutoPropertyWithPrivateSetter` set to `DO_NOT_SHOW`; `CSUseAutoProperty` cleanup step disabled). If you see the suggestion in your IDE, ignore it and check that your local Rider is using the team-shared settings. **`field` is not optional in this codebase.**

For trivial getters with no setter logic, plain auto-properties are fine: `public TextDocument? Document { get; private set; }`.
Expand Down Expand Up @@ -163,6 +186,7 @@ private void ExtendCaretBy (int delta)
- **No file longer than 1000 lines.** When a file approaches that, split — by partial class (`Editor.Drawing.cs`, `Editor.Mouse.cs`), by helper extraction, or by genuinely splitting the type. The cleanup hook does not enforce this; the reviewer does.
- **C# 14 `extension` blocks**: prefer extension blocks over a static class full of `this`-prefixed extension methods when the extensions form a coherent group on a single receiver type.
- **Namespace per folder.** `src/Terminal.Gui.Editor/Document/` ⇒ `Terminal.Gui.Document`; `src/Terminal.Gui.Editor/Rendering/` ⇒ `Terminal.Gui.Views.Rendering`. Don't put unrelated types in the same namespace just because they share a folder.
- **No static members on `View`-derived types.** A class that derives from `Terminal.Gui.View` (e.g. `Editor`) must not declare `static` members — not fields, not properties, not events, not even "harmless" caches or lookup tables. Terminal.Gui's `Application` lifetime is per-instance (see "Testing tiers"); static state on a View is process-global, survives across `IApplication` instances, and silently couples otherwise-independent windows and parallel tests (the canonical cause of parallel-test hangs). Shared/lookup data lives in a dedicated non-View type (e.g. `XshdRoleMap`), exposed read-only (`private` + `FrozenDictionary`/`IReadOnlyXxx`), and is injected or queried — never hung off the View. `const` is the only exception (it is not state). This is a hard rule; a reviewer blocks on it.

### Testing convention

Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>

<!-- Pinned Terminal.Gui version. CI / release workflows can override via -p:TerminalGuiVersion=<x>. -->
<TerminalGuiVersion Condition="'$(TerminalGuiVersion)' == ''">2.1.1-develop.59</TerminalGuiVersion>
<TerminalGuiVersion Condition="'$(TerminalGuiVersion)' == ''">2.1.1-develop.98</TerminalGuiVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
54 changes: 25 additions & 29 deletions examples/ted/EditorSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ internal static class EditorSettings
[ConfigurationProperty (Scope = typeof (TedSettingsScope))]
public static bool ShowTabs { get; set; }

[ConfigurationProperty (Scope = typeof (TedSettingsScope))]
public static bool UseThemeBackground { get; set; } = true;

[ConfigurationProperty (Scope = typeof (TedSettingsScope))]
public static int IndentSize { get; set; } = 4;

Expand Down Expand Up @@ -50,13 +47,12 @@ internal static void Load (string path)

try
{
string text = File.ReadAllText (path);
var text = File.ReadAllText (path);

LineNumbers = ReadBool (text, "EditorSettings.LineNumbers", LineNumbers);
FoldIndicators = ReadBool (text, "EditorSettings.FoldIndicators", FoldIndicators);
WordWrap = ReadBool (text, "EditorSettings.WordWrap", WordWrap);
ShowTabs = ReadBool (text, "EditorSettings.ShowTabs", ShowTabs);
UseThemeBackground = ReadBool (text, "EditorSettings.UseThemeBackground", UseThemeBackground);
IndentSize = ReadInt (text, "EditorSettings.IndentSize", IndentSize);
ConvertTabsToSpaces = ReadBool (text, "EditorSettings.ConvertTabsToSpaces", ConvertTabsToSpaces);
AutoIndent = ReadBool (text, "EditorSettings.AutoIndent", AutoIndent);
Expand All @@ -77,27 +73,26 @@ internal static void Save (string path)
try
{
EnsureConfigFile (path);
string text = File.ReadAllText (path);
var text = File.ReadAllText (path);
Dictionary<string, string> entries = new ()
{
["EditorSettings.LineNumbers"] = ToJson (LineNumbers),
["EditorSettings.FoldIndicators"] = ToJson (FoldIndicators),
["EditorSettings.WordWrap"] = ToJson (WordWrap),
["EditorSettings.ShowTabs"] = ToJson (ShowTabs),
["EditorSettings.UseThemeBackground"] = ToJson (UseThemeBackground),
["EditorSettings.IndentSize"] = IndentSize.ToString (),
["EditorSettings.ConvertTabsToSpaces"] = ToJson (ConvertTabsToSpaces),
["EditorSettings.AutoIndent"] = ToJson (AutoIndent)
};

List<string> toInsert = [];

foreach ((string key, string value) in entries)
foreach (var (key, value) in entries)
{
Regex pattern = new (
$@"^(?<prefix>\s*""{Regex.Escape (key)}""\s*:\s*)(?:true|false|-?\d+)(?<suffix>\s*,?\s*(?://.*)?)$",
RegexOptions.Multiline);
bool replaced = false;
var replaced = false;
text = pattern.Replace (
text,
match =>
Expand All @@ -118,19 +113,19 @@ internal static void Save (string path)

if (toInsert.Count > 0)
{
int lastBrace = FindRootClosingBrace (text);
var lastBrace = FindRootClosingBrace (text);

if (lastBrace >= 0)
{
int insertCommaAfter = FindLastObjectMemberCharacterPosition (text, lastBrace);
var insertCommaAfter = FindLastObjectMemberCharacterPosition (text, lastBrace);

if (insertCommaAfter >= 0 && text[insertCommaAfter] != ',' && text[insertCommaAfter] != '{')
{
text = text.Insert (insertCommaAfter + 1, ",");
lastBrace = FindRootClosingBrace (text);
}

string insertion = $"\n\n{string.Join (",\n", toInsert)}\n";
var insertion = $"\n\n{string.Join (",\n", toInsert)}\n";
text = text.Insert (lastBrace, insertion);
}
}
Expand All @@ -145,17 +140,16 @@ internal static void Save (string path)

internal static string GetConfigPath ()
{
string home =
var home =
Environment.GetEnvironmentVariable ("HOME")
?? Environment.GetFolderPath (Environment.SpecialFolder.UserProfile)
?? Directory.GetCurrentDirectory ();
?? Environment.GetFolderPath (Environment.SpecialFolder.UserProfile);

return Path.Combine (home, ".tui", "ted.config.json");
}

private static void EnsureConfigFile (string path)
{
string? directory = Path.GetDirectoryName (path);
var directory = Path.GetDirectoryName (path);

if (!string.IsNullOrWhiteSpace (directory))
{
Expand All @@ -182,7 +176,9 @@ private static bool ReadBool (string json, string key, bool defaultValue)
$@"^(?!\s*//)\s*""{Regex.Escape (key)}""\s*:\s*(?<v>true|false)",
RegexOptions.IgnoreCase | RegexOptions.Multiline);

return m.Success ? string.Equals (m.Groups["v"].Value, "true", StringComparison.OrdinalIgnoreCase) : defaultValue;
return m.Success
? string.Equals (m.Groups["v"].Value, "true", StringComparison.OrdinalIgnoreCase)
: defaultValue;
}

private static int ReadInt (string json, string key, int defaultValue)
Expand All @@ -192,7 +188,7 @@ private static int ReadInt (string json, string key, int defaultValue)
$@"^(?!\s*//)\s*""{Regex.Escape (key)}""\s*:\s*(?<v>-?\d+)",
RegexOptions.Multiline);

return m.Success && int.TryParse (m.Groups["v"].Value, out int v) ? v : defaultValue;
return m.Success && int.TryParse (m.Groups["v"].Value, out var v) ? v : defaultValue;
}

/// <summary>
Expand All @@ -201,7 +197,7 @@ private static int ReadInt (string json, string key, int defaultValue)
/// </summary>
private static int FindRootClosingBrace (string text)
{
int i = text.Length - 1;
var i = text.Length - 1;

while (i >= 0)
{
Expand All @@ -213,8 +209,8 @@ private static int FindRootClosingBrace (string text)
}

// Check if this '}' is on a comment line
int lineStart = text.LastIndexOf ('\n', i) + 1;
string lineBeforeBrace = text[lineStart..i];
var lineStart = text.LastIndexOf ('\n', i) + 1;
var lineBeforeBrace = text[lineStart..i];

if (lineBeforeBrace.TrimStart ().StartsWith ("//", StringComparison.Ordinal))
{
Expand All @@ -231,11 +227,11 @@ private static int FindRootClosingBrace (string text)

private static int FindLastObjectMemberCharacterPosition (string text, int braceIndex)
{
int i = braceIndex - 1;
var i = braceIndex - 1;

while (i >= 0)
{
char c = text[i];
var c = text[i];

if (char.IsWhiteSpace (c))
{
Expand All @@ -244,9 +240,9 @@ private static int FindLastObjectMemberCharacterPosition (string text, int brace
continue;
}

int lineStart = text.LastIndexOf ('\n', i) + 1;
string line = text[lineStart..(i + 1)];
string trimmedLine = line.TrimStart ();
var lineStart = text.LastIndexOf ('\n', i) + 1;
var line = text[lineStart..(i + 1)];
var trimmedLine = line.TrimStart ();

if (trimmedLine.StartsWith ("//", StringComparison.Ordinal))
{
Expand All @@ -255,12 +251,12 @@ private static int FindLastObjectMemberCharacterPosition (string text, int brace
continue;
}

int commentStart = line.IndexOf ("//", StringComparison.Ordinal);
var commentStart = line.IndexOf ("//", StringComparison.Ordinal);

if (commentStart >= 0)
{
string withoutComment = line[..commentStart];
int lastNonWhitespace = withoutComment.TrimEnd ().Length - 1;
var withoutComment = line[..commentStart];
var lastNonWhitespace = withoutComment.TrimEnd ().Length - 1;

if (lastNonWhitespace >= 0)
{
Expand Down
20 changes: 9 additions & 11 deletions examples/ted/EditorSettingsDialog.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Terminal.Gui.App;
using Terminal.Gui.Editor;
using Terminal.Gui.Input;
using Terminal.Gui.Text.Indentation;
using Terminal.Gui.ViewBase;
using Terminal.Gui.Views;
Expand All @@ -9,11 +7,9 @@ namespace Ted;

internal sealed class EditorSettingsDialog : Dialog
{
private readonly NumericUpDown<int> _indentSize;
private readonly CheckBox _convertTabsCheck;
private readonly CheckBox _autoIndentCheck;

internal bool WasAccepted { get; private set; }
private readonly CheckBox _convertTabsCheck;
private readonly NumericUpDown<int> _indentSize;

internal EditorSettingsDialog (Editor editor)
{
Expand All @@ -28,7 +24,7 @@ internal EditorSettingsDialog (Editor editor)
Height = Dim.Fill ()
};

_indentSize = new ()
_indentSize = new NumericUpDown<int>
{
X = 20,
Y = 1,
Expand All @@ -43,15 +39,15 @@ internal EditorSettingsDialog (Editor editor)
}
};

_convertTabsCheck = new ()
_convertTabsCheck = new CheckBox
{
X = 1,
Y = 3,
Title = "Con_vert Tabs to Spaces",
Value = editor.ConvertTabsToSpaces ? CheckState.Checked : CheckState.UnChecked
};

_autoIndentCheck = new ()
_autoIndentCheck = new CheckBox
{
X = 1,
Y = 5,
Expand All @@ -60,7 +56,7 @@ internal EditorSettingsDialog (Editor editor)
};

tabSettingsTab.Add (
new Label () { X = 1, Y = 1, Text = "_Indent size:" },
new Label { X = 1, Y = 1, Text = "_Indent size:" },
_indentSize,
_convertTabsCheck,
_autoIndentCheck);
Expand All @@ -72,7 +68,7 @@ internal EditorSettingsDialog (Editor editor)
Height = Dim.Fill ()
};

configTab.Add (new Label () { X = 1, Y = 1, Text = "No settings yet." });
configTab.Add (new Label { X = 1, Y = 1, Text = "No settings yet." });

Tabs tabs = new ()
{
Expand Down Expand Up @@ -110,6 +106,8 @@ internal EditorSettingsDialog (Editor editor)
Add (tabs, okBtn, cancelBtn);
}

internal bool WasAccepted { get; private set; }

internal void ApplyTo (Editor editor)
{
editor.IndentationSize = Math.Max (1, _indentSize.Value);
Expand Down
2 changes: 1 addition & 1 deletion examples/ted/FindReplaceDialog.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Terminal.Gui.Document.Search;
using Terminal.Gui.ViewBase;
using Terminal.Gui.Editor;
using Terminal.Gui.ViewBase;
using Terminal.Gui.Views;

namespace Ted;
Expand Down
2 changes: 0 additions & 2 deletions examples/ted/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,3 @@
}

app.Run (ted);

return;
2 changes: 0 additions & 2 deletions examples/ted/TedApp.EditCommands.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using Terminal.Gui.App;
using Terminal.Gui.Input;
using Terminal.Gui.ViewBase;
using Terminal.Gui.Editor;
using Terminal.Gui.Views;

namespace Ted;
Expand Down
3 changes: 2 additions & 1 deletion examples/ted/TedApp.FileOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ public void OpenMissingFile (string filePath)

SetDocument (string.Empty, filePath);
TextDocument document = Editor.Document
?? throw new InvalidOperationException ("ted cannot open a missing file because the editor has no document.");
?? throw new InvalidOperationException (
"ted cannot open a missing file because the editor has no document.");
document.UndoStack.DiscardOriginalFileMarker ();
}

Expand Down
4 changes: 1 addition & 3 deletions examples/ted/TedApp.MarkdownPreview.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Terminal.Gui.Drawing;
using Terminal.Gui.ViewBase;
using Terminal.Gui.Views;
using TextMateSharp.Grammars;

namespace Ted;

Expand Down Expand Up @@ -75,8 +74,7 @@ private void ShowMarkdownPreview ()
Height = Editor.Height,
Text = Editor.Document?.Text ?? string.Empty,
ViewportSettings = ViewportSettingsFlags.HasScrollBars,
SyntaxHighlighter = new TextMateSyntaxHighlighter (ThemeName.DarkPlus),
UseThemeBackground = Editor.UseThemeBackground
SyntaxHighlighter = new TextMateSyntaxHighlighter ()
};

// Editor takes the left half, preview takes the right half.
Expand Down
Loading
Loading