clankerdiff-ratatui 0.1.7

Embeddable Ratatui diff review widget
Documentation
#![allow(missing_docs)]

use clankerdiff_core::{
    ContentProjection, DiffDocument, DiffPresentation, PresentationOptions,
    testing::DocumentBuilder,
};
use clankerdiff_ratatui::KeyCode;
use clankerdiff_ratatui::{
    DiffPreviewOptions, DiffPreviewState, DiffReviewState, render_diff_preview,
};
use clankerdiff_syntax::SyntaxHighlighter;
use clankerdiff_theme::ReviewTheme;
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use std::{hint::black_box, sync::Arc};

#[path = "../tests/support/mod.rs"]
mod support;

use support::{ReviewHarness, key};

fn large_document(rows: usize) -> Arc<DiffDocument> {
    DocumentBuilder::new()
        .generated("src/large.rs", rows)
        .build()
}

fn many_file_document(files: usize, rows_per_file: usize) -> Arc<DiffDocument> {
    DocumentBuilder::new()
        .generated_files(files, rows_per_file)
        .build()
}

const SCROLL_STEPS: u64 = 100;
const PAGE_STEPS: u64 = 10;
const FILE_SWITCHES: u64 = 50;

#[allow(clippy::format_collect)]
fn presentation_creation(criterion: &mut Criterion) {
    let mut group = criterion.benchmark_group("presentation_creation");
    group.sample_size(20);
    let theme = clankerdiff_theme::ReviewTheme::default();
    for rows in [1_000, 10_000, 100_000] {
        let document = large_document(rows);
        group.bench_with_input(BenchmarkId::from_parameter(rows), &rows, |bencher, _| {
            bencher
                .iter(|| DiffReviewState::with_theme(black_box(document.clone()), theme.clone()));
        });
    }

    let old = (1..=100_000)
        .map(|line| format!("let value_{line} = {line};\n"))
        .collect::<String>();
    let new = old.replacen("let value_50000 = 50000;", "let value_50000 = 0;", 1);
    let document = DocumentBuilder::new()
        .changed_with_hunk_window("src/full.rs", &old, &new, 49_997..=50_003)
        .build();
    let mut projection = ContentProjection::default();
    projection.set_full_file(document.files[0].path.clone(), true);
    group.bench_function("full-file-100000", |bencher| {
        bencher.iter(|| {
            DiffPresentation::with_projection(
                black_box(document.clone()),
                PresentationOptions::default(),
                black_box(&projection),
            )
        });
    });
    group.finish();
}

fn rendering(criterion: &mut Criterion) {
    let document = large_document(10_000);
    let mut group = criterion.benchmark_group("visible_rendering_10k");
    group.sample_size(20);

    group.bench_function("cold_unified_80x24", |bencher| {
        bencher.iter_batched(
            || ReviewHarness::new(document.clone(), 80, 24),
            |mut harness| black_box(harness.draw()),
            BatchSize::SmallInput,
        );
    });

    let mut warm = ReviewHarness::new(document.clone(), 80, 24);
    warm.draw();
    group.bench_function("warm_unified_80x24", |bencher| {
        bencher.iter(|| black_box(warm.draw()));
    });

    let mut split = ReviewHarness::new(document.clone(), 140, 40);
    split.draw();
    group.bench_function("warm_split_140x40", |bencher| {
        bencher.iter(|| black_box(split.draw()));
    });

    let mut cached_navigation = ReviewHarness::new(document.clone(), 100, 24);
    cached_navigation.draw();
    cached_navigation.input(key(KeyCode::Enter));
    cached_navigation.draw();
    let mut down = true;
    group.bench_function("navigate_and_draw_100x24", |bencher| {
        bencher.iter(|| {
            let code = if down { KeyCode::Down } else { KeyCode::Up };
            down = !down;
            black_box(cached_navigation.input_and_draw(key(code)))
        });
    });
    group.finish();

    navigation(criterion, &document);
}

fn navigation(criterion: &mut Criterion, document: &Arc<DiffDocument>) {
    let mut group = criterion.benchmark_group("navigation_10k");
    group.sample_size(20);

    group.throughput(Throughput::Elements(SCROLL_STEPS));
    group.bench_function("scroll_100_new_rows_100x24", |bencher| {
        bencher.iter_batched(
            || patch_harness(document.clone(), 100, 24),
            |mut harness| {
                for _ in 0..SCROLL_STEPS {
                    black_box(harness.input_and_draw(key(KeyCode::Down)));
                }
            },
            BatchSize::SmallInput,
        );
    });

    group.throughput(Throughput::Elements(PAGE_STEPS));
    group.bench_function("page_down_10_new_viewports_100x24", |bencher| {
        bencher.iter_batched(
            || patch_harness(document.clone(), 100, 24),
            |mut harness| {
                for _ in 0..PAGE_STEPS {
                    black_box(harness.input_and_draw(key(KeyCode::PageDown)));
                }
            },
            BatchSize::SmallInput,
        );
    });

    let files = many_file_document(100, 100);
    group.throughput(Throughput::Elements(FILE_SWITCHES));
    group.bench_function("switch_50_files_100x24", |bencher| {
        bencher.iter_batched(
            || {
                let mut harness = ReviewHarness::new(files.clone(), 100, 24);
                harness.draw();
                harness
            },
            |mut harness| {
                for _ in 0..FILE_SWITCHES {
                    black_box(harness.input_and_draw(key(KeyCode::Down)));
                }
            },
            BatchSize::SmallInput,
        );
    });

    group.throughput(Throughput::Elements(2));
    group.bench_function("cycle_split_and_unified_140x40", |bencher| {
        bencher.iter_batched(
            || patch_harness(document.clone(), 140, 40),
            |mut harness| {
                black_box(harness.input_and_draw(key(KeyCode::Char('v'))));
                black_box(harness.input_and_draw(key(KeyCode::Char('v'))));
            },
            BatchSize::SmallInput,
        );
    });

    group.finish();
}

fn patch_harness(document: Arc<DiffDocument>, width: u16, height: u16) -> ReviewHarness {
    let mut harness = ReviewHarness::new(document, width, height);
    harness.draw();
    harness.input(key(KeyCode::Enter));
    harness.draw();
    harness
}

fn preview_ownership(criterion: &mut Criterion) {
    let document = large_document(10_000);
    let file = &document.files[0];
    let theme = ReviewTheme::default();
    let mut highlighter = SyntaxHighlighter::default();
    let options = DiffPreviewOptions::default();
    let mut group = criterion.benchmark_group("preview_10k");
    group.bench_function("input_clone", |bencher| {
        bencher.iter(|| black_box(file.clone()));
    });
    group.bench_function("unchanged_owned_render", |bencher| {
        bencher.iter(|| {
            black_box(render_diff_preview(
                file.clone(),
                80,
                &theme,
                &mut highlighter,
                options,
            ))
        });
    });
    let mut state = DiffPreviewState::new(file.clone());
    state.render(80, &theme, &mut highlighter, options);
    group.bench_function("unchanged_shared_render", |bencher| {
        bencher.iter(|| black_box(state.render(80, &theme, &mut highlighter, options)));
    });
    group.finish();
}

criterion_group!(benches, presentation_creation, rendering, preview_ownership);
criterion_main!(benches);