semdiff-differ-binary 0.4.2

Binary diff calculator and reporters for semdiff.
Documentation
<style>
    .binary-detail {
        width: 100%;
        font-size: 1rem;
        font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
        display: grid;
        column-gap: 0;
        row-gap: 0;
        {% if detail.is_multicolumn() %}
        grid-template-columns: max-content minmax(0, 1fr) max-content minmax(0, 1fr);
        {% else %}
        grid-template-columns: max-content minmax(0, 1fr);
        {% endif %}
    }

    .binary-detail-row {
        display: grid;
        grid-template-columns: subgrid;
        grid-column: 1 / -1;
    }

    .binary-detail-cell {
        border-left: 1px solid var(--status-neutral-border);
        border-right: 1px solid var(--status-neutral-border);
        padding: 0 0.5rem;
        vertical-align: top;
        line-height: 1.6;
        user-select: none;
        box-sizing: border-box;
        grid-row: 1;
    }

    .binary-detail-header .binary-detail-cell {
        background: var(--status-neutral-bg);
        text-align: left;
        font-weight: 600;
    }

    .binary-detail-cell.offset {
        text-align: right;
        color: var(--status-neutral-text-subtle);
        background: var(--status-neutral-bg);
    }

    .binary-detail-cell.same {
        background: var(--status-neutral-bg);
    }

    .binary-detail-cell.added {
        background: var(--status-added-bg);
    }

    .binary-detail-cell.deleted {
        background: var(--status-deleted-bg);
    }

    .binary-detail-cell.empty {
        background: var(--status-neutral-surface);
    }

    .binary-detail-cell .cell-binary {
        display: block;
        white-space: pre-wrap;
        word-break: break-word;
        user-select: text;
    }

    .binary-detail.select-left .cell-right .cell-binary,
    .binary-detail.select-right .cell-left .cell-binary {
        user-select: none;
    }

    {% if detail.is_multicolumn() %}
    @media (max-width: 1024px) {
        .binary-detail {
            grid-template-columns: max-content max-content minmax(0, 1fr);
        }

        .binary-detail .binary-detail-cell.offset.expected {
            grid-column: 1;
        }

        .binary-detail .binary-detail-cell.offset.actual {
            grid-column: 2;
        }

        .binary-detail .binary-detail-header .binary-detail-cell.header-label {
            grid-column: 3;
            color: transparent;
        }

        .binary-detail .cell-left,
        .binary-detail .cell-right {
            grid-column: 3;
            min-width: 0;
        }

        .binary-detail .cell-right.same {
            display: none;
        }

        .binary-detail .empty {
            display: none;
        }
    }
    {% endif %}
