Contributing to Terminal.Gui
June 28, 2026 ยท View on GitHub
๐ This document is the single source of truth for all contributors (humans and AI agents) to Terminal.Gui.
For Terminal.Gui's product mission, design tenets, and engineering philosophy, see specs/constitution.md.
Welcome! This guide provides everything you need to know to contribute effectively to Terminal.Gui, including project structure, build instructions, coding conventions, testing requirements, and CI/CD workflows.
Table of Contents
- Project Overview
- Key Architecture Concepts
- Coding Conventions
- Building and Testing
- Testing Requirements
- API Documentation Requirements
- Pull Request Guidelines
- CI/CD Workflows
- Repository Structure
- Branching Model
- What NOT to Do
Project Overview
Terminal.Gui is a cross-platform UI toolkit for creating console-based graphical user interfaces in .NET. It's a large codebase (~1,050 C# files) providing a comprehensive framework for building interactive console applications with support for keyboard and mouse input, customizable views, and a robust event system.
Key characteristics:
- Language: C# 14 (net10.0)
- Platform: Cross-platform (Windows, macOS, Linux)
- Architecture: Console UI toolkit with driver-based architecture
- Version: v2 (stable), v1 (maintenance mode)
- Branching: GitFlow model (develop is default/active development)
Key Architecture Concepts
โ ๏ธ CRITICAL - AI Agents MUST understand these concepts before starting work.
- Application Lifecycle - How
Application.Init,Application.Run, andApplication.Shutdownwork - Application Deep Dive - Cancellable Workflow Patern - CWP Deep Dive
- View Hierarchy - Understanding
View,Runnable,Window, and view containment - View Deep Dive - Layout System - Pos, Dim, and automatic layout - Layout System
- Event System - How keyboard, mouse, and application events flow - Events Deep Dive
- Driver Architecture - How console drivers abstract platform differences - Drivers
- Drawing Model - How rendering works with Attributes, Colors, and Glyphs - Drawing Deep Dive
Building and Testing
Required Tools
- .NET SDK: 10.0.100 (see
global.json) - Runtime: .NET 10.x (latest GA)
- Optional: ReSharper/Rider for code formatting (honor
.editorconfigandTerminal.sln.DotSettings)
Build Commands (In Order)
ALWAYS run these commands from the repository root:
-
Restore packages (required first, ~15-20 seconds):
dotnet restore -
Build solution (Debug, ~50 seconds):
dotnet build --configuration Debug --no-restore- Expect ~326 warnings (nullable reference warnings, unused variables, etc.) - these are normal
- 0 errors expected
-
Build Release (for packaging):
dotnet build --configuration Release --no-restore
Test Commands
Two test projects exist:
-
Non-parallel tests (depend on static state, ~10 min timeout):
dotnet test --project Tests/UnitTests.NonParallelizable --no-build --verbosity normal- Uses
Application.Initand static state - Cannot run in parallel
- Includes
--diagnosticflag for logging
- Uses
-
Parallel tests (can run concurrently, ~10 min timeout):
dotnet test --project Tests/UnitTestsParallelizable --no-build --verbosity normal- No dependencies on static state
- Preferred for new tests
-
Integration tests:
dotnet test --project Tests/IntegrationTests --no-build --verbosity normal
Common Build Issues
Coding Conventions
โ ๏ธ CRITICAL - These rules MUST be followed in ALL new or modified code
Code Style Tenets
- Six-Year-Old Reading Level - Readability over terseness
- Consistency, Consistency, Consistency - Follow existing patterns ruthlessly
- Don't be Weird - Follow Microsoft/.NET conventions
- Set and Forget - Rely on automated tooling
- Documentation is the Spec - API docs are source of truth
Code Formatting
โ ๏ธ CRITICAL - These rules MUST be followed in ALL new or modified code:
AI or AI Agent Written or Modified Code MUST Follow these instructions
- Read and study
.editorconfigandTerminal.sln.DotSettingsto determine code style and formatting. - Format code with:
- ReSharper/Rider (
Ctrl-E-C) - JetBrains CleanupCode CLI tool (free)
- Visual Studio (
Ctrl-K-D) as fallback
- ReSharper/Rider (
- Only format files you modify
- Follow
.editorconfigsettings - ALWAYS use explicit types - Never use
varexcept for built-in simple types (int,string,bool,double,float,decimal,char,byte)// โ CORRECT - Explicit types View view = new () { Width = 10 }; MouseEventArgs args = new () { Position = new Point(5, 5) }; List<View?> views = new (); var count = 0; // OK - int is a built-in type var name = "test"; // OK - string is a built-in type // โ WRONG - Using var for non-built-in types var view = new View { Width = 10 }; var args = new MouseEventArgs { Position = new Point(5, 5) }; var views = new List<View?>(); - ALWAYS use target-typed
new ()- Usenew ()instead ofnew TypeName()when the type is already declared// โ CORRECT - Target-typed new View view = new () { Width = 10 }; MouseEventArgs args = new (); // โ WRONG - Redundant type name View view = new View() { Width = 10 }; MouseEventArgs args = new MouseEventArgs(); - ALWAYS use collection initializers if possible:
// โ CORRECT - Collection initializer List<View> views = [ new Button("OK"), new Button("Cancel") ]; // โ WRONG - Adding items separately List<View> views = new (); views.Add(new Button("OK")); views.Add(new Button("Cancel"));
โ ๏ธ CRITICAL - These conventions apply to ALL code - production code, test code, examples, documentation, and samples.
- Prefer early return - Use guard clauses to reduce nesting:
// โ CORRECT - Early return if (view is null) { return; } DoWork (view); // โ WRONG - Unnecessary nesting if (view is not null) { DoWork (view); } - One type per file - Each public or internal type gets its own file, named to match the type (e.g.,
Button.csforclass Button). Private nested types belong in their containing type's file.
Unicode and Grapheme Handling
Think in graphemes, not runes. A grapheme cluster is what the user perceives as a single character, but it may consist of multiple Rune values (e.g., base character + combining marks, or ZWJ emoji sequences).
- Always use
string.GetColumns()to measure display width โ neverEnumerateRunes().Sum(r => r.GetColumns())(inflates multi-rune clusters) orstring.Length(counts chars, not terminal cells) - Iterate by grapheme using
GraphemeHelper.GetGraphemes()when rendering text โ never iterate byRuneand callAddRunefor each (breaks combining marks and ZWJ sequences) - Render with
AddStrpassing complete grapheme strings โAddRunewith individual runes from a cluster will not compose correctly
// โ
CORRECT โ grapheme-aware width measurement
int width = text.GetColumns ();
// โ WRONG โ inflates width for ZWJ emoji (e.g., ๐จโ๐ฉโ๐ฆโ๐ฆ โ 8 instead of 2)
int width = text.EnumerateRunes ().Sum (r => r.GetColumns ());
// โ
CORRECT โ grapheme-aware rendering
foreach (string grapheme in GraphemeHelper.GetGraphemes (text))
{
AddStr (grapheme);
}
// โ WRONG โ breaks combining marks (รฉ rendered as e + ฬ separately)
foreach (Rune rune in text.EnumerateRunes ())
{
AddRune (rune);
}
Exception: Rune-level iteration is appropriate when inspecting individual Unicode scalar values (e.g., counting zero-width runes for vertical text layout), not for rendering or measurement.
Testing Requirements
Code Coverage
- Never decrease code coverage - PRs must maintain or increase coverage
- Target: 70%+ coverage for new code
- Coverage collection:
- Temporarily disabled in CI during xUnit v3 / MTP migration
- Will be re-enabled once an MTP-compatible coverage solution is integrated
Test Patterns
- AI Created Tests MUST follow these patterns exactly.
- Add comment indicating the test was AI generated - e.g.,
// CoPilot - ChatGPT v4 - Make tests granular - Each test should cover smallest area possible
- Follow existing test patterns in respective test projects
- Avoid adding new tests to the
UnitTestsProject - Make them parallelizable and add them toUnitTests.Parallelizable - Avoid static dependencies - DO NOT use the legacy/static
ApplicationAPI orConfigurationManagerin tests unless the tests explicitly test related functionality. - Don't use
[AutoInitShutdown]or[SetupFakeApplication]- Legacy pattern, being phased out
Test Configuration
xunit.runner.json- xUnit configurationcoverlet.runsettings- Coverage settings (currently unused, pending MTP integration)
API Documentation Requirements
All public APIs MUST have XML documentation:
- Clear, concise
<summary>tags - Use
<see cref=""/>for cross-references - Add
<remarks>for context - Include
<example>for non-obvious usage - Complex topics โ
docfx/docs/*.mdfiles - Proper English and grammar - Clear, concise, complete. Use imperative mood.
Pull Request Guidelines
PR Requirements
-
ALWAYS include instructions for pulling down locally at end of Description
-
Title: "Fixes #issue. Terse description". If multiple issues, list all, separated by commas (e.g. "Fixes #123, #456. Terse description")
-
Description:
- Include "- Fixes #issue" for each issue near the top
- Suggest user setup a remote named
copilotpointing to your fork - Example:
# To pull down this PR locally: git remote add copilot <your-fork-url> git fetch copilot <branch-name> git checkout copilot/<branch-name>
-
Tests: Add tests for new functionality (see Testing Requirements)
-
Coverage: Maintain or increase code coverage
-
Scenarios: Update UICatalog scenarios when adding features
-
Warnings: CRITICAL - PRs must not introduce any new warnings
- Any file modified in a PR that currently generates warnings MUST be fixed to remove those warnings
- Exception: Warnings caused by
[Obsolete]attributes can remain - Action: Before submitting a PR, verify your changes don't add new warnings and fix any warnings in files you modify
Repository Structure
Root Directory Files
Terminal.slnx- Main solution fileTerminal.sln.DotSettings- ReSharper code style settings.editorconfig- Code formatting rules (111KB, extensive)global.json- .NET SDK version pinningDirectory.Build.props- Common MSBuild propertiesDirectory.Packages.props- Central package version managementGitVersion.yml- Version numbering configurationCONTRIBUTING.md- This file - contribution guidelines (source of truth)AGENTS.md- Pointer to this file for AI agentsREADME.md- Project documentation
Main Directories
/Terminal.Gui/ - Core library (496 C# files):
App/- Application lifecycle (Application.csstatic class,SessionToken,MainLoop)Configuration/-ConfigurationManagerfor settingsDrivers/- Console driver implementations (dotnet,Windows,Unix,ansi)Drawing/- Rendering system (attributes, colors, glyphs)Input/- Keyboard and mouse input handlingViewBase/- CoreViewclass hierarchy and layoutViews/- Specific View subclasses (Window, Dialog, Button, ListView, etc.)Text/- Text manipulation and formattingFileServices/- File operations and services
/Tests/:
UnitTests/- Non-parallel tests (useApplication.Init, static state)UnitTestsParallelizable/- Parallel tests (no static dependencies) - PreferredIntegrationTests/- Integration testsStressTests/- Long-running stress tests (scheduled daily)NativeAotSmoke/- AOT smoke-test app used in CI validationcoverlet.runsettings- Code coverage configuration
/Examples/:
UICatalog/- Comprehensive demo app for manual testingScenarioRunner/- Scenario automation tool- Additional examples live in tui-cs/Examples
/docfx/ - Documentation source:
docs/- Conceptual documentation (deep dives)api/- Generated API docs (gitignored)docfx.json- DocFX configuration
/Scripts/ - PowerShell build utilities (requires PowerShell 7.4+)
/.github/workflows/ - CI/CD pipelines (see CI/CD Workflows)
Branching Model
GitFlow Model
develop- Default branch, active developmentmain- Stable releases, matches NuGetv1_develop,v1_release- Legacy v1 (maintenance only)
Release Process
Automated Release Workflow
Releases are automated using GitHub Actions to prevent manual errors. The flow is PR-based: Prepare Release opens a release PR from develop โ main, and merging it runs Finalize Release. To create a release:
- Navigate to Actions tab in the GitHub repository
- Select "Prepare Release" workflow from the left sidebar
- Click "Run workflow" button
- Configure release parameters:
- Release type: Choose from
beta,rc, orstable - Version override: (Optional) Specify exact version (e.g.,
2.0.0), otherwise GitVersion calculates it automatically
- Release type: Choose from
- Click "Run workflow" to create the release PR
Prepare Release creates a release/<tag> branch โ where <tag> is the full version tag, e.g. release/v2.0.0 for a stable release or release/v2.0.0-beta.1 for a pre-release โ with the GitVersion label updated for the release type, and opens a PR into main. After CI passes, review and merge that PR. Merging triggers Finalize Release, which:
- Creates an annotated git tag (e.g.,
v2.0.0-beta.1orv2.0.0) - Creates a GitHub Release with auto-generated notes
- Opens a back-merge PR from
mainโdevelop - Triggers the publish workflow (via the
v*tag) to push the package to NuGet.org
What NOT to Do
- โ Don't add new linters/formatters (use existing)
- โ Don't modify unrelated code
- โ Don't remove/edit unrelated tests
- โ Don't break existing functionality
- โ Don't add tests to
UnitTestsif they can be parallelizable - โ Don't use
Application.Initin new tests - โ Don't decrease code coverage
- โ Don't use
varfor anything but built-in simple types (use explicit types) - โ Don't use redundant type names with
new(ALWAYS PREFER target-typednew ()) - โ Don't introduce new warnings (fix warnings in files you modify; exception:
[Obsolete]warnings) - โ Don't use
EnumerateRunes().Sum(GetColumns)for display width โ usestring.GetColumns() - โ Don't use
AddRunein a rune loop to render text โ iterate by grapheme withGraphemeHelper.GetGraphemes()and useAddStr
Thank you for contributing to Terminal.Gui! ๐