Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b345c0c
Progress report
Nickelony Apr 11, 2026
2a41281
Progress report
Nickelony Apr 11, 2026
5cca767
Fixes and adjustments
Nickelony Apr 11, 2026
e84cc2e
Refactor pass 1
Nickelony Apr 12, 2026
2fe1966
Refactor pass 2
Nickelony Apr 12, 2026
a75991d
Refactor pass 3
Nickelony Apr 12, 2026
ee5e275
Add "Tomorrow Night" theme
Nickelony Apr 12, 2026
1eaacc6
Fix hover definition issues
Nickelony Apr 12, 2026
4f5807a
Cleanups and adjustments
Nickelony Apr 12, 2026
eb9d3b2
Cleanups and improvements
Nickelony Apr 12, 2026
8db72a8
Cleanups
Nickelony Apr 12, 2026
97096cd
Docs
Nickelony Apr 12, 2026
afb8e43
Cleanups
Nickelony Apr 12, 2026
93b55cd
Cleanups
Nickelony Apr 12, 2026
1184a01
Address Copilot comments
Nickelony Apr 12, 2026
e13e1bb
Apply Copilot suggestions
Nickelony Apr 12, 2026
06097ec
Address review findings: improve null safety, clarify logic, add erro…
Copilot Apr 12, 2026
4f58ed9
Name remaining completion priority constants
Copilot Apr 12, 2026
c35de24
Ship the Lua Language Server + Update all .csproj files (#1190)
Nickelony Apr 21, 2026
f8b256b
Refactors Part 1
Nickelony Apr 21, 2026
b107dbe
Progress report
Nickelony Apr 22, 2026
a16aac5
More improvements
Nickelony Apr 22, 2026
133e3dd
Add docs
Nickelony Apr 22, 2026
9655118
Fix desync issues
Nickelony Apr 22, 2026
b8f0d4c
Add debounce to prevent constant exceptions
Nickelony Apr 22, 2026
19b7b3c
Address Copilot comments
Nickelony May 8, 2026
c15fd5c
Actual improvements
Nickelony May 10, 2026
061be31
Cleanups part 1
Nickelony May 10, 2026
e2e1721
Cleanups part 2
Nickelony May 10, 2026
d0d5886
Test cleanups
Nickelony May 10, 2026
5ebd902
Fixes and more tests
Nickelony May 10, 2026
e112972
Add more tests
Nickelony May 10, 2026
2385ab6
More features
Nickelony May 10, 2026
0475702
Cleanups
Nickelony May 10, 2026
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ packages/
.idea/

build_output.txt

/TombIDE/TombIDE.Shared/TIDE/LuaLS/
116 changes: 77 additions & 39 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## General Project Information

- Language: **C# targeting .NET 8** (desktop application).
- Language: **C# targeting .NET 8** (desktop application).
- This is a level editor suite for a family of 3D game engines used in the classic Tomb Raider series.
- Level formats are grid-based, room-based, and portal-based.
- A room is a spatial container for level geometry and game entities.
Expand All @@ -10,21 +10,43 @@

## General Guidelines

### Files and Namespaces

- Files must use Windows line endings. Only standard ASCII symbols are allowed; do not use Unicode symbols.
- `using` directives are grouped and sorted as follows: `DarkUI` namespaces first, then `System` namespaces, followed by third-party and local namespaces.
- Namespace declarations and type definitions should place the opening brace on a new line.
- Prefer grouping all feature-related functionality within a self-contained module or modules. Avoid creating large code blocks over 10–15 lines in existing modules; instead, offload code to helper functions.
- Avoid duplicating and copypasting code. Implement helper methods instead, whenever similar code is used within a given module, class or feature scope.
- Every document should end with a trailing newline.
- `using` directives and namespace declarations should always be sorted alphabetically.
- Remove unused `using` statements.
- Prefer importing namespaces over fully qualifying framework types when there is no ambiguity. Remove redundant qualifiers such as `System.StringComparison` and use `StringComparison` directly when no namespace conflict exists.
- Prefer file-scoped namespaces where a file contains a single namespace and no language constraint prevents it. If a block-scoped namespace is still required, place the opening brace on a new line and sort multiple namespace declarations alphabetically.

### Nullability

- Each refactor should enable nullable reference types for the touched code. Add `#nullable enabled` at the top of the file only when the project does not already enable nullables, and update the touched code to use nullable annotations and checks correctly.
- Always use `is null` / `is not null` rather than `== null` / `!= null`.
- Prefer nullability attributes and helpers such as `[NotNullWhen]`, `[MemberNotNull]`, `[MaybeNullWhen]` and related annotations when they improve flow analysis and keep the API clear.
- Avoid the null-forgiving operator (`!`) where possible. Prefer flow analysis, null checks, annotations and helper methods instead, and use `!` only when it is truly necessary to express a proven invariant the compiler cannot infer.

### Architecture and Composition

- Keep feature-related functionality within self-contained modules. Avoid large code blocks over 10-15 lines in existing modules; move that logic into helpers or dedicated types.
- Always look for opportunities to de-duplicate code and fix duplication where suitable. Prefer shared helpers or extracted modules when similar code appears within a module, class or feature scope.
- Prefer modern .NET and C# conventions when project-specific guidance does not require something else.
- Design new code with service-based composition in mind. Favor dependency injection seams, and use the temporary `TombLib.WPF` service locator only in code paths that already depend on it.
- Keep vertical slice architecture in mind when choosing where new features, helpers and dependencies should live.

## Formatting

- **Indentation** is four spaces; tabs are not used.
### Indentation

- Indentation uses four spaces; tabs are not used.

- **Braces**:
- Always use braces for multi-statement blocks.
- Do not use braces for single-statement blocks, unless they are within multiple `else if` conditions where surrounding statements are multi-line.

- Opening curly brace `{` for structures, classes and methods should be on the next line, not on the same line:
### Braces

- Always use braces for multi-statement blocks.
- Single-line conditions should not use braces when the entire `if` / `else if` / `else` chain stays single-line.
- Multi-line conditions or multi-line bodies must always use braces.
- If any branch in an `if` / `else if` / `else` chain uses braces, all sibling branches should use braces as well.
- Opening curly brace `{` for structures, classes and methods should be on the next line, not on the same line:

```csharp
public class Foo
Expand All @@ -39,27 +61,29 @@
}
```

- Anonymous delegates and lambdas should keep the brace on the same line:
`delegate () { ... }` or `() => { ... }`.

- **Line breaks and spacing**:
- A blank line separates logically distinct groups of members (fields, constructors, public methods, private helpers, etc.).
- Spaces around binary operators (`=`, `+`, `==`, etc.) and after commas.
- A single space follows keyword `if`/`for`/`while` before the opening parenthesis.
- Expressions may be broken into multiple lines and aligned with the previous line's indentation level to improve readability.
- However, chained LINQ method calls, lambdas or function/method arguments should not be broken into multiple lines, unless they reach more than 150 symbols in length.

- Do not collapse early exits or single-statement conditions into a single line:

Bad example:
```csharp
if (condition) return;
```
Do this instead:
```csharp
if (condition)
return;
```
- Anonymous delegates and lambdas should keep the brace on the same line: `delegate () { ... }` or `() => { ... }`.

### Line Breaks and Spacing

- A blank line separates logically distinct groups of members (fields, constructors, public methods, private helpers, etc.).
- Within method bodies, use a blank line between logically distinct statements and before a control-flow block that starts a new step.
- Avoid whitespace-only lines or dead indentation; blank lines should be truly blank.
- Spaces around binary operators (`=`, `+`, `==`, etc.) and after commas.
- A single space follows keyword `if` / `for` / `while` before the opening parenthesis.
- Expressions may be broken into multiple lines and aligned with the previous line's indentation level to improve readability.
- Chained LINQ method calls, lambdas or function arguments should stay on one line unless they exceed roughly 150 characters.
- Do not collapse early exits or single-statement conditions into one line.

Bad example:
```csharp
if (condition) return;
```

Do this instead:
```csharp
if (condition)
return;
```

## Naming

Expand All @@ -75,40 +99,54 @@

- Fields are generally declared as `public` or `private readonly` depending on usage; expose state via properties where appropriate.
- `var` type should be preferred where possible, when the right-hand type is evident from the initializer.
- Explicit typing should be only used when it is required by logic or compiler, or when type name is shorter than 6 symbols (e.g. `int`, `bool`, `float`).
- Explicit typing should only be used when it is required by logic or compiler, or when the type name is shorter than 6 symbols (e.g. `int`, `bool`, `float`).
- For floating-point numbers, always use `f` postfix and decimal, even if value is not fractional (e.g. `2.0f`).
- Consider `record` or `record struct` when value semantics, immutability, or concise data-carrier behavior make them a better fit than a class or struct.
- Prefer expression-bodied members for methods or properties whose implementation is a single readable line.
- Prefer collection expressions (`[]`, `[item]`, `[..items]`) over `Array.Empty<T>()`, explicit array or list construction, or simple `.ToArray()` / `.ToList()` materialization when the target type supports them and the result stays clear.

## Control Flow and Syntax

- Avoid excessive condition nesting and use early exits / breaks where possible.
- LINQ and lambda expressions are used for collections (`FirstOrDefault`, `Where`, `Any`, etc.).
- Use pattern matching where it keeps the code clearer or removes redundant casts, temporary variables or branching.
- Under nullable-aware code, avoid throwing `ArgumentNullException` for non-nullable parameters when the guard adds no meaningful value.
- When an exception type exposes helper APIs such as `ArgumentNullException.ThrowIfNull`, prefer those helpers over manual `if` blocks when the behavior stays clear.
- Exception and error handling is done with `try`/`catch`, and caught exceptions are logged with [NLog](https://nlog-project.org/) where appropriate.
- Warnings must also be logged by NLog, if cause for the incorrect behaviour is user action.
- Warnings caused by user action should also be logged through NLog.

## Comments

- When comments appear they are single-line `//`. Block comments (`/* ... */`) are rare.
- Comments are sparse. Code relies on meaningful names rather than inline documentation.
- Do not use `<summary>` if surrounding code and/or module isn't already using it. Only add `<summary>` for non-private methods with high complexity.
- If module or function implements complex functionality, a brief description (2-3 lines) may be added in front of it, separated by a blank line from the function body.
- Add XML documentation to classes where it clarifies intent, and to public methods and public properties by default. Use XML documentation for private members only when the behavior is complex enough that names alone are not sufficient.
- If a module or function implements complex functionality, use brief section comments to split long methods into smaller, digestible steps.
- All descriptive comments should end with a full stop (`.`).

## Code Grouping

- Large methods should group related actions together, separated by blank lines.
- Large methods should group related actions together, separated by blank lines and short section comments when they cannot be broken apart further.
- Constants and static helpers that are used several times should appear at the top of a class.
- Constants that are used only within a scope of a method, should be declared within this method.
- One-liner lambdas may be grouped together, if they share similar meaning or functionality.
- Prefer one top-level type per file when practical. Keep multiple classes, enums, records or interfaces in the same file only when they are strictly coupled.
- When a class grows too large in size or scope, split it into smaller partial classes organized by responsibility. Use partial classes only when the responsibilities still belong to the same type; otherwise extract a dedicated helper, service or type instead.
- Avoid one-line wrapper methods unless they remove duplication, enforce a policy, or provide meaning beyond a direct redirect.
- Do not keep generic helper methods inside the same feature class. First check whether a suitable shared helper already exists elsewhere in the codebase; otherwise extract the helper into the most suitable shared library project or dedicated module.
- If a helper method is broad in scope, such as a general WPF helper like `FindAncestor()`, first verify whether an equivalent already exists. If not, place it in the narrowest suitable shared library among `TombLib`, `TombLib.Scripting`, `TombLib.WPF` and `DarkUI.WPF` rather than adding it to a feature-local helper class.

## User Interface Implementation

- For WinForms-based workflows, maintain the existing Visual Studio module pair for each control or unit: `.cs` and `.Designer.cs`.
- For existing WinForms-based `DarkUI` controls and containers, prefer to use existing WinForms-based `DarkUI` controls.
- For new controls and containers with complex logic, or where WinForms may not perform fast enough, prefer `DarkUI.WPF` framework. Use `GeometryIOSettingsWindow` as a reference.
- For new WPF views and view models, use `GeometryIOSettingsWindow` as the reference for structure, localization and service usage patterns.
- When writing WPF UI, prioritize localization and the existing localization infrastructure from `TombLib.WPF`.
- Creating new generic WPF controls should be delegated to `DarkUI.WPF`.
- For new controls and containers with complex logic, or where WinForms may not perform fast enough, prefer `DarkUI.WPF`.
- Use `CommunityToolkit` functionality where possible.

## Performance

- For 3D rendering controls, prefer more performant approaches and locally cache frequently used data within the function scope whenever possible.
- Avoid scenarios where bulk data updates may cause event floods, as the project relies heavily on event subscriptions across multiple controls and sub-controls.
- Use `Parallel` for bulk operations to maximize performance. Avoid using it in thread-unsafe contexts or when operating on serial data sets.
- Use `Parallel` for bulk operations to maximize performance. Avoid using it in thread-unsafe contexts or when operating on serial data sets.
14 changes: 0 additions & 14 deletions DarkUI/DarkUI.WPF.Demo/DarkUI.WPF.Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,9 @@

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<UseWindowsForms>True</UseWindowsForms>
<Configurations>Debug;Release</Configurations>
<Platforms>x64;x86</Platforms>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)'=='Release'">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='x64'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='x86'">
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>

<ItemGroup>
Expand Down
18 changes: 0 additions & 18 deletions DarkUI/DarkUI.WPF/DarkUI.WPF.csproj
Original file line number Diff line number Diff line change
@@ -1,26 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<Configurations>Debug;Release</Configurations>
<Platforms>x64;x86</Platforms>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<OutputPath>..\..\Build ($(Platform))\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<OutputPath>..\..\BuildRelease ($(Platform))\</OutputPath>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='x64'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='x86'">
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>

<ItemGroup>
Expand Down
19 changes: 0 additions & 19 deletions DarkUI/DarkUI/DarkUI.csproj
Original file line number Diff line number Diff line change
@@ -1,26 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<OutputType>Library</OutputType>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
<Configurations>Debug;Release</Configurations>
<Platforms>x64;x86</Platforms>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<OutputPath>..\..\Build ($(Platform))\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<OutputPath>..\..\BuildRelease ($(Platform))\</OutputPath>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='x64'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='x86'">
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Compile Update="Controls\DarkCheckBox.cs">
Expand Down
2 changes: 2 additions & 0 deletions DarkUI/DarkUI/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
Expand All @@ -12,6 +13,7 @@
[assembly: AssemblyCopyright("Copyright © Robin Perris")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: SupportedOSPlatform("windows")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
Expand Down
14 changes: 0 additions & 14 deletions DarkUI/Example/Example.csproj
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<OutputType>WinExe</OutputType>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
<Configurations>Debug;Release</Configurations>
<Platforms>x64;x86</Platforms>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='x64'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='x86'">
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Compile Update="Forms\Docking\DockDocument.cs">
Expand Down
2 changes: 2 additions & 0 deletions DarkUI/Example/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
Expand All @@ -12,6 +13,7 @@
[assembly: AssemblyCopyright("Copyright © Robin Perris")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: SupportedOSPlatform("windows")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
Expand Down
Loading