</style>
<div class="binary-detail">
    <div class="binary-detail-row binary-detail-header">
        {% match detail %}
        {% when BinaryDetailBody::Diff with { .. } %}
        <div class="binary-detail-cell offset">Offset</div>
        <div class="binary-detail-cell header-label">expected</div>
        <div class="binary-detail-cell offset">Offset</div>
        <div class="binary-detail-cell header-label">actual</div>
        {% when BinaryDetailBody::Single with { label, .. } %}
        <div class="binary-detail-cell offset">Offset</div>
        <div class="binary-detail-cell">{{ label }}</div>
        {% endmatch %}
    </div>
    {% match detail %}
    {% when BinaryDetailBody::Diff with { expected, actual, diff } %}
    {% let mut expected_index = IncrementUsize::new() %}
    {% let mut actual_index = IncrementUsize::new() %}
    {% for (tag, chunk) in self::diff_iter(diff, expected, actual) %}
    {% match tag %}
    {% when ChangeTag::Equal %}
    {% for line in chunk.chunks(16) %}
    <div class="binary-detail-row">
        <div class="binary-detail-cell offset expected">{{ format_args!("{:08X}", expected_index.incr(line.len())) }}
        </div>
        <div class="binary-detail-cell cell-left same"><span class="cell-binary">{{ self::format_line(line) }}</span>
        </div>
        <div class="binary-detail-cell offset actual">{{ format_args!("{:08X}", actual_index.incr(line.len())) }}</div>
        <div class="binary-detail-cell cell-right same"><span class="cell-binary">{{ self::format_line(line) }}</span>
        </div>
    </div>
    {% endfor %}
    {% when ChangeTag::Insert %}
    {% for line in chunk.chunks(16) %}
    <div class="binary-detail-row">
        <div class="binary-detail-cell offset expected"></div>
        <div class="binary-detail-cell cell-left empty"></div>
        <div class="binary-detail-cell offset actual">{{ format_args!("{:08X}", actual_index.incr(line.len())) }}</div>
        <div class="binary-detail-cell cell-right added"><span class="cell-binary">{{ self::format_line(line) }}</span>
        </div>
    </div>
    {% endfor %}
    {% when ChangeTag::Delete %}
    {% for line in chunk.chunks(16) %}
    <div class="binary-detail-row">
        <div class="binary-detail-cell offset expected">{{ format_args!("{:08X}", expected_index.incr(line.len())) }}
        </div>
        <div class="binary-detail-cell cell-left deleted"><span class="cell-binary">{{ self::format_line(line) }}</span>
        </div>
        <div class="binary-detail-cell offset actual"></div>
        <div class="binary-detail-cell cell-right empty"></div>
    </div>
    {% endfor %}
    {% endmatch %}
    {% endfor %}
    {% when BinaryDetailBody::Single with { label, body } %}
    {% for (i, line) in body.chunks(16).enumerate() %}
    <div class="binary-detail-row">
        <div class="binary-detail-cell offset">{{ format_args!("{:08X}", i * 16) }}</div>
        <div class="binary-detail-cell cell-left {{ label }}"><span
                class="cell-binary">{{ self::format_line(line) }}</span></div>
    </div>
    {% endfor %}
    {#
    {% for (i, line) in body.lines().enumerate() %}
    <div class="binary-detail-row">
        <div class="binary-detail-cell offset">{{ i + 1 }}</div>
        <div class="binary-detail-cell cell-left {{ label }}"><span class="cell-binary">{{ line }}</span></div>
    </div>
    {% endfor %}
    #}
    {% endmatch %}
</div>
<script>
    (() => {
        const detail = document.querySelector(".binary-detail");
        if (!detail) {
            return;
        }
        const clearSelectionMode = () => {
            detail.classList.remove("select-left", "select-right");
        };
        const clearSelectionRanges = () => {
            const selection = window.getSelection();
            if (selection) {
                selection.removeAllRanges();
            }
        };
        let pointerDown = false;
        let dragged = false;
        let downSide = null;

        document.addEventListener(
            "mousedown",
            (event) => {
                if (!event.target.closest(".binary-detail")) {
                    clearSelectionMode();
                }
            },
            true
        );
        detail.addEventListener("mousedown", (event) => {
            const cell = event.target.closest(".binary-detail-cell");
            if (!cell) {
                clearSelectionMode();
                clearSelectionRanges();
                return;
            }
            pointerDown = true;
            dragged = false;
            if (cell.classList.contains("cell-left")) {
                downSide = "left";
            } else if (cell.classList.contains("cell-right")) {
                downSide = "right";
            } else {
                downSide = null;
            }
            clearSelectionMode();
            clearSelectionRanges();
            if (downSide === "left") {
                detail.classList.add("select-left");
            } else if (downSide === "right") {
                detail.classList.add("select-right");
            }
        });
        detail.addEventListener("mousemove", (event) => {
            if (!pointerDown || dragged) {
                return;
            }
            if (event.buttons !== 0) {
                dragged = true;
            }
        });
        document.addEventListener(
            "mouseup",
            () => {
                if (!pointerDown) {
                    return;
                }
                pointerDown = false;
                if (!dragged && downSide) {
                    clearSelectionRanges();
                }
                dragged = false;
                downSide = null;
            },
            true
        );
    })();
</script>