From a8fd3e174565faf527f0dbfea956175d692b654c Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 28 Jul 2016 05:19:34 -0700 Subject: [PATCH] Colorize diffs --- src/main.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index 65b0aeecacd92f2e328e381429622f92e12dc0bd..001241caa9e0c3fefff618a122463f5952914f6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -866,7 +866,7 @@ fn commit_status(out: &mut Output, repo: &Repository, m: &ArgMatches, do_status: } if m.is_present("verbose") { try!(writeln!(file, "{}\n{}", SCISSOR_LINE, SCISSOR_COMMENT)); - try!(write_diff(&mut file, &diff)); + try!(write_diff(&mut file, &DiffColors::plain(), &diff)); } drop(file); try!(run_editor(&config, &filename)); @@ -1043,21 +1043,94 @@ fn split_message(message: &str) -> (&str, &str) { (subject, body) } +struct DiffColors { + commit: Style, + meta: Style, + frag: Style, + func: Style, + context: Style, + old: Style, + new: Style, +} + +impl DiffColors { + fn plain() -> Self { + DiffColors { + commit: Style::new(), + meta: Style::new(), + frag: Style::new(), + func: Style::new(), + context: Style::new(), + old: Style::new(), + new: Style::new(), + } + } + + fn new(out: &Output, config: &Config) -> Result { + Ok(DiffColors { + commit: try!(out.get_color(&config, "diff", "commit", "yellow")), + meta: try!(out.get_color(&config, "diff", "meta", "bold")), + frag: try!(out.get_color(&config, "diff", "frag", "cyan")), + func: try!(out.get_color(&config, "diff", "func", "normal")), + context: try!(out.get_color(&config, "diff", "context", "normal")), + old: try!(out.get_color(&config, "diff", "old", "red")), + new: try!(out.get_color(&config, "diff", "new", "green")), + }) + } +} + fn diffstat(diff: &Diff) -> Result { let stats = try!(diff.stats()); let stats_buf = try!(stats.to_buf(git2::DIFF_STATS_FULL|git2::DIFF_STATS_INCLUDE_SUMMARY, 72)); Ok(stats_buf.as_str().unwrap().to_string()) } -fn write_diff(f: &mut W, diff: &Diff) -> Result<()> { +fn write_diff(f: &mut W, colors: &DiffColors, diff: &Diff) -> Result<()> { let mut err = Ok(()); + let normal = Style::new(); try!(diff.print(git2::DiffFormat::Patch, |_, _, l| { err = || -> Result<()> { let o = l.origin(); + let style = match o { + ' '|'=' => colors.context, + '-'|'<' => colors.old, + '+'|'>' => colors.new, + 'F' => colors.meta, + 'H' => colors.frag, + _ => normal, + }; + let obyte = [o as u8]; + let mut v = Vec::new(); if o == '+' || o == '-' || o == ' ' { - try!(f.write_all(&[o as u8])); + v.push(style.paint(&obyte[..])); + } + if o == 'H' { + // Split frag and func + let line = l.content(); + let at = &|&(_,&c): &(usize, &u8)| c == b'@'; + let not_at = &|&(_,&c): &(usize, &u8)| c != b'@'; + match line.iter().enumerate().skip_while(at).skip_while(not_at).skip_while(at).nth(1).unwrap_or((0,&b'\n')) { + (_,&c) if c == b'\n' => v.push(style.paint(&line[..line.len()-1])), + (pos,_) => { + v.push(style.paint(&line[..pos-1])); + v.push(normal.paint(" ".as_bytes())); + v.push(colors.func.paint(&line[pos..line.len()-1])); + }, + } + v.push(normal.paint("\n".as_bytes())); + } else { + // The less pager resets ANSI colors at each newline, so emit colors separately for + // each line. + for (n, line) in l.content().split(|c| *c == b'\n').enumerate() { + if n != 0 { + v.push(normal.paint("\n".as_bytes())); + } + if !line.is_empty() { + v.push(style.paint(line)); + } + } } - try!(f.write_all(l.content())); + try!(ansi_term::ANSIByteStrings(&v).write_to(f)); Ok(()) }(); err.is_ok() @@ -1086,7 +1159,7 @@ fn ensure_nl(s: &str) -> &'static str { } fn format(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> { - let config = try!(repo.config()); + let config = try!(try!(repo.config()).snapshot()); let to_stdout = m.is_present("stdout"); let no_from = m.is_present("no-from"); @@ -1134,8 +1207,15 @@ fn format(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> { let signature = mail_signature(); - let mut out : Box = if to_stdout { + if to_stdout { try!(out.auto_pager(&config, "format-patch", true)); + } + let diffcolors = if to_stdout { + try!(DiffColors::new(out, &config)) + } else { + DiffColors::plain() + }; + let mut out : Box = if to_stdout { Box::new(out) } else { Box::new(std::io::stdout()) @@ -1234,7 +1314,7 @@ fn format(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> { } try!(writeln!(out, "---")); try!(writeln!(out, "{}", stats)); - try!(write_diff(&mut out, &diff)); + try!(write_diff(&mut out, &diffcolors, &diff)); if first_mail { try!(writeln!(out, "\nbase-commit: {}", base.id())); } @@ -1247,7 +1327,7 @@ fn format(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> { fn log(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> { let config = try!(try!(repo.config()).snapshot()); try!(out.auto_pager(&config, "log", true)); - let color_commit = try!(out.get_color(&config, "diff", "commit", "yellow")); + let diffcolors = try!(DiffColors::new(out, &config)); let mut revwalk = try!(repo.revwalk()); try!(revwalk.push_ref(SHEAD_REF)); @@ -1287,7 +1367,7 @@ fn log(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> { let commit = try!(repo.find_commit(oid)); let author = commit.author(); - try!(writeln!(out, "{}", color_commit.paint(format!("commit {}", oid)))); + try!(writeln!(out, "{}", diffcolors.commit.paint(format!("commit {}", oid)))); try!(writeln!(out, "Author: {} <{}>", author.name().unwrap(), author.email().unwrap())); try!(writeln!(out, "Date: {}\n", date_822(author.when()))); for line in commit.message().unwrap().lines() { @@ -1308,7 +1388,7 @@ fn log(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> { Some(try!(try!(repo.find_commit(parent_ids[0])).tree())) }; let diff = try!(repo.diff_tree_to_tree(parent_tree.as_ref(), Some(&tree), None)); - try!(write_diff(out, &diff)); + try!(write_diff(out, &diffcolors, &diff)); } } } @@ -1446,7 +1526,7 @@ fn rebase(repo: &Repository, m: &ArgMatches) -> Result<()> { } fn req(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> { - let config = try!(repo.config()); + let config = try!(try!(repo.config()).snapshot()); let shead = try!(repo.find_reference(SHEAD_REF)); let shead_commit = try!(peel_to_commit(try!(shead.resolve()))); let stree = try!(shead_commit.tree()); @@ -1547,6 +1627,8 @@ fn req(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> { let stats = try!(diffstat(&diff)); try!(out.auto_pager(&config, "request-pull", true)); + let diffcolors = try!(DiffColors::new(out, &config)); + try!(writeln!(out, "From {} Mon Sep 17 00:00:00 2001", shead_commit.id())); try!(writeln!(out, "Message-Id: {}", message_id)); try!(writeln!(out, "From: {} <{}>", author.name().unwrap(), author_email)); @@ -1569,7 +1651,7 @@ fn req(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> { try!(writeln!(out, "{}", shortlog(&mut commits))); try!(writeln!(out, "{}", stats)); if m.is_present("patch") { - try!(write_diff(out, &diff)); + try!(write_diff(out, &diffcolors, &diff)); } try!(writeln!(out, "{}", mail_signature()));