Skip to content

Commit

Permalink
feat: Highlight WS errors based on git/gex config (#45)
Browse files Browse the repository at this point in the history
First checks the gex config, then if nothing is found it falls back on
the git config.

Close #45
  • Loading branch information
Piturnah committed Jul 19, 2023
1 parent e47913d commit ecab032
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 13 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## [Unreleased](https://github.com/Piturnah/gex/compare/v0.5.0...main)
### Added
- Trailing whitespace detection based on setting in either gitconfig or gex config ([#45](https://github.com/Piturnah/gex/issues/45))
- New config option: `options.ws_error_highlight`
- Arbitrary process execution with <kbd>!</kbd> ([#25](https://github.com/Piturnah/gex/issues/25))
### Changed
- Item expansion no longer resets on updating status ([#39](https://github.com/Piturnah/gex/issues/39))
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ auto_expand_files = false
auto_expand_hunks = true
lookahead_lines = 5
truncate_lines = true # `false` is not recommended - see #37
ws_error_highlight = "new" # override git's diff.wsErrorHighlight
```

## Versioning
Expand Down
85 changes: 84 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Gex configuration.
#![allow(clippy::derivable_impls)]
use std::{fs, path::PathBuf};
use std::{fs, path::PathBuf, str::FromStr};

use anyhow::{Context, Result};
use clap::Parser;
Expand Down Expand Up @@ -36,6 +36,15 @@ pub struct Options {
pub auto_expand_hunks: bool,
pub lookahead_lines: usize,
pub truncate_lines: bool,
pub ws_error_highlight: WsErrorHighlight,
}

#[derive(Deserialize, Clone, Copy, Debug)]
#[serde(try_from = "String")]
pub struct WsErrorHighlight {
pub old: bool,
pub new: bool,
pub context: bool,
}

impl Default for Options {
Expand All @@ -45,6 +54,7 @@ impl Default for Options {
auto_expand_hunks: true,
lookahead_lines: 5,
truncate_lines: true,
ws_error_highlight: WsErrorHighlight::default(),
}
}
}
Expand Down Expand Up @@ -79,3 +89,76 @@ impl Config {
Ok(Some((config, unused_keys)))
}
}

impl WsErrorHighlight {
/// The default value defined by git.
const GIT_DEFAULT: Self = Self {
old: false,
new: true,
context: false,
};
const NONE: Self = Self {
old: false,
new: false,
context: false,
};
const ALL: Self = Self {
old: true,
new: true,
context: true,
};
}

impl Default for WsErrorHighlight {
/// If none was provided by the gex config, we will look in the git config. If we couldn't get
/// that one then we'll just provide `Self::GIT_DEFAULT`.
fn default() -> Self {
let Ok(Ok(git_config)) = git2::Config::open_default().map(|mut config| config.snapshot())
else {
return Self::GIT_DEFAULT;
};

let Ok(value) = git_config.get_str("diff.wsErrorHighlight") else {
return Self::GIT_DEFAULT;
};

Self::from_str(value).unwrap_or(Self::GIT_DEFAULT)
}
}

// NOTE: If anyone is reading this, do you happen to know why this impl is even needed? Really
// feels like this should be provided by default is `FromStr` is implemented on the type.
impl TryFrom<String> for WsErrorHighlight {
type Error = anyhow::Error;
fn try_from(s: String) -> std::result::Result<Self, Self::Error> {
Self::from_str(&s)
}
}

impl FromStr for WsErrorHighlight {
type Err = anyhow::Error;
/// Highlight whitespace errors in the context, old or new lines of the diff. Multiple values
/// are separated by by comma, none resets previous values, default reset the list to new and
/// all is a shorthand for old,new,context.
///
/// <https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---ws-error-highlightltkindgt>
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let mut result = Self::GIT_DEFAULT;
for opt in s.split(',') {
match opt {
"all" => result = Self::ALL,
"default" => result = Self::GIT_DEFAULT,
"none" => result = Self::NONE,
"old" => result.old = true,
"new" => result.new = true,
"context" => result.context = true,
otherwise => {
return Err(anyhow::Error::msg(format!(
"unrecognised option in `ws_error_highlight`: {otherwise}"
)))
}
}
}
Ok(result)
}
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ See https://github.com/Piturnah/gex/issues/13.", MessageType::Error);
.context("failed to leave alternate screen")?;
process::exit(0);
}
KeyCode::Char(c) => cmd.handle_input(c, &mut state, &config)?,
KeyCode::Char(c) => cmd.handle_input(c, &mut state, config)?,
_ => {}
},
};
Expand Down
71 changes: 60 additions & 11 deletions src/status.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Module relating to the Status display, including diffs of files.
use std::{
borrow::Cow,
fmt, fs,
io::{stdout, Read, Write},
process::{Command, Output, Stdio},
Expand All @@ -12,7 +13,7 @@ use git2::{ErrorCode::UnbornBranch, Repository};
use nom::{bytes::complete::take_until, IResult};

use crate::{
config::Options,
config::{Options, CONFIG},
git_process,
minibuffer::{MessageType, MiniBuffer},
parse::{self, parse_hunk_new, parse_hunk_old},
Expand Down Expand Up @@ -55,23 +56,71 @@ impl fmt::Display for Hunk {
);

if self.expanded {
let ws_error_highlight = CONFIG
.get()
.expect("config is initialised at the start of the program")
.options
.ws_error_highlight;
for line in lines {
write!(
&mut outbuf,
"\r\n{}{}",
match line.chars().next() {
Some('+') => style::SetForegroundColor(Color::DarkGreen),
Some('-') => style::SetForegroundColor(Color::DarkRed),
_ => style::SetForegroundColor(Color::Reset),
},
line
)?;
match line.chars().next() {
Some('+') => write!(
&mut outbuf,
"\r\n{}{}",
style::SetForegroundColor(Color::DarkGreen),
if ws_error_highlight.new {
format_trailing_whitespace(line)
} else {
Cow::Borrowed(line)
}
),
Some('-') => write!(
&mut outbuf,
"\r\n{}{}",
style::SetForegroundColor(Color::DarkRed),
if ws_error_highlight.old {
format_trailing_whitespace(line)
} else {
Cow::Borrowed(line)
}
),
_ => write!(
&mut outbuf,
"\r\n{}{}",
style::SetForegroundColor(Color::Reset),
if ws_error_highlight.context {
format_trailing_whitespace(line)
} else {
Cow::Borrowed(line)
}
),
}?;
}
}
write!(f, "{outbuf}")
}
}

fn format_trailing_whitespace(s: &str) -> Cow<'_, str> {
let count_trailing_whitespace = s
.bytes()
.skip(1)
.rev()
.take_while(|c| c.is_ascii_whitespace())
.count();
if count_trailing_whitespace > 0 {
Cow::Owned({
let mut line = s.to_string();
line.insert_str(
line.len() - count_trailing_whitespace,
&format!("{}", style::SetBackgroundColor(Color::Red)),
);
line
})
} else {
Cow::Borrowed(s)
}
}

impl Hunk {
pub const fn new(diff: String, expanded: bool) -> Self {
Self { diff, expanded }
Expand Down

0 comments on commit ecab032

Please sign in to comment.