Skip to content

Project System

abbaye edited this page Mar 19, 2026 · 2 revisions

Project System

WpfHexEditor.ProjectSystem manages solutions, projects, and per-file state persistence. Three solution formats are supported via pluggable ISolutionLoader plugins.


Architecture

flowchart TD
    App["WpfHexEditor.App\nMainWindow.ProjectSystem.cs"]
    SM["SolutionManager\nopen · close · create · save"]
    Loaders["ISolutionLoader plugins\n(sorted by Priority)"]
    WH["SolutionLoader.WH (priority 95)\n.whsln / .whproj"]
    VS["SolutionLoader.VS (priority 90)\n.sln / .csproj (4 templates)"]
    Folder["SolutionLoader.Folder (priority 85)\n.whfolder JSON marker + gitignore"]
    Sol["ISolution / SolutionDto\n.whsln JSON file"]
    User["SolutionUserDto\n.whsln.user sidecar\n(machine-local state)"]
    Items["IProjectItem\nfiles + virtual folders"]
    Migrate["MigrationPipeline\nIFormatMigrator chain\n.v{N}.bak backup"]
    Persist["IEditorPersistable\nbookmarks · scroll · encoding\nunsaved edits (.whchg)"]
    Build["WpfHexEditor.BuildSystem\nIBuildSystem + IBuildAdapter"]

    App --> SM
    SM --> Loaders
    Loaders --> WH
    Loaders --> VS
    Loaders --> Folder
    WH --> Sol
    Sol --> User
    Sol --> Items
    SM --> Migrate
    Items --> Persist
    SM --> Build
Loading

Supported Solution Formats

Loader Plugin Priority Handles Notes
SolutionLoader.WH 95 .whsln / .whproj Native format — full feature set
SolutionLoader.VS 90 .sln / .csproj Read Visual Studio solutions; 4 project templates
SolutionLoader.Folder 85 .whfolder JSON marker Open-folder mode (VS Code–like), gitignore-aware

SolutionManager iterates loaders in descending priority order, calling CanLoad(path) until one accepts.

ISolutionLoader contract

public interface ISolutionLoader : IWpfHexEditorPluginV2
{
    int Priority { get; }
    bool CanLoad(string path);
    Task<ISolution> LoadAsync(string path, CancellationToken ct);
    Task SaveAsync(ISolution solution, CancellationToken ct);
}

File Formats

Native format (.whsln / .whproj)

File Format Description
*.whsln JSON Solution root — references projects, solution folders
*.whproj JSON Project — references files, virtual folders, item metadata
*.whsln.user JSON Machine-local sidecar — tree expand state, recent tabs, layout hints
*.whchg Binary Companion to a binary file — stores pending in-memory edits

All formats carry a Version integer field used by MigrationPipeline.

Open-Folder mode (.whfolder)

A minimal JSON marker placed at the root of the folder:

{
  "version": 1,
  "name": "MyProject",
  "excludes": ["bin/", "obj/", ".vs/"]
}

FolderFileEnumerator walks the directory tree, applying:

  • .gitignore rules (GitIgnoreFilter)
  • Built-in exclusions (bin/, obj/, node_modules/, .vs/)

FolderFileWatcher monitors for external changes with a 500 ms debounce and refreshes the Solution Explorer automatically.


Solution / Project Model

ISolution
├── string Name
├── string FilePath
├── int SourceFormatVersion
├── bool IsReadOnlyFormat          ← true when file cannot be upgraded
├── IReadOnlyList<IProject> Projects
└── IReadOnlyList<ISolutionFolder> Folders

IProject
├── string Name
├── string ProjectFilePath
└── IReadOnlyList<IProjectItem> Items

IProjectItem
├── string Id                      ← stable GUID, used as ContentId key
├── string Name
├── string RelativePath            ← relative to .whproj dir
├── bool IsVirtual                 ← virtual (no physical file)
└── Dictionary<string,string> Metadata  ← editor prefs, ActiveEditorId…

SolutionManager API

ISolutionManager sm = serviceProvider.GetRequiredService<ISolutionManager>();

// Open — loader is auto-selected by priority + CanLoad()
ISolution sol = await sm.OpenAsync(@"C:\Projects\MySolution\MySolution.whsln");
ISolution sol = await sm.OpenAsync(@"C:\Projects\MySolution.sln");  // VS format
ISolution sol = await sm.OpenFolderAsync(@"C:\Projects\MyFolder");  // open-folder

// Create a new blank solution (native format)
ISolution sol = await sm.CreateAsync("MySolution", @"C:\Projects\MySolution");

