omnidiff 0.2.0

Fast, robust, syntax-aware code diffing using tree-sitter ASTs
Documentation
/*  This file is part of the OmniDiff code diffing tool.
 *
 *  Copyright (C) 2026 Marko Ivankovic
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

use crate::code::ASTMetadata;

use super::common::{
    ContainmentCtx, DeltaTable, ForestDist, PostorderIndexer, UnitCostModel, forest_dist,
};

/// Fills `delta[(pre_before, pre_after)]` with the subtree-to-subtree edit distance for every
/// keyroot pair, by the classic Zhang-Shasha keyroot decomposition (no single-path functions).
///
/// Keyroots run in ascending postorder on both sides, so every `delta` lookup `forest_dist`
/// makes has already been filled by an earlier pair.
pub(crate) fn compute_delta_zhang_shasha(
    before: &PostorderIndexer,
    after: &PostorderIndexer,
    before_meta: &ASTMetadata,
    after_meta: &ASTMetadata,
    cost_model: &UnitCostModel,
    containment: Option<&ContainmentCtx>,
) -> DeltaTable {
    let mut delta = DeltaTable::new(before.size.max(1), after.size.max(1));
    if before.size == 0 || after.size == 0 {
        return delta;
    }

    let mut before_keyroots = before.keyroots.clone();
    before_keyroots.sort_by_key(|&pre| before.pre_to_post[pre]);
    let mut after_keyroots = after.keyroots.clone();
    after_keyroots.sort_by_key(|&pre| after.pre_to_post[pre]);

    let mut forestdist = ForestDist::new(before.size + 1, after.size + 1, 0);

    for &kr1_pre in &before_keyroots {
        let kr1_boundary = before.pre_to_post[kr1_pre] + 1;
        for &kr2_pre in &after_keyroots {
            let kr2_boundary = after.pre_to_post[kr2_pre] + 1;
            forest_dist(
                before,
                after,
                before_meta,
                after_meta,
                cost_model,
                containment,
                &mut delta,
                kr1_boundary,
                kr2_boundary,
                &mut forestdist,
                true,
            );
        }
    }

    delta
}