Skip to content

Editor Registry

abbaye edited this page Mar 19, 2026 · 2 revisions

Editor Registry

EditorRegistry (in WpfHexEditor.Editor.Core) is the central catalog of document editors. It resolves which editor opens a given file — with format-aware preferred-editor selection and fallback logic.


Architecture

flowchart TD
    App["MainWindow.Editors.cs"]
    Registry["EditorRegistry\nIEditorRegistry"]
    Catalog["EmbeddedFormatCatalog\n.whfmt definitions\npreferredEditor key"]
    Factory["IEditorFactory\nDescriptor · CanOpen() · Create()"]
    Editor["IDocumentEditor\n(UserControl / FrameworkElement)"]
    Persist["IEditorPersistable\n(optional)\nbookmarks · scroll · encoding"]
    Diag["IDiagnosticSource\n(optional)\nerrors → Error Panel"]
    UndoEng["UndoEngine (Editor.Core.Undo)\ncoalescing · transactions · save-point"]

    App --> Registry
    Registry --> Catalog
    Registry --> Factory
    Factory --> Editor
    Editor -.->|implements| Persist
    Editor -.->|implements| Diag
    Editor --> UndoEng
Loading

IDocumentEditor Contract

Every editor implements IDocumentEditor. The host (docking system, main window) drives all editors through this interface uniformly:

public interface IDocumentEditor
{
    // State
    bool IsDirty    { get; }
    bool CanUndo    { get; }
    bool CanRedo    { get; }
    bool IsReadOnly { get; set; }
    string Title    { get; }
    bool IsBusy     { get; }

    // Undo engine stats (v0.6.0 — default interface members)
    int UndoCount   => 0;
    int RedoCount   => 0;

    // Bindable commands (bind to MenuItem.Command, toolbar buttons…)
    ICommand? UndoCommand      { get; }
    ICommand? RedoCommand      { get; }
    ICommand? SaveCommand      { get; }
    ICommand? CopyCommand      { get; }
    ICommand? CutCommand       { get; }
    ICommand? PasteCommand     { get; }
    ICommand? DeleteCommand    { get; }
    ICommand? SelectAllCommand { get; }

    // Methods
    void Undo();   void Redo();
    void Save();   Task SaveAsync(CancellationToken ct = default);
    Task SaveAsAsync(string filePath, CancellationToken ct = default);
    void Copy();   void Cut();  void Paste();
    void Delete(); void SelectAll(); void Close();
    void CancelOperation();

    // Events
    event EventHandler?          ModifiedChanged;
    event EventHandler?          CanUndoChanged;
    event EventHandler?          CanRedoChanged;
    event EventHandler<string>?  TitleChanged;
    event EventHandler<string>?  StatusMessage;
    event EventHandler<string>?  OutputMessage;
    event EventHandler?          SelectionChanged;
    event EventHandler<DocumentOperationEventArgs>?          OperationStarted;
    event EventHandler<DocumentOperationEventArgs>?          OperationProgress;
    event EventHandler<DocumentOperationCompletedEventArgs>? OperationCompleted;
}

IEditorFactory Contract

public interface IEditorFactory
{
    IEditorDescriptor Descriptor { get; }  // id, display name, supported extensions
    bool CanOpen(string filePath);         // extension / magic-byte check
    IDocumentEditor Create();              // returns a new blank editor instance
}

The returned IDocumentEditor must also be a FrameworkElement — the host casts after Create().


Format-Aware Selection

EditorRegistry.FindFactory(filePath, preferredEditorId?) uses a three-step resolution:

flowchart TD
    Call["FindFactory(filePath, preferredId?)"]
    Step1{"preferredId\nprovided?"}
    Step2{"EmbeddedFormatCatalog\nhas preferredEditor?"}
    Step3{"isTextFormat\n== true?"}
    Fallback["First factory\nwhere CanOpen() == true"]
    Hex["HexEditor (default)"]

    Call --> Step1
    Step1 -->|Yes| Preferred["Return factory\nwith matching Id"]
    Step1 -->|No| Step2
    Step2 -->|Yes| WhfmtEditor["Return factory\nnamed in .whfmt"]
    Step2 -->|No| Step3
    Step3 -->|Yes| TextEditor["Return TextEditor factory"]
    Step3 -->|No| Fallback
    Fallback -->|None found| Hex
Loading

.whfmt preferredEditor key

In a .whfmt format definition:

{
  "id": "fmt-zip",
  "name": "ZIP Archive",
  "detection": { "isTextFormat": false },
  "preferredEditor": "structure-editor"
}

Formats with preferredEditor set: ZIP, RAR, PNG, JPEG, BMP, PDF, PE_EXE, ELF, XAML.


Registering an Editor

1. Implement the factory