// Add a project
IProject proj = await sm.AddProjectAsync(sol, "MyProject", @"C:\Projects\MyProject");

// Add a file to a project
IProjectItem item = await sm.AddItemAsync(proj, @"C:\Data\firmware.bin");

// Save everything
await sm.SaveAsync(sol);

// Close
await sm.CloseAsync(sol);

Migration Pipeline

When a solution or project file has an older Version, MigrationPipeline upgrades it automatically:

flowchart LR
    Open["Open .whsln"] --> Check{"Version ==\nCurrentVersion?"}
    Check -->|Yes| Load["Load directly"]
    Check -->|No| Backup["Create .v{N}.bak"]
    Backup --> Run["Run IFormatMigrator chain\n(v1→v2→v3…)"]
    Run --> Update["Set Version = CurrentVersion"]
    Update --> Save["Save upgraded file"]
    Save --> Load
Loading

Implementing a Migrator

public sealed class V1ToV2Migrator : IFormatMigrator
{
    public int FromVersion => 1;
    public int ToVersion   => 2;

    public void Migrate(SolutionDto dto)
    {
        // e.g. move layout JSON from .whsln to .whsln.user
        dto.DeprecatedLayoutJson = null;
    }
}

Register in MigrationPipeline:

MigrationPipeline.Register(new V1ToV2Migrator());

ISolution.IsReadOnlyFormat is true when the file cannot be written (e.g. opened from a read-only location). The IDE will warn the user and block save.


Build System Integration

When a solution is opened, MainWindow.Build.cs wires the build system:

flowchart LR
    Build["Ctrl+Shift+B"] --> BS["IBuildSystem.BuildAsync()"]
    BS --> Adapter["IBuildAdapter.BuildAsync()\n(Build.MSBuild plugin)"]
    Adapter --> CLI["dotnet build ..."]
    CLI --> Parser["BuildOutputParser\nDiagnosticEntry[]"]
    Parser --> EP["IErrorPanelService.PostDiagnostic()"]
    Parser --> OP["IOutputService.WriteLine()"]
Loading

Build configuration management:

// Get available configurations (Debug/Release/custom)
var configs = buildSystem.GetConfigurations(project);

// Set active configuration
buildSystem.SetActiveConfiguration(project, "Release");

// Trigger build
await buildSystem.BuildAsync(project, activeConfig, progress, ct);

Per-File State Persistence (IEditorPersistable)

Editors that implement IEditorPersistable can round-trip their state through the project system:

public interface IEditorPersistable
{
    EditorConfigDto  GetEditorConfig();
    void             ApplyEditorConfig(EditorConfigDto config);

    byte[]?          GetUnsavedModifications();
    void             ApplyUnsavedModifications(byte[] data);

    ChangesetSnapshot GetChangesetSnapshot();
    void              ApplyChangeset(ChangesetDto changeset);
    void              MarkChangesetSaved();

    IReadOnlyList<BookmarkDto>? GetBookmarks();
    void ApplyBookmarks(IReadOnlyList<BookmarkDto> bookmarks);
}

EditorConfigDto.Extra (dictionary) stores editor-specific keys:

  • xd.layout — XAML Designer split layout mode
  • ce.foldDepth — Code Editor folding depth
  • he.bytesPerLine — HexEditor bytes-per-line preference

What is persisted per file:

Data Storage Notes
Bytes/line, encoding IProjectItem.Metadata Restored on next open
Bookmarks IProjectItem.Metadata Named offsets
Scroll position, caret EditorConfigDto Session restore
Pending byte edits .whchg companion file Survives IDE restart
Editor-specific prefs EditorConfigDto.Extra Extensible key-value

Virtual Folders

Projects support both physical folders (mirroring a directory on disk) and virtual folders (grouping only inside the IDE):

// Add a virtual folder
IProjectFolder folder = await sm.AddVirtualFolderAsync(proj, "Headers");

// Add a file under it
IProjectItem item = await sm.AddItemAsync(folder, @"C:\Data\header.bin");

Virtual folders have RelativePath = null in the DTO.


See Also

Navigation

Getting Started

IDE Documentation

HexEditor Control

Advanced

Development


v0.6.0 Highlights

  • WpfHexEditor.Shell (renamed from Docking.Wpf)
  • Code Editor ~90% (NavBar, Inline Hints, Quick Info)
  • XAML Visual Designer ~70%
  • Shared UndoEngine (coalescing + transactions)
  • VS .sln/.csproj + Open-Folder mode
  • Build System (IBuildAdapter + MSBuild plugin)
  • Assembly Explorer + NuGet Solution Manager
  • Synalysis Grammar Support (IGrammarProvider)

Links

Clone this wiki locally