Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e895125
perf: speed up file diffing
KnorpelSenf Sep 8, 2025
9aab445
style: fix lints
KnorpelSenf Sep 8, 2025
0770d7f
Merge branch 'main' into fast-file-diff
KnorpelSenf Sep 8, 2025
d257d1a
build: enabled unified_diff feature for imara
KnorpelSenf Sep 8, 2025
4bd1ca6
Merge branch 'main' into fast-file-diff
KnorpelSenf Sep 10, 2025
1b1496c
Merge remote-tracking branch 'fork/fast-file-diff' into fast-file-diff
KnorpelSenf Sep 18, 2025
f5d0f2b
Merge branch 'main' into fast-file-diff
KnorpelSenf Sep 18, 2025
b834694
feat: restore first bits of diff formatting
KnorpelSenf Sep 18, 2025
7a863b4
build: disable unused imara diff feature
KnorpelSenf Sep 18, 2025
0332a9a
test: revert temporary changes
KnorpelSenf Sep 18, 2025
85057ca
fix: bad rename
KnorpelSenf Sep 18, 2025
ed4188a
fix: lints
KnorpelSenf Sep 18, 2025
ebc2b9c
Merge branch 'main' into fast-file-diff
KnorpelSenf Sep 27, 2025
b2fb495
Merge branch 'main' into fast-file-diff
KnorpelSenf Nov 7, 2025
15bfdd9
Merge branch 'main' into fast-file-diff
KnorpelSenf Nov 22, 2025
60ed8e5
Merge branch 'main' into fast-file-diff
bartlomieju Mar 12, 2026
c151892
fix: properly handle multi-hunk diffs and line number tracking
bartlomieju Mar 12, 2026
b7c83b1
fix: show context lines between hunks, fix Cargo.lock
bartlomieju Mar 12, 2026
057583a
fix: interleave delete/insert pairs and run formatter
bartlomieju Mar 12, 2026
ae9f49b
fix: resolve clippy warnings and remove context lines between hunks
bartlomieju Mar 12, 2026
6ac9249
fix: update frozen lockfile test expectations for imara-diff
bartlomieju Mar 12, 2026
8d30685
fix: preserve newline-only changes in diff output
bartlomieju Mar 12, 2026
f9f56af
fix: add separator between non-contiguous hunks in diff output
bartlomieju Mar 12, 2026
a24eb00
update tests
bartlomieju Mar 12, 2026
1311270
fix the test
bartlomieju Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ console_static_text = "=0.8.3"
crossterm = "0.28.1"
dhat = "0.3.3"
dissimilar = "=1.0.9"
imara-diff = "=0.2.0"
dprint-core = "=0.67.4"
dprint-plugin-json = "=0.20.0"
dprint-plugin-jupyter = "=0.2.0"
Expand Down
2 changes: 1 addition & 1 deletion libs/resolver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ deno_permissions = { workspace = true, optional = true }
deno_semver.workspace = true
deno_terminal.workspace = true
deno_unsync.workspace = true
dissimilar.workspace = true
imara-diff.workspace = true
futures.workspace = true
http = { workspace = true, optional = true }
import_map.workspace = true
Expand Down
236 changes: 89 additions & 147 deletions libs/resolver/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
//! It would be best to move these utilities out of this
//! crate as this is not specific to resolution, but for
//! the time being it's fine for this to live here.
use std::fmt::Write as _;

use deno_terminal::colors;
use dissimilar::Chunk;
use dissimilar::diff as difference;
use imara_diff::{
BasicLineDiffPrinter, Diff, InternedInput, UnifiedDiffConfig,
};

/// Print diff of the same file_path, before and after formatting.
///
Expand All @@ -25,153 +24,96 @@ pub fn diff(orig_text: &str, edit_text: &str) -> String {
return " | Text differed by line endings.\n".to_string();
}

DiffBuilder::build(&orig_text, &edit_text)
}

struct DiffBuilder {
output: String,
line_number_width: usize,
orig_line: usize,
edit_line: usize,
orig: String,
edit: String,
has_changes: bool,
}

impl DiffBuilder {
pub fn build(orig_text: &str, edit_text: &str) -> String {
let mut diff_builder = DiffBuilder {
output: String::new(),
orig_line: 1,
edit_line: 1,
orig: String::new(),
edit: String::new(),
has_changes: false,
line_number_width: {
let line_count = std::cmp::max(
orig_text.split('\n').count(),
edit_text.split('\n').count(),
);
line_count.to_string().chars().count()
},
};

let chunks = difference(orig_text, edit_text);
diff_builder.handle_chunks(chunks);
diff_builder.output
}

fn handle_chunks<'a>(&'a mut self, chunks: Vec<Chunk<'a>>) {
for chunk in chunks {
match chunk {
Chunk::Delete(s) => {
let split = s.split('\n').enumerate();
for (i, s) in split {
if i > 0 {
self.orig.push('\n');
}
self.orig.push_str(&fmt_rem_text_highlight(s));
}
self.has_changes = true
}
Chunk::Insert(s) => {
let split = s.split('\n').enumerate();
for (i, s) in split {
if i > 0 {
self.edit.push('\n');
}
self.edit.push_str(&fmt_add_text_highlight(s));
}
self.has_changes = true
}
Chunk::Equal(s) => {
let split = s.split('\n').enumerate();
for (i, s) in split {
if i > 0 {
self.flush_changes();
}
self.orig.push_str(&fmt_rem_text(s));
self.edit.push_str(&fmt_add_text(s));
}
}
}
}

self.flush_changes();
}

