file-viewer
Opening and editing multi-GB files without the app falling over.
github.com/dhruvch1244/File-Viewer ↗A boundary fast enough that the tests actually get run
FileViewer.Core has zero reference to WPF. Its test suite, FileViewer.Core.Tests, runs as plain xUnit against a plain library — no WPF App, no window, no UI harness. That speed isn't incidental: it's the direct, measurable consequence of the boundary being real rather than a naming convention, and it's also what makes the tests something people actually run instead of skip under deadline pressure.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="FileViewer.Core.Tests" />
</ItemGroup>
</Project>Edits as an overlay, not a mutation
Cell edits, row add/duplicate/delete, and bulk delete are all represented as entries in an ordered overlay over the original file contents — the original stays untouched in memory for the whole session. The view renders original + overlay, never a mutated copy. Undo is "remove the last overlay entry," not "compute and apply an inverse" — which is why it's structurally guaranteed to work rather than best-effort: there's no edit that lacks an inverse, because the inverse is simply not applying it. The overlay only gets materialized into a new file at an explicit Save/Export — closing without saving is trivially correct, since it's just discarding the overlay.
public void Undo()
{
lock (_gate)
{
if (_rowOps.Count == 0) return;
RowOp op = _rowOps[^1];
_rowOps.RemoveAt(_rowOps.Count - 1);
ApplyReversalNoLock(op);
}
}Scale as a day-one constraint, not a later optimization
The app targets files up to 2 GB and stays responsive by indexing on load rather than holding the whole file in memory or re-scanning it per interaction — the kind of decision that has to be made in the data-access design from the start, because retrofitting indexing onto code written as if files were small is a rewrite, not a patch.
/// Owns a read-only handle onto a DIF file plus the unmanaged row index and sort-key arrays built
/// by <see cref="FileIndexer"/>. The source file is never mutated — <see cref="GetRowBytes"/> reads
/// a row's bytes on demand via <see cref="RandomAccess"/> rather than through a persistent
/// memory-mapped view of the whole file: mapping the entire file into one contiguous view up front
/// is an all-or-nothing commitment that can fail on Windows (ERROR_NOT_ENOUGH_MEMORY) depending on
/// how much memory/page-file space happens to be free on the machine at that moment — a real failure
/// this project hit in practice, independent of the file's actual size (see git history). Reading
/// each row on demand instead means opening the file never requires reserving space for the whole
/// thing at once.