Skip to content

Commit 5cee347

Browse files
committed
Add lib.rs, serde
1 parent 03b8a4c commit 5cee347

File tree

11 files changed

+394
-351
lines changed

11 files changed

+394
-351
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const_format = "0.2.22"
4141
owo-colors = "3.2.0"
4242
rpds = "0.10.0"
4343
wu-diff = "0.1.2"
44+
serde = { version = "1.0", features = ["derive"] }
4445

4546
[dev-dependencies]
4647
pretty_assertions = "1.0.0"

src/guess_language.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ pub enum Language {
5050
Zig,
5151
}
5252

53-
use Language::*;
53+
pub use Language::*;
5454

5555
pub fn guess(path: &Path, src: &str) -> Option<Language> {
5656
if let Some(lang) = from_emacs_mode_header(src) {

src/lib.rs

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
//! Difftastic is a syntactic diff tool.
2+
//!
3+
//! For usage instructions and advice on contributing, see [the
4+
//! manual](http://difftastic.wilfred.me.uk/).
5+
//!
6+
7+
// This tends to trigger on larger tuples of simple types, and naming
8+
// them would probably be worse for readability.
9+
#![allow(clippy::type_complexity)]
10+
// == "" is often clearer when dealing with strings.
11+
#![allow(clippy::comparison_to_empty)]
12+
// It's common to have pairs foo_lhs and foo_rhs, leading to double
13+
// the number of arguments and triggering this lint.
14+
#![allow(clippy::too_many_arguments)]
15+
16+
mod context;
17+
mod dijkstra;
18+
pub mod files;
19+
mod graph;
20+
pub mod guess_language;
21+
mod hunks;
22+
mod inline;
23+
mod line_parser;
24+
mod lines;
25+
mod myers_diff;
26+
pub mod option_types;
27+
mod positions;
28+
mod side_by_side;
29+
mod sliders;
30+
pub mod style;
31+
pub mod summary;
32+
pub mod syntax;
33+
pub mod tree_sitter_parser;
34+
mod unchanged;
35+
36+
#[macro_use]
37+
extern crate log;
38+
39+
use crate::hunks::{matched_pos_to_hunks, merge_adjacent};
40+
use context::opposite_positions;
41+
use guess_language::guess;
42+
use log::info;
43+
use mimalloc::MiMalloc;
44+
use option_types::DisplayMode;
45+
46+
/// The global allocator used by difftastic.
47+
///
48+
/// Diffing allocates a large amount of memory, and `MiMalloc` performs
49+
/// better.
50+
#[global_allocator]
51+
static GLOBAL: MiMalloc = MiMalloc;
52+
53+
use sliders::fix_all_sliders;
54+
use std::{env, path::Path};
55+
use style::BackgroundColor;
56+
pub use summary::{DiffResult, FileContent};
57+
use syntax::{init_next_prev, Syntax};
58+
use typed_arena::Arena;
59+
60+
use crate::{
61+
dijkstra::mark_syntax, files::is_probably_binary, lines::MaxLine, syntax::init_all_info,
62+
tree_sitter_parser as tsp,
63+
};
64+
65+
extern crate pretty_env_logger;
66+
67+
pub fn diff_file_content(
68+
display_path: &str,
69+
lhs_bytes: &[u8],
70+
rhs_bytes: &[u8],
71+
node_limit: u32,
72+
byte_limit: usize,
73+
language_override: Option<guess_language::Language>,
74+
) -> DiffResult {
75+
if is_probably_binary(lhs_bytes) || is_probably_binary(rhs_bytes) {
76+
return DiffResult {
77+
path: display_path.into(),
78+
language: None,
79+
lhs_src: FileContent::Binary(lhs_bytes.to_vec()),
80+
rhs_src: FileContent::Binary(rhs_bytes.to_vec()),
81+
lhs_positions: vec![],
82+
rhs_positions: vec![],
83+
};
84+
}
85+
86+
// TODO: don't replace tab characters inside string literals.
87+
let mut lhs_src = String::from_utf8_lossy(lhs_bytes)
88+
.to_string()
89+
.replace('\t', " ");
90+
let mut rhs_src = String::from_utf8_lossy(rhs_bytes)
91+
.to_string()
92+
.replace('\t', " ");
93+
94+
// Ignore the trailing newline, if present.
95+
// TODO: highlight if this has changes (#144).
96+
// TODO: factor out a string cleaning function.
97+
if lhs_src.ends_with('\n') {
98+
lhs_src.pop();
99+
}
100+
if rhs_src.ends_with('\n') {
101+
rhs_src.pop();
102+
}
103+
104+
// TODO: take a Path directly instead.
105+
let path = Path::new(&display_path);
106+
107+
// Take the larger of the two files when guessing the
108+
// language. This is useful when we've added or removed a whole
109+
// file.
110+
let guess_src = if lhs_src.len() > rhs_src.len() {
111+
&lhs_src
112+
} else {
113+
&rhs_src
114+
};
115+
let ts_lang = language_override
116+
.or_else(|| guess(path, guess_src))
117+
.map(tsp::from_language);
118+
119+
if lhs_bytes == rhs_bytes {
120+
// If the two files are completely identical, return early
121+
// rather than doing any more work.
122+
return DiffResult {
123+
path: display_path.into(),
124+
language: ts_lang.map(|l| l.name.into()),
125+
lhs_src: FileContent::Text("".into()),
126+
rhs_src: FileContent::Text("".into()),
127+
lhs_positions: vec![],
128+
rhs_positions: vec![],
129+
};
130+
}
131+
132+
let (lang_name, lhs_positions, rhs_positions) = match ts_lang {
133+
_ if lhs_bytes.len() > byte_limit || rhs_bytes.len() > byte_limit => {
134+
let lhs_positions = line_parser::change_positions(&lhs_src, &rhs_src);
135+
let rhs_positions = line_parser::change_positions(&rhs_src, &lhs_src);
136+
(
137+
Some("Text (exceeded DFT_BYTE_LIMIT)".into()),
138+
lhs_positions,
139+
rhs_positions,
140+
)
141+
}
142+
Some(ts_lang) => {
143+
let arena = Arena::new();
144+
let lhs = tsp::parse(&arena, &lhs_src, &ts_lang);
145+
let rhs = tsp::parse(&arena, &rhs_src, &ts_lang);
146+
147+
init_all_info(&lhs, &rhs);
148+
149+
let possibly_changed = if env::var("DFT_DBG_KEEP_UNCHANGED").is_ok() {
150+
vec![(lhs.clone(), rhs.clone())]
151+
} else {
152+
unchanged::mark_unchanged(&lhs, &rhs)
153+
};
154+
155+
let possibly_changed_max = max_num_nodes(&possibly_changed);
156+
if possibly_changed_max > node_limit {
157+
info!(
158+
"Found {} nodes, exceeding the limit {}",
159+
possibly_changed_max, node_limit
160+
);
161+
162+
let lhs_positions = line_parser::change_positions(&lhs_src, &rhs_src);
163+
let rhs_positions = line_parser::change_positions(&rhs_src, &lhs_src);
164+
(
165+
Some("Text (exceeded DFT_NODE_LIMIT)".into()),
166+
lhs_positions,
167+
rhs_positions,
168+
)
169+
} else {
170+
for (lhs_section_nodes, rhs_section_nodes) in possibly_changed {
171+
init_next_prev(&lhs_section_nodes);
172+
init_next_prev(&rhs_section_nodes);
173+
174+
mark_syntax(
175+
lhs_section_nodes.get(0).copied(),
176+
rhs_section_nodes.get(0).copied(),
177+
);
178+
179+
fix_all_sliders(&lhs_section_nodes);
180+
fix_all_sliders(&rhs_section_nodes);
181+
}
182+
183+
let lhs_positions = syntax::change_positions(&lhs);
184+
let rhs_positions = syntax::change_positions(&rhs);
185+
(Some(ts_lang.name.into()), lhs_positions, rhs_positions)
186+
}
187+
}
188+
None => {
189+
let lhs_positions = line_parser::change_positions(&lhs_src, &rhs_src);
190+
let rhs_positions = line_parser::change_positions(&rhs_src, &lhs_src);
191+
(None, lhs_positions, rhs_positions)
192+
}
193+
};
194+
195+
DiffResult {
196+
path: display_path.into(),
197+
language: lang_name,
198+
lhs_src: FileContent::Text(lhs_src),
199+
rhs_src: FileContent::Text(rhs_src),
200+
lhs_positions,
201+
rhs_positions,
202+
}
203+
}
204+
205+
// TODO: factor out a DiffOptions struct.
206+
pub fn print_diff_result(
207+
display_width: usize,
208+
use_color: bool,
209+
display_mode: DisplayMode,
210+
background: BackgroundColor,
211+
print_unchanged: bool,
212+
summary: &DiffResult,
213+
) {
214+
match (&summary.lhs_src, &summary.rhs_src) {
215+
(FileContent::Text(lhs_src), FileContent::Text(rhs_src)) => {
216+
let opposite_to_lhs = opposite_positions(&summary.lhs_positions);
217+
let opposite_to_rhs = opposite_positions(&summary.rhs_positions);
218+
219+
let hunks = matched_pos_to_hunks(&summary.lhs_positions, &summary.rhs_positions);
220+
let hunks = merge_adjacent(
221+
&hunks,
222+
&opposite_to_lhs,
223+
&opposite_to_rhs,
224+
lhs_src.max_line(),
225+
rhs_src.max_line(),
226+
);
227+
228+
let lang_name = summary.language.clone().unwrap_or_else(|| "Text".into());
229+
if hunks.is_empty() {
230+
if print_unchanged {
231+
println!(
232+
"{}",
233+
style::header(&summary.path, 1, 1, &lang_name, use_color, background)
234+
);
235+
if lang_name == "Text" || summary.lhs_src == summary.rhs_src {
236+
// TODO: there are other Text names now, so
237+
// they will hit the second case incorrectly.
238+
println!("No changes.\n");
239+
} else {
240+
println!("No syntactic changes.\n");
241+
}
242+
}
243+
return;
244+
}
245+
246+
match display_mode {
247+
DisplayMode::Inline => {
248+
inline::print(
249+
lhs_src,
250+
rhs_src,
251+
&summary.lhs_positions,
252+
&summary.rhs_positions,
253+
&hunks,
254+
&summary.path,
255+
&lang_name,
256+
use_color,
257+
background,
258+
);
259+
}
260+
DisplayMode::SideBySide | DisplayMode::SideBySideShowBoth => {
261+
side_by_side::print(
262+
&hunks,
263+
display_width,
264+
use_color,
265+
display_mode,
266+
background,
267+
&summary.path,
268+
&lang_name,
269+
lhs_src,
270+
rhs_src,
271+
&summary.lhs_positions,
272+
&summary.rhs_positions,
273+
);
274+
}
275+
}
276+
}
277+
(FileContent::Binary(lhs_bytes), FileContent::Binary(rhs_bytes)) => {
278+
let changed = lhs_bytes != rhs_bytes;
279+
if print_unchanged || changed {
280+
println!(
281+
"{}",
282+
style::header(&summary.path, 1, 1, "binary", use_color, background)
283+
);
284+
if changed {
285+
println!("Binary contents changed.");
286+
} else {
287+
println!("No changes.");
288+
}
289+
}
290+
}
291+
(_, FileContent::Binary(_)) | (FileContent::Binary(_), _) => {
292+
// We're diffing a binary file against a text file.
293+
println!(
294+
"{}",
295+
style::header(&summary.path, 1, 1, "binary", use_color, background)
296+
);
297+
println!("Binary contents changed.");
298+
}
299+
}
300+
}
301+
302+
/// What is the total number of nodes in `roots`?
303+
fn num_nodes(roots: &[&Syntax]) -> u32 {
304+
roots
305+
.iter()
306+
.map(|n| {
307+
1 + match n {
308+
Syntax::List {
309+
num_descendants, ..
310+
} => *num_descendants,
311+
Syntax::Atom { .. } => 0,
312+
}
313+
})
314+
.sum()
315+
}
316+
317+
fn max_num_nodes(roots_vec: &[(Vec<&Syntax>, Vec<&Syntax>)]) -> u32 {
318+
roots_vec
319+
.iter()
320+
.map(|(lhs, rhs)| num_nodes(lhs) + num_nodes(rhs))
321+
.max()
322+
.unwrap_or(0)
323+
}

src/lines.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Manipulate lines of text and groups of lines.
22
33
use crate::positions::SingleLineSpan;
4+
use serde::{Deserialize, Serialize};
45
use std::{
56
cmp::{max, Ordering},
67
fmt,
@@ -10,7 +11,7 @@ use std::{
1011
/// other numerical data.
1112
///
1213
/// Zero-indexed internally.
13-
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14+
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1415
pub struct LineNumber(pub usize);
1516

1617
impl LineNumber {

0 commit comments

Comments
 (0)