public sealed class MyEditorFactory : IEditorFactory
{
    public IEditorDescriptor Descriptor { get; } = new EditorDescriptor(
        id:         "my-editor",
        name:       "My Editor",
        extensions: new[] { ".myext" });

    public bool CanOpen(string filePath) =>
        Path.GetExtension(filePath).Equals(".myext", StringComparison.OrdinalIgnoreCase);

    public IDocumentEditor Create() => new MyEditorControl();
}

2. Register on startup

// In App.xaml.cs or MainWindow constructor
editorRegistry.Register(new MyEditorFactory());

3. Assign preferredEditor in .whfmt (optional)

{
  "id": "fmt-myext",
  "preferredEditor": "my-editor"
}

Optional Interfaces

IEditorPersistable

Implement to let the project system save/restore bookmarks, scroll position, and pending edits.

public class MyEditorControl : UserControl, IDocumentEditor, IEditorPersistable
{
    public EditorConfigDto GetEditorConfig()         => new() { /* … */ };
    public void ApplyEditorConfig(EditorConfigDto c) { /* … */ }
    public byte[]? GetUnsavedModifications()         => null;
    public void ApplyUnsavedModifications(byte[] d)  { /* … */ }
    // …bookmarks, changeset…
}

EditorConfigDto.Extra — extensible dictionary for editor-specific settings (e.g. xd.layout for XAML Designer layout mode).

IDiagnosticSource

Implement to push errors/warnings directly to the Error Panel:

public class MyEditorControl : UserControl, IDocumentEditor, IDiagnosticSource
{
    public event EventHandler<DiagnosticEventArgs>? DiagnosticRaised;

    private void ReportError(string message) =>
        DiagnosticRaised?.Invoke(this, new DiagnosticEventArgs(
            DiagnosticSeverity.Error, message, offset: 0));
}

IFileValidator

Implement on a factory to perform background validation before the editor opens:

public sealed class MyEditorFactory : IEditorFactory, IFileValidator
{
    public async Task<ValidationResult> ValidateAsync(string filePath, CancellationToken ct)
    {
        var info = new FileInfo(filePath);
        if (info.Length == 0)
            return ValidationResult.Error("File is empty.");
        return ValidationResult.Ok();
    }
}

Querying the Registry

// Find best factory for a file
IEditorFactory? factory = editorRegistry.FindFactory(@"C:\data\image.png");

// Find with explicit preference
IEditorFactory? factory = editorRegistry.FindFactory(@"C:\data\image.png", "hex-editor");

// List all registered factories
IReadOnlyList<IEditorFactory> all = editorRegistry.GetAll();

Built-In Editors

Factory Id Editor Progress Handles
hex-editor HexEditor ~75% Binary / unknown formats
code-editor CodeEditor ~90% Source code (55+ languages), XAML, config files
xaml-designer XamlDesigner ~70% .xaml — visual designer mode
text-editor TextEditor ~50% Text files, syntax highlighting
tbl-editor TblEditor ~60% .tbl ROM character tables
json-editor JsonEditor ~65% .json with live validation
structure-editor StructureEditor ~30% ZIP, archives, structured binary
image-viewer ImageViewer ~30% PNG, JPEG, BMP, GIF…
entropy-viewer EntropyViewer ~25% Entropy graph for binary analysis
diff-viewer DiffViewer ~20% Binary diff

Shared Undo Engine

All editors based on WpfHexEditor.Editor.Core share the UndoEngine:

// Access from any IDocumentEditor that wraps UndoEngine
if (editor is ICodeEditorHost ceh)
{
    int undoCount = ceh.UndoEngine.UndoCount;
    int redoCount = ceh.UndoEngine.RedoCount;
    bool isDirty  = !ceh.UndoEngine.IsAtSavePoint;
}

The IDocumentEditor.UndoCount and RedoCount default interface members expose undo stack depth to the toolbar (dynamic "Undo (N)" header).


See Also

Navigation

Getting Started

IDE Documentation

HexEditor Control

Advanced

Development


v0.6.4.75 Highlights

  • whfmt.FileFormatCatalog v1.0.0 NuGet (cross-platform net8.0)
  • 690+ .whfmt definitions (schema v2.3)
  • Structure Editor — block DataGrid, drag-drop, validation, SmartComplete
  • WhfmtBrowser/Catalog panels — browse all embedded formats
  • AI Assistant (5 providers, 25 MCP tools)
  • Tab Groups, Document Structure, Lazy Plugin Loading
  • Window Menu + Win32 Fullscreen (F11)
  • Git Integration UI (changes, history, blame)
  • Shared Undo Engine (HexEditor ↔ CodeEditor)
  • Bracket pair colorization, sticky scroll, peek definition
  • Format detection hardening (thread-safe, crash guard)

Links

Clone this wiki locally