fn flush_changes(&mut self) {
if self.has_changes {
self.write_line_diff();

self.orig_line += self.orig.split('\n').count();
self.edit_line += self.edit.split('\n').count();
self.has_changes = false;
} else {
self.orig_line += 1;
self.edit_line += 1;
}

self.orig.clear();
self.edit.clear();
}

fn write_line_diff(&mut self) {
let split = self.orig.split('\n').enumerate();
for (i, s) in split {
write!(
self.output,
"{:width$}{} ",
self.orig_line + i,
colors::gray(" |"),
width = self.line_number_width
)
.unwrap();
self.output.push_str(&fmt_rem());
self.output.push_str(s);
self.output.push('\n');
}

let split = self.edit.split('\n').enumerate();
for (i, s) in split {
write!(
self.output,
"{:width$}{} ",
self.edit_line + i,
colors::gray(" |"),
width = self.line_number_width
)
.unwrap();
self.output.push_str(&fmt_add());
self.output.push_str(s);
self.output.push('\n');
}
}
}

fn fmt_add() -> String {
colors::green_bold("+").to_string()
}

fn fmt_add_text(x: &str) -> String {
colors::green(x).to_string()
}

fn fmt_add_text_highlight(x: &str) -> String {
colors::black_on_green(x).to_string()
}

fn fmt_rem() -> String {
colors::red_bold("-").to_string()
build(&orig_text, &edit_text)
}

fn fmt_rem_text(x: &str) -> String {
colors::red(x).to_string()
pub fn build(orig_text: &str, edit_text: &str) -> String {
let input = InternedInput::new(orig_text, edit_text);
let mut diff = Diff::compute(imara_diff::Algorithm::Histogram, &input);
diff.postprocess_lines(&input);

diff
.unified_diff(
&BasicLineDiffPrinter(&input.interner),
UnifiedDiffConfig::default(),
&input,
)
.to_string()
}

fn fmt_rem_text_highlight(x: &str) -> String {
colors::white_on_red(x).to_string()
}
// fn handle_diff(&mut self, diff: &Diff, input: &InternedInput<&str>) {
// let mut old_line = 0u32;
// let mut new_line = 0u32;

// for hunk in diff.hunks() {
// // Process unchanged lines before this hunk
// if old_line < hunk.before.start || new_line < hunk.after.start {
// let unchanged_start = std::cmp::max(old_line, new_line);
// let unchanged_end = std::cmp::min(hunk.before.start, hunk.after.start);

// for line_idx in unchanged_start..unchanged_end {
// if line_idx > unchanged_start {
// self.flush_changes();
// }
// if (line_idx as usize) < input.before.len() {
// let line_text = &input.interner[input.before[line_idx as usize]];
// self.orig.push_str(&fmt_rem_text(line_text));
// }
// if (line_idx as usize) < input.after.len() {
// let line_text = &input.interner[input.after[line_idx as usize]];
// self.edit.push_str(&fmt_add_text(line_text));
// }
// }
// }

// // Process deletions (lines only in before)
// if hunk.before.start < hunk.before.end {
// for line_idx in hunk.before.start..hunk.before.end {
// if line_idx > hunk.before.start {
// self.orig.push('\n');
// }
// let line_text = &input.interner[input.before[line_idx as usize]];
// self.orig.push_str(&fmt_rem_text_highlight(line_text));
// }
// self.has_changes = true;
// }

// // Process insertions (lines only in after)
// if hunk.after.start < hunk.after.end {
// for line_idx in hunk.after.start..hunk.after.end {
// if line_idx > hunk.after.start {
// self.edit.push('\n');
// }
// let line_text = &input.interner[input.after[line_idx as usize]];
// self.edit.push_str(&fmt_add_text_highlight(line_text));
// }
// self.has_changes = true;
// }

// old_line = hunk.before.end;
// new_line = hunk.after.end;
// }

// // Process any remaining unchanged lines
// let max_lines = std::cmp::max(input.before.len(), input.after.len()) as u32;
// if old_line < max_lines || new_line < max_lines {
// for line_idx in std::cmp::max(old_line, new_line)..max_lines {
// if line_idx > std::cmp::max(old_line, new_line) {
// self.flush_changes();
// }
// if (line_idx as usize) < input.before.len() {
// let line_text = &input.interner[input.before[line_idx as usize]];
// self.orig.push_str(&fmt_rem_text(line_text));
// }
// if (line_idx as usize) < input.after.len() {
// let line_text = &input.interner[input.after[line_idx as usize]];
// self.edit.push_str(&fmt_add_text(line_text));
// }
// }
// }

// self.flush_changes();
// }

pub struct DisplayTreeNode {
pub text: String,
Expand Down
Loading