From d8335d7bb5e981bb21139623bff23266e536b62e Mon Sep 17 00:00:00 2001 From: Sidharth Menon Date: Mon, 5 Oct 2026 13:12:31 -0700 Subject: [PATCH] Drop structural_changes from the file record A text diff's trees already say which lines changed: a leaf's `changed` spans, and every line of a leaf with no partner on the other side. `structural_changes` repeated that as line ranges, and consumers that need per-line detail walk the trees anyway. `present` now counts only the visible changed lines that `stats.visible` needs. diffr-ts loses `StructuralChanges` and `structuralChangeCounts`. AI assistance: developed with Claude Code. Claude-Session: https://claude.ai/code/session_01Jcs63VAkAWzCT54XzdvDyb Agent-Session: 82924cb5-dcfa-42e4-abb0-ef40393249f8 Agent-Session: 01a10dbe-5dcc-74a3-a06b-f810c6d3af7e Agent-Session: 01a10e7a-1dfa-7bd1-a6f4-8449c22813e2 Agent-Session: 5700e9f2-ed1a-41d0-825c-bda83f9e8aa0 Agent-Session: 01a10f90-e4ce-70f2-84d1-26d64820eed3 Agent-Session: 01a11898-f278-7b30-b366-ea64bfc7ff5e Agent-Session: 01a118dd-0ded-7b60-a820-60e4f5797db5 --- crates/diffr-core/src/present.rs | 115 +++++----------------- crates/diffr-core/src/protocol/mod.rs | 26 ----- crates/diffr-core/src/protocol/project.rs | 2 - diffr-ts/src/contract.ts | 33 ------- tui/test/fixtures/comparison.ndjson | 4 +- 5 files changed, 26 insertions(+), 154 deletions(-) diff --git a/crates/diffr-core/src/present.rs b/crates/diffr-core/src/present.rs index 3e62a53f9..d1b87e558 100644 --- a/crates/diffr-core/src/present.rs +++ b/crates/diffr-core/src/present.rs @@ -2,7 +2,7 @@ //! lines that stay visible. use crate::hash::DftHashSet; use crate::pairing::Pairing; -use crate::protocol::{Diff, LineRange, Node, Region, Source, StructuralChanges, Visibility}; +use crate::protocol::{Diff, LineCounts, LineRange, Node, Region, Source, Visibility}; /// Run the plugins on a diff and recount what stays visible. `shape` runs /// the plugins on a text diff's sides. A hidden file runs no plugin: its @@ -27,27 +27,17 @@ pub async fn present( }), None => shape(sides).await?, }; - let coverage = change_coverage(&sides); - stats.visible = coverage.initially_visible.counts(); - Ok(Diff::Text { - sides, - stats, - structural_changes: coverage.all, - }) + stats.visible = visible_changes(&sides); + Ok(Diff::Text { sides, stats }) } Diff::Binary { sides } => Ok(Diff::Binary { sides }), } } -/// Collect complete and default-visible coverage together. A paired leaf counts -/// only lines carrying changed spans; every line of an unpaired leaf counts, -/// including blank lines. Visibility never removes lines from `all`. -struct ChangeCoverage { - all: StructuralChanges, - initially_visible: StructuralChanges, -} - -fn change_coverage(sides: &Pairing) -> ChangeCoverage { +/// Count the changed lines that start visible. A paired leaf counts only +/// lines carrying changed spans; every line of an unpaired leaf counts, +/// including blank lines. +fn visible_changes(sides: &Pairing) -> LineCounts { fn alignments(regions: &[Region], out: &mut DftHashSet) { for region in regions { match ®ion.node { @@ -62,61 +52,54 @@ fn change_coverage(sides: &Pairing) -> ChangeCoverage { regions: &[Region], other: &DftHashSet, hidden: bool, - all: &mut Vec, visible: &mut Vec, ) { for region in regions { let hidden = hidden || region.visibility.collapsed; match ®ion.node { + Node::Leaf { .. } if hidden => {} Node::Leaf { alignment_id, changed, } => { - let start = all.len(); if other.contains(alignment_id) { - all.extend(changed.iter().map(|span| [span.line, span.line + 1])); + visible.extend(changed.iter().map(|span| [span.line, span.line + 1])); } else { let lines = region.range.lines(); - all.push([lines.start, lines.end]); - } - if !hidden { - visible.extend_from_slice(&all[start..]); + visible.push([lines.start, lines.end]); } } - Node::Fold { children, .. } => collect(children, other, hidden, all, visible), + Node::Fold { children, .. } => collect(children, other, hidden, visible), } } } - fn side(source: Option<&Source>, other: Option<&Source>) -> (Vec, Vec) { + fn side(source: Option<&Source>, other: Option<&Source>) -> u32 { let mut paired = DftHashSet::default(); if let Some(other) = other { alignments(std::slice::from_ref(&other.root), &mut paired); } - let (mut all, mut visible) = (Vec::new(), Vec::new()); + let mut visible = Vec::new(); if let Some(source) = source { collect( std::slice::from_ref(&source.root), &paired, false, - &mut all, &mut visible, ); } - (coalesce(all), coalesce(visible)) + coalesce(visible) + .iter() + .map(|[start, end]| end - start) + .sum() } let (lhs, rhs) = match sides { Pairing::Both { lhs, rhs } => (Some(lhs), Some(rhs)), Pairing::LeftOnly { lhs } => (Some(lhs), None), Pairing::RightOnly { rhs } => (None, Some(rhs)), }; - let (base, visible_base) = side(lhs, rhs); - let (head, visible_head) = side(rhs, lhs); - ChangeCoverage { - all: StructuralChanges { base, head }, - initially_visible: StructuralChanges { - base: visible_base, - head: visible_head, - }, + LineCounts { + added: side(rhs, lhs), + removed: side(lhs, rhs), } } @@ -241,64 +224,14 @@ mod visible_tests { leaf(9, 9, (3, 4), &[], false), leaf(10, 8, (4, 7), &[4, 5], true), ]); - let coverage = change_coverage(&Pairing::Both { lhs, rhs }); - assert_eq!(coverage.all.head, vec![[0, 2], [4, 15]]); - assert_eq!(coverage.all.base, vec![[0, 1], [4, 7]]); - let counts = coverage.initially_visible.counts(); + let counts = visible_changes(&Pairing::Both { lhs, rhs }); assert_eq!(counts.added, 2 + 2 + 3); assert_eq!(counts.removed, 1); } #[test] - fn changing_fold_visibility_never_changes_complete_coverage() { - let lhs = source(vec![leaf(1, 7, (0, 3), &[], false)]); - let rhs = source(vec![fold( - 2, - (0, 3), - true, - vec![fold( - 3, - (0, 3), - false, - vec![leaf(4, 7, (0, 3), &[2, 0, 0], false)], - )], - )]); - let mut sides = Pairing::Both { lhs, rhs }; - let hidden = change_coverage(&sides); - assert_eq!(hidden.all.head, vec![[0, 1], [2, 3]]); - assert!(hidden.all.base.is_empty()); // Added tokens do not imply removed tokens. - assert_eq!(hidden.initially_visible.counts().added, 0); - if let Pairing::Both { rhs, .. } = &mut sides { - let Node::Fold { children, .. } = &mut rhs.root.node else { - unreachable!("a root is a fold"); - }; - children[0].visibility.collapsed = false; - } - let opened = change_coverage(&sides); - assert_eq!(opened.all, hidden.all); - assert_eq!(opened.initially_visible, opened.all); - } - - #[test] - fn deleted_blank_lines_and_empty_files_have_complete_coverage() { - let lhs = source(vec![leaf(1, 0, (0, 2), &[], true)]); - let deleted = change_coverage(&Pairing::LeftOnly { lhs }); - assert_eq!(deleted.all.base, vec![[0, 2]]); - assert!(deleted.all.head.is_empty()); - assert_eq!(deleted.initially_visible.counts().removed, 0); - let empty = change_coverage(&Pairing::RightOnly { - rhs: source(vec![]), - }); - assert_eq!(empty.all, StructuralChanges::default()); - } - - #[test] - fn a_missing_side_counts_nothing() { - let rhs = source(vec![leaf(0, 1, (0, 1), &[0], false)]); - let counts = change_coverage(&Pairing::RightOnly { rhs }) - .initially_visible - .counts(); - assert_eq!(counts.added, 1); - assert_eq!(counts.removed, 0); + fn every_line_of_a_deleted_file_counts_even_blank_ones() { + let lhs = source(vec![leaf(1, 0, (0, 2), &[], false)]); + assert_eq!(visible_changes(&Pairing::LeftOnly { lhs }).removed, 2); } } diff --git a/crates/diffr-core/src/protocol/mod.rs b/crates/diffr-core/src/protocol/mod.rs index 87b48028f..1920c557c 100644 --- a/crates/diffr-core/src/protocol/mod.rs +++ b/crates/diffr-core/src/protocol/mod.rs @@ -191,8 +191,6 @@ pub enum Diff { #[serde(flatten)] sides: Pairing, stats: Stats, - /// All structurally changed lines, including content hidden by folds. - structural_changes: StructuralChanges, }, /// Either side being binary makes the whole diff binary. Binary { @@ -356,25 +354,6 @@ pub struct SourcePos { /// Zero-based, half-open source line interval: `[start, end)` on the wire. pub type LineRange = [u32; 2]; -/// Structural change coverage, independent of visibility. Ranges are sorted, -/// nonempty, disjoint, and coalesced when adjacent. A missing side has no ranges. -/// `base` refers to `lhs`; `head` refers to `rhs`. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct StructuralChanges { - pub base: Vec, - pub head: Vec, -} - -impl StructuralChanges { - pub fn counts(&self) -> LineCounts { - let count = |ranges: &[LineRange]| ranges.iter().map(|[start, end]| end - start).sum(); - LineCounts { - added: count(&self.head), - removed: count(&self.base), - } - } -} - /// Line counts for one file. `fallback` is present exactly when the AST /// match did not run and the alignment is a line diff, carrying why: /// `too_complex`, `too_large`, `unsupported_language`, `parse_error`, @@ -486,10 +465,6 @@ mod tests { }], ), }, - structural_changes: StructuralChanges { - base: vec![], - head: vec![[1, 2]], - }, stats: Stats { textual: LineCounts { added: 1, @@ -543,7 +518,6 @@ mod tests { "rhs": {"text": "fn f() {\n 1 + 2\n}\n", "root": root(10, region(5, json!([{"line": 1, "start_column": 5, "end_column": 9}])))}, "stats": {"textual": {"added": 1, "removed": 1}, "visible": {"added": 1, "removed": 1}}, - "structural_changes": {"base": [], "head": [[1, 2]]}, }, }); assert_eq!(serde_json::to_value(example_file()).unwrap(), expected); diff --git a/crates/diffr-core/src/protocol/project.rs b/crates/diffr-core/src/protocol/project.rs index 0244d89be..bfa467a4c 100644 --- a/crates/diffr-core/src/protocol/project.rs +++ b/crates/diffr-core/src/protocol/project.rs @@ -86,8 +86,6 @@ pub fn diff(result: &DiffResult, inputs: Inputs<'_>) -> Diff { Diff::Text { sides, stats: stats(result, lhs_src, rhs_src), - // Filled together with visible counts after plugins shape the trees. - structural_changes: Default::default(), } } diff --git a/diffr-ts/src/contract.ts b/diffr-ts/src/contract.ts index 7a7de338d..73302af7d 100644 --- a/diffr-ts/src/contract.ts +++ b/diffr-ts/src/contract.ts @@ -158,42 +158,10 @@ export const StructuralVisibilitySchema: z.ZodType = }), ); -/** Structural changed lines, independent of presentation and fold state. */ -export type StructuralChanges = { - base: [number, number][]; - head: [number, number][]; -}; - -const structuralRangesSchema = z - .array(z.tuple([structuralU32, structuralU32])) - .refine( - (ranges) => - ranges.every( - ([start, end], index) => - start < end && (index === 0 || ranges[index - 1][1] < start), - ), - "Expected sorted, coalesced, nonempty half-open ranges", - ); - -export const StructuralChangesSchema: z.ZodType = z.object({ - base: structuralRangesSchema, - head: structuralRangesSchema, -}); - -export function structuralChangeCounts( - changes: StructuralChanges, -): StructuralLineCounts { - const count = (ranges: [number, number][]) => - ranges.reduce((sum, [start, end]) => sum + end - start, 0); - - return { added: count(changes.head), removed: count(changes.base) }; -} - export type StructuralDiff = | ({ type: "text"; stats: StructuralStats; - structural_changes: StructuralChanges; } & StructuralPairing) | ({ type: "binary" } & StructuralPairing); @@ -203,7 +171,6 @@ export const StructuralDiffSchema: z.ZodType = z.lazy(() => .object({ type: z.literal("text"), stats: StructuralStatsSchema, - structural_changes: StructuralChangesSchema, }) .and(structuralPairingSchema(StructuralSourceSchema)), z diff --git a/tui/test/fixtures/comparison.ndjson b/tui/test/fixtures/comparison.ndjson index 93aa82ee2..6192ee253 100644 --- a/tui/test/fixtures/comparison.ndjson +++ b/tui/test/fixtures/comparison.ndjson @@ -1,4 +1,4 @@ {"type":"start","version":3,"lhs":{"type":"revision","rev":"4895447da86c90df267f64320ac5cd23c58d65ea"},"rhs":{"type":"revision","rev":"476b98e96f6196f2e18e1d08d5d3c2b957e7e294"},"files":[{"file":{"lhs":{"path":"src/greet.ts","oid":"8529d7a6dc7709c707ebd16b3689352f90f91993","mode":"100644"},"rhs":{"path":"src/greet.ts","oid":"f9895c906b6b46c0c562bc9e8c39b1702096cf4f","mode":"100644"}},"status":"modified"},{"file":{"lhs":{"path":"tests/greet.test.ts","oid":"0231e3a6a765dbd026034d9e12a988347d559f88","mode":"100644"},"rhs":{"path":"tests/greet.test.ts","oid":"ae1cd95b02cc02cff3387547fceff8bda367db5b","mode":"100644"}},"status":"modified","tags":["test"]}]} -{"type":"file","file":{"lhs":{"path":"src/greet.ts","oid":"8529d7a6dc7709c707ebd16b3689352f90f91993","mode":"100644"},"rhs":{"path":"src/greet.ts","oid":"f9895c906b6b46c0c562bc9e8c39b1702096cf4f","mode":"100644"}},"diff":{"type":"text","lhs":{"text":"export function greet(name: string) {\n return \"hello \" + name;\n}\n","syntax":[{"line":0,"start_column":0,"end_column":6,"capture":"keyword"},{"line":0,"start_column":7,"end_column":15,"capture":"keyword"},{"line":0,"start_column":16,"end_column":21,"capture":"variable"},{"line":0,"start_column":21,"end_column":22,"capture":"punctuation.bracket"},{"line":0,"start_column":22,"end_column":26,"capture":"variable"},{"line":0,"start_column":28,"end_column":34,"capture":"type.builtin"},{"line":0,"start_column":34,"end_column":35,"capture":"punctuation.bracket"},{"line":0,"start_column":36,"end_column":37,"capture":"punctuation.bracket"},{"line":1,"start_column":2,"end_column":8,"capture":"keyword"},{"line":1,"start_column":9,"end_column":17,"capture":"string"},{"line":1,"start_column":18,"end_column":19,"capture":"operator"},{"line":1,"start_column":20,"end_column":24,"capture":"variable"},{"line":1,"start_column":24,"end_column":25,"capture":"punctuation.delimiter"},{"line":2,"start_column":0,"end_column":1,"capture":"punctuation.bracket"}],"root":{"id":9,"fold_state_id":9,"start":{"line":0,"column":0},"end":{"line":3,"column":0},"kind":"fold","indent":{"line":0,"column":0},"children":[{"id":1,"fold_state_id":1,"start":{"line":0,"column":0},"end":{"line":1,"column":0},"kind":"leaf","alignment_id":0},{"id":2,"fold_state_id":2,"start":{"line":1,"column":0},"end":{"line":3,"column":0},"tags":["context:scope"],"kind":"fold","children":[{"id":3,"fold_state_id":3,"start":{"line":1,"column":0},"end":{"line":2,"column":0},"kind":"leaf","alignment_id":1},{"id":4,"fold_state_id":4,"start":{"line":2,"column":0},"end":{"line":3,"column":0},"kind":"leaf","alignment_id":2}],"indent":{"line":1,"column":2}}]}},"rhs":{"text":"export function greet(name: string, punctuation = \"!\") {\n return \"hello \" + name + punctuation;\n}\n","syntax":[{"line":0,"start_column":0,"end_column":6,"capture":"keyword"},{"line":0,"start_column":7,"end_column":15,"capture":"keyword"},{"line":0,"start_column":16,"end_column":21,"capture":"variable"},{"line":0,"start_column":21,"end_column":22,"capture":"punctuation.bracket"},{"line":0,"start_column":22,"end_column":26,"capture":"variable"},{"line":0,"start_column":28,"end_column":34,"capture":"type.builtin"},{"line":0,"start_column":34,"end_column":35,"capture":"punctuation.delimiter"},{"line":0,"start_column":36,"end_column":47,"capture":"variable"},{"line":0,"start_column":48,"end_column":49,"capture":"operator"},{"line":0,"start_column":50,"end_column":53,"capture":"string"},{"line":0,"start_column":53,"end_column":54,"capture":"punctuation.bracket"},{"line":0,"start_column":55,"end_column":56,"capture":"punctuation.bracket"},{"line":1,"start_column":2,"end_column":8,"capture":"keyword"},{"line":1,"start_column":9,"end_column":17,"capture":"string"},{"line":1,"start_column":18,"end_column":19,"capture":"operator"},{"line":1,"start_column":20,"end_column":24,"capture":"variable"},{"line":1,"start_column":25,"end_column":26,"capture":"operator"},{"line":1,"start_column":27,"end_column":38,"capture":"variable"},{"line":1,"start_column":38,"end_column":39,"capture":"punctuation.delimiter"},{"line":2,"start_column":0,"end_column":1,"capture":"punctuation.bracket"}],"root":{"id":10,"fold_state_id":9,"start":{"line":0,"column":0},"end":{"line":3,"column":0},"kind":"fold","indent":{"line":0,"column":0},"children":[{"id":5,"fold_state_id":1,"start":{"line":0,"column":0},"end":{"line":1,"column":0},"kind":"leaf","alignment_id":0,"changed":[{"line":0,"start_column":34,"end_column":35},{"line":0,"start_column":36,"end_column":47},{"line":0,"start_column":48,"end_column":49},{"line":0,"start_column":50,"end_column":53}]},{"id":6,"fold_state_id":2,"start":{"line":1,"column":0},"end":{"line":3,"column":0},"tags":["context:scope"],"kind":"fold","children":[{"id":7,"fold_state_id":3,"start":{"line":1,"column":0},"end":{"line":2,"column":0},"kind":"leaf","alignment_id":1,"changed":[{"line":1,"start_column":25,"end_column":26},{"line":1,"start_column":27,"end_column":38}]},{"id":8,"fold_state_id":4,"start":{"line":2,"column":0},"end":{"line":3,"column":0},"kind":"leaf","alignment_id":2}],"indent":{"line":1,"column":2}}]}},"stats":{"textual":{"added":2,"removed":2},"visible":{"added":2,"removed":0}},"structural_changes":{"base":[],"head":[[0,2]]}}} -{"type":"file","file":{"lhs":{"path":"tests/greet.test.ts","oid":"0231e3a6a765dbd026034d9e12a988347d559f88","mode":"100644"},"rhs":{"path":"tests/greet.test.ts","oid":"ae1cd95b02cc02cff3387547fceff8bda367db5b","mode":"100644"}},"diff":{"type":"text","lhs":{"text":"import { greet } from \"../src/greet\";\ntest(\"greets\", () => expect(greet(\"a\")).toBe(\"hello a\"));\n","syntax":[{"line":0,"start_column":0,"end_column":6,"capture":"keyword"},{"line":0,"start_column":7,"end_column":8,"capture":"punctuation.bracket"},{"line":0,"start_column":9,"end_column":14,"capture":"variable"},{"line":0,"start_column":15,"end_column":16,"capture":"punctuation.bracket"},{"line":0,"start_column":17,"end_column":21,"capture":"keyword"},{"line":0,"start_column":22,"end_column":36,"capture":"string"},{"line":0,"start_column":36,"end_column":37,"capture":"punctuation.delimiter"},{"line":1,"start_column":0,"end_column":4,"capture":"variable"},{"line":1,"start_column":4,"end_column":5,"capture":"punctuation.bracket"},{"line":1,"start_column":5,"end_column":13,"capture":"string"},{"line":1,"start_column":13,"end_column":14,"capture":"punctuation.delimiter"},{"line":1,"start_column":15,"end_column":17,"capture":"punctuation.bracket"},{"line":1,"start_column":18,"end_column":20,"capture":"operator"},{"line":1,"start_column":21,"end_column":27,"capture":"variable"},{"line":1,"start_column":27,"end_column":28,"capture":"punctuation.bracket"},{"line":1,"start_column":28,"end_column":33,"capture":"variable"},{"line":1,"start_column":33,"end_column":34,"capture":"punctuation.bracket"},{"line":1,"start_column":34,"end_column":37,"capture":"string"},{"line":1,"start_column":37,"end_column":39,"capture":"punctuation.bracket"},{"line":1,"start_column":39,"end_column":40,"capture":"punctuation.delimiter"},{"line":1,"start_column":40,"end_column":44,"capture":"property"},{"line":1,"start_column":44,"end_column":45,"capture":"punctuation.bracket"},{"line":1,"start_column":45,"end_column":54,"capture":"string"},{"line":1,"start_column":54,"end_column":56,"capture":"punctuation.bracket"},{"line":1,"start_column":56,"end_column":57,"capture":"punctuation.delimiter"}],"root":{"id":5,"fold_state_id":5,"start":{"line":0,"column":0},"end":{"line":2,"column":0},"kind":"fold","indent":{"line":0,"column":0},"children":[{"id":1,"fold_state_id":1,"start":{"line":0,"column":0},"end":{"line":1,"column":0},"kind":"leaf","alignment_id":0},{"id":2,"fold_state_id":2,"start":{"line":1,"column":0},"end":{"line":2,"column":0},"kind":"leaf","alignment_id":1}],"visibility":{"collapsed":true,"label":"Test file · hidden by default"}}},"rhs":{"text":"import { greet } from \"../src/greet\";\ntest(\"greets\", () => expect(greet(\"a\")).toBe(\"hello a!\"));\n","syntax":[{"line":0,"start_column":0,"end_column":6,"capture":"keyword"},{"line":0,"start_column":7,"end_column":8,"capture":"punctuation.bracket"},{"line":0,"start_column":9,"end_column":14,"capture":"variable"},{"line":0,"start_column":15,"end_column":16,"capture":"punctuation.bracket"},{"line":0,"start_column":17,"end_column":21,"capture":"keyword"},{"line":0,"start_column":22,"end_column":36,"capture":"string"},{"line":0,"start_column":36,"end_column":37,"capture":"punctuation.delimiter"},{"line":1,"start_column":0,"end_column":4,"capture":"variable"},{"line":1,"start_column":4,"end_column":5,"capture":"punctuation.bracket"},{"line":1,"start_column":5,"end_column":13,"capture":"string"},{"line":1,"start_column":13,"end_column":14,"capture":"punctuation.delimiter"},{"line":1,"start_column":15,"end_column":17,"capture":"punctuation.bracket"},{"line":1,"start_column":18,"end_column":20,"capture":"operator"},{"line":1,"start_column":21,"end_column":27,"capture":"variable"},{"line":1,"start_column":27,"end_column":28,"capture":"punctuation.bracket"},{"line":1,"start_column":28,"end_column":33,"capture":"variable"},{"line":1,"start_column":33,"end_column":34,"capture":"punctuation.bracket"},{"line":1,"start_column":34,"end_column":37,"capture":"string"},{"line":1,"start_column":37,"end_column":39,"capture":"punctuation.bracket"},{"line":1,"start_column":39,"end_column":40,"capture":"punctuation.delimiter"},{"line":1,"start_column":40,"end_column":44,"capture":"property"},{"line":1,"start_column":44,"end_column":45,"capture":"punctuation.bracket"},{"line":1,"start_column":45,"end_column":55,"capture":"string"},{"line":1,"start_column":55,"end_column":57,"capture":"punctuation.bracket"},{"line":1,"start_column":57,"end_column":58,"capture":"punctuation.delimiter"}],"root":{"id":6,"fold_state_id":5,"start":{"line":0,"column":0},"end":{"line":2,"column":0},"kind":"fold","indent":{"line":0,"column":0},"children":[{"id":3,"fold_state_id":1,"start":{"line":0,"column":0},"end":{"line":1,"column":0},"kind":"leaf","alignment_id":0},{"id":4,"fold_state_id":2,"start":{"line":1,"column":0},"end":{"line":2,"column":0},"kind":"leaf","alignment_id":1,"changed":[{"line":1,"start_column":53,"end_column":54}]}],"visibility":{"collapsed":true,"label":"Test file · hidden by default"}}},"stats":{"textual":{"added":1,"removed":1},"visible":{"added":1,"removed":0}},"structural_changes":{"base":[],"head":[[1,2]]}}} +{"type":"file","file":{"lhs":{"path":"src/greet.ts","oid":"8529d7a6dc7709c707ebd16b3689352f90f91993","mode":"100644"},"rhs":{"path":"src/greet.ts","oid":"f9895c906b6b46c0c562bc9e8c39b1702096cf4f","mode":"100644"}},"diff":{"type":"text","lhs":{"text":"export function greet(name: string) {\n return \"hello \" + name;\n}\n","syntax":[{"line":0,"start_column":0,"end_column":6,"capture":"keyword"},{"line":0,"start_column":7,"end_column":15,"capture":"keyword"},{"line":0,"start_column":16,"end_column":21,"capture":"variable"},{"line":0,"start_column":21,"end_column":22,"capture":"punctuation.bracket"},{"line":0,"start_column":22,"end_column":26,"capture":"variable"},{"line":0,"start_column":28,"end_column":34,"capture":"type.builtin"},{"line":0,"start_column":34,"end_column":35,"capture":"punctuation.bracket"},{"line":0,"start_column":36,"end_column":37,"capture":"punctuation.bracket"},{"line":1,"start_column":2,"end_column":8,"capture":"keyword"},{"line":1,"start_column":9,"end_column":17,"capture":"string"},{"line":1,"start_column":18,"end_column":19,"capture":"operator"},{"line":1,"start_column":20,"end_column":24,"capture":"variable"},{"line":1,"start_column":24,"end_column":25,"capture":"punctuation.delimiter"},{"line":2,"start_column":0,"end_column":1,"capture":"punctuation.bracket"}],"root":{"id":9,"fold_state_id":9,"start":{"line":0,"column":0},"end":{"line":3,"column":0},"kind":"fold","indent":{"line":0,"column":0},"children":[{"id":1,"fold_state_id":1,"start":{"line":0,"column":0},"end":{"line":1,"column":0},"kind":"leaf","alignment_id":0},{"id":2,"fold_state_id":2,"start":{"line":1,"column":0},"end":{"line":3,"column":0},"tags":["context:scope"],"kind":"fold","children":[{"id":3,"fold_state_id":3,"start":{"line":1,"column":0},"end":{"line":2,"column":0},"kind":"leaf","alignment_id":1},{"id":4,"fold_state_id":4,"start":{"line":2,"column":0},"end":{"line":3,"column":0},"kind":"leaf","alignment_id":2}],"indent":{"line":1,"column":2}}]}},"rhs":{"text":"export function greet(name: string, punctuation = \"!\") {\n return \"hello \" + name + punctuation;\n}\n","syntax":[{"line":0,"start_column":0,"end_column":6,"capture":"keyword"},{"line":0,"start_column":7,"end_column":15,"capture":"keyword"},{"line":0,"start_column":16,"end_column":21,"capture":"variable"},{"line":0,"start_column":21,"end_column":22,"capture":"punctuation.bracket"},{"line":0,"start_column":22,"end_column":26,"capture":"variable"},{"line":0,"start_column":28,"end_column":34,"capture":"type.builtin"},{"line":0,"start_column":34,"end_column":35,"capture":"punctuation.delimiter"},{"line":0,"start_column":36,"end_column":47,"capture":"variable"},{"line":0,"start_column":48,"end_column":49,"capture":"operator"},{"line":0,"start_column":50,"end_column":53,"capture":"string"},{"line":0,"start_column":53,"end_column":54,"capture":"punctuation.bracket"},{"line":0,"start_column":55,"end_column":56,"capture":"punctuation.bracket"},{"line":1,"start_column":2,"end_column":8,"capture":"keyword"},{"line":1,"start_column":9,"end_column":17,"capture":"string"},{"line":1,"start_column":18,"end_column":19,"capture":"operator"},{"line":1,"start_column":20,"end_column":24,"capture":"variable"},{"line":1,"start_column":25,"end_column":26,"capture":"operator"},{"line":1,"start_column":27,"end_column":38,"capture":"variable"},{"line":1,"start_column":38,"end_column":39,"capture":"punctuation.delimiter"},{"line":2,"start_column":0,"end_column":1,"capture":"punctuation.bracket"}],"root":{"id":10,"fold_state_id":9,"start":{"line":0,"column":0},"end":{"line":3,"column":0},"kind":"fold","indent":{"line":0,"column":0},"children":[{"id":5,"fold_state_id":1,"start":{"line":0,"column":0},"end":{"line":1,"column":0},"kind":"leaf","alignment_id":0,"changed":[{"line":0,"start_column":34,"end_column":35},{"line":0,"start_column":36,"end_column":47},{"line":0,"start_column":48,"end_column":49},{"line":0,"start_column":50,"end_column":53}]},{"id":6,"fold_state_id":2,"start":{"line":1,"column":0},"end":{"line":3,"column":0},"tags":["context:scope"],"kind":"fold","children":[{"id":7,"fold_state_id":3,"start":{"line":1,"column":0},"end":{"line":2,"column":0},"kind":"leaf","alignment_id":1,"changed":[{"line":1,"start_column":25,"end_column":26},{"line":1,"start_column":27,"end_column":38}]},{"id":8,"fold_state_id":4,"start":{"line":2,"column":0},"end":{"line":3,"column":0},"kind":"leaf","alignment_id":2}],"indent":{"line":1,"column":2}}]}},"stats":{"textual":{"added":2,"removed":2},"visible":{"added":2,"removed":0}}}} +{"type":"file","file":{"lhs":{"path":"tests/greet.test.ts","oid":"0231e3a6a765dbd026034d9e12a988347d559f88","mode":"100644"},"rhs":{"path":"tests/greet.test.ts","oid":"ae1cd95b02cc02cff3387547fceff8bda367db5b","mode":"100644"}},"diff":{"type":"text","lhs":{"text":"import { greet } from \"../src/greet\";\ntest(\"greets\", () => expect(greet(\"a\")).toBe(\"hello a\"));\n","syntax":[{"line":0,"start_column":0,"end_column":6,"capture":"keyword"},{"line":0,"start_column":7,"end_column":8,"capture":"punctuation.bracket"},{"line":0,"start_column":9,"end_column":14,"capture":"variable"},{"line":0,"start_column":15,"end_column":16,"capture":"punctuation.bracket"},{"line":0,"start_column":17,"end_column":21,"capture":"keyword"},{"line":0,"start_column":22,"end_column":36,"capture":"string"},{"line":0,"start_column":36,"end_column":37,"capture":"punctuation.delimiter"},{"line":1,"start_column":0,"end_column":4,"capture":"variable"},{"line":1,"start_column":4,"end_column":5,"capture":"punctuation.bracket"},{"line":1,"start_column":5,"end_column":13,"capture":"string"},{"line":1,"start_column":13,"end_column":14,"capture":"punctuation.delimiter"},{"line":1,"start_column":15,"end_column":17,"capture":"punctuation.bracket"},{"line":1,"start_column":18,"end_column":20,"capture":"operator"},{"line":1,"start_column":21,"end_column":27,"capture":"variable"},{"line":1,"start_column":27,"end_column":28,"capture":"punctuation.bracket"},{"line":1,"start_column":28,"end_column":33,"capture":"variable"},{"line":1,"start_column":33,"end_column":34,"capture":"punctuation.bracket"},{"line":1,"start_column":34,"end_column":37,"capture":"string"},{"line":1,"start_column":37,"end_column":39,"capture":"punctuation.bracket"},{"line":1,"start_column":39,"end_column":40,"capture":"punctuation.delimiter"},{"line":1,"start_column":40,"end_column":44,"capture":"property"},{"line":1,"start_column":44,"end_column":45,"capture":"punctuation.bracket"},{"line":1,"start_column":45,"end_column":54,"capture":"string"},{"line":1,"start_column":54,"end_column":56,"capture":"punctuation.bracket"},{"line":1,"start_column":56,"end_column":57,"capture":"punctuation.delimiter"}],"root":{"id":5,"fold_state_id":5,"start":{"line":0,"column":0},"end":{"line":2,"column":0},"kind":"fold","indent":{"line":0,"column":0},"children":[{"id":1,"fold_state_id":1,"start":{"line":0,"column":0},"end":{"line":1,"column":0},"kind":"leaf","alignment_id":0},{"id":2,"fold_state_id":2,"start":{"line":1,"column":0},"end":{"line":2,"column":0},"kind":"leaf","alignment_id":1}],"visibility":{"collapsed":true,"label":"Test file · hidden by default"}}},"rhs":{"text":"import { greet } from \"../src/greet\";\ntest(\"greets\", () => expect(greet(\"a\")).toBe(\"hello a!\"));\n","syntax":[{"line":0,"start_column":0,"end_column":6,"capture":"keyword"},{"line":0,"start_column":7,"end_column":8,"capture":"punctuation.bracket"},{"line":0,"start_column":9,"end_column":14,"capture":"variable"},{"line":0,"start_column":15,"end_column":16,"capture":"punctuation.bracket"},{"line":0,"start_column":17,"end_column":21,"capture":"keyword"},{"line":0,"start_column":22,"end_column":36,"capture":"string"},{"line":0,"start_column":36,"end_column":37,"capture":"punctuation.delimiter"},{"line":1,"start_column":0,"end_column":4,"capture":"variable"},{"line":1,"start_column":4,"end_column":5,"capture":"punctuation.bracket"},{"line":1,"start_column":5,"end_column":13,"capture":"string"},{"line":1,"start_column":13,"end_column":14,"capture":"punctuation.delimiter"},{"line":1,"start_column":15,"end_column":17,"capture":"punctuation.bracket"},{"line":1,"start_column":18,"end_column":20,"capture":"operator"},{"line":1,"start_column":21,"end_column":27,"capture":"variable"},{"line":1,"start_column":27,"end_column":28,"capture":"punctuation.bracket"},{"line":1,"start_column":28,"end_column":33,"capture":"variable"},{"line":1,"start_column":33,"end_column":34,"capture":"punctuation.bracket"},{"line":1,"start_column":34,"end_column":37,"capture":"string"},{"line":1,"start_column":37,"end_column":39,"capture":"punctuation.bracket"},{"line":1,"start_column":39,"end_column":40,"capture":"punctuation.delimiter"},{"line":1,"start_column":40,"end_column":44,"capture":"property"},{"line":1,"start_column":44,"end_column":45,"capture":"punctuation.bracket"},{"line":1,"start_column":45,"end_column":55,"capture":"string"},{"line":1,"start_column":55,"end_column":57,"capture":"punctuation.bracket"},{"line":1,"start_column":57,"end_column":58,"capture":"punctuation.delimiter"}],"root":{"id":6,"fold_state_id":5,"start":{"line":0,"column":0},"end":{"line":2,"column":0},"kind":"fold","indent":{"line":0,"column":0},"children":[{"id":3,"fold_state_id":1,"start":{"line":0,"column":0},"end":{"line":1,"column":0},"kind":"leaf","alignment_id":0},{"id":4,"fold_state_id":2,"start":{"line":1,"column":0},"end":{"line":2,"column":0},"kind":"leaf","alignment_id":1,"changed":[{"line":1,"start_column":53,"end_column":54}]}],"visibility":{"collapsed":true,"label":"Test file · hidden by default"}}},"stats":{"textual":{"added":1,"removed":1},"visible":{"added":1,"removed":0}}}} {"type":"complete","succeeded":2,"failed":0}