Repository: dandavison/delta
Pull request #211 (2020-05-30 20:56:37)

Add 'raw' style attribute and fix 'omit' handling
Implements https://github.com/dandavison/delta/issues/205#issuecomment-636119494


Patch:
diff --git a/README.md b/README.md
index 1f9a0ab..d7b4701 100644
--- a/README.md
+++ b/README.md
@@ -384,15 +384,7 @@ All delta color options work the same way. There are three ways to specify a col
 </table>
 
 
-## Build delta from source
 
-You'll need to [install the rust tools](https://www.rust-lang.org/learn/get-started). Then:
-
-```
-cargo build --release
-```
-
-and use the executable found at `./target/release/delta`.
 
 
 ## Credit
diff --git a/src/config.rs b/src/config.rs
index 1db6ace..b8d60bf 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -3,8 +3,9 @@ use syntect::highlighting::{Theme, ThemeSet};
 use syntect::parsing::SyntaxSet;
 
 use crate::bat::output::PagingMode;
-use crate::cli;
+use crate::cli::{self, unreachable};
 use crate::color;
+use crate::delta::State;
 use crate::env;
 use crate::style::Style;
 use crate::theme;
@@ -38,6 +39,17 @@ pub struct Config<'a> {
     pub paging_mode: PagingMode,
 }
 
+impl<'a> Config<'a> {
+    pub fn get_style(&self, state: &State) -> &Style {
+        match state {
+            State::CommitMeta => &self.commit_style,
+            State::FileMeta => &self.file_style,
+            State::HunkHeader => &self.hunk_header_style,
+            _ => unreachable("Unreachable code reached in get_style."),
+        }
+    }
+}
+
 pub fn get_config<'a>(
     opt: cli::Opt,
     syntax_set: SyntaxSet,
diff --git a/src/delta.rs b/src/delta.rs
index e8dbb97..02a1cc6 100644
--- a/src/delta.rs
+++ b/src/delta.rs
@@ -1,11 +1,10 @@
+use std::io::BufRead;
 use std::io::Write;
 
 use bytelines::ByteLines;
 use console::strip_ansi_codes;
-use std::io::BufRead;
 use unicode_segmentation::UnicodeSegmentation;
 
-use crate::cli::unreachable;
 use crate::config::Config;
 use crate::draw;
 use crate::paint::Painter;
@@ -74,7 +73,7 @@ where
         if line.starts_with("commit ") {
             painter.paint_buffered_lines();
             state = State::CommitMeta;
-            if config.commit_style.decoration_style.is_some() {
+            if should_handle(&state, config) {
                 painter.emit()?;
                 handle_commit_meta_header_line(&mut painter, &raw_line, config)?;
                 continue;
@@ -84,7 +83,7 @@ where
             state = State::FileMeta;
         } else if (state == State::FileMeta || source == Source::DiffUnified)
             && (line.starts_with("--- ") || line.starts_with("rename from "))
-            && config.file_style.decoration_style.is_some()
+            && should_handle(&State::FileMeta, config)
         {
             minus_file = parse::get_file_path_from_file_meta_line(&line, source == Source::GitDiff);
             if source == Source::DiffUnified {
@@ -97,7 +96,7 @@ where
                 ));
             }
         } else if (line.starts_with("+++ ") || line.starts_with("rename to "))
-            && config.file_style.decoration_style.is_some()
+            && should_handle(&State::FileMeta, config)
         {
             plus_file = parse::get_file_path_from_file_meta_line(&line, source == Source::GitDiff);
             painter.set_syntax(parse::get_file_extension_from_file_meta_line_file_path(
@@ -114,9 +113,9 @@ where
         } else if line.starts_with("@@") {
             state = State::HunkHeader;
             painter.set_highlighter();
-            if config.hunk_header_style.decoration_style.is_some() {
+            if should_handle(&state, config) {
                 painter.emit()?;
-                handle_hunk_header_line(&mut painter, &line, config)?;
+                handle_hunk_header_line(&mut painter, &line, &raw_line, config)?;
                 continue;
             }
         } else if source == Source::DiffUnified && line.starts_with("Only in ")
@@ -138,7 +137,7 @@ where
 
             state = State::FileMeta;
             painter.paint_buffered_lines();
-            if config.file_style.decoration_style.is_some() {
+            if should_handle(&State::FileMeta, config) {
                 painter.emit()?;
                 handle_generic_file_meta_header_line(&mut painter, &raw_line, config)?;
                 continue;
@@ -151,7 +150,7 @@ where
             continue;
         }
 
-        if state == State::FileMeta && config.file_style.decoration_style.is_some() {
+        if state == State::FileMeta && should_handle(&State::FileMeta, config) {
             // The file metadata section is 4 lines. Skip them under non-plain file-styles.
             continue;
         } else {
@@ -165,6 +164,12 @@ where
     Ok(())
 }
 
+/// Should a handle_* function be called on this element?
+fn should_handle(state: &State, config: &Config) -> bool {
+    let style = config.get_style(state);
+    !(style.is_raw && style.decoration_style == DecorationStyle::NoDecoration)
+}
+
 /// Try to detect what is producing the input for delta.
 ///
 /// Currently can detect:
@@ -188,28 +193,33 @@ fn handle_commit_meta_header_line(
     line: &str,
     config: &Config,
 ) -> std::io::Result<()> {
+    if config.commit_style.is_omitted {
+        return Ok(());
+    }
     let decoration_ansi_term_style;
     let mut pad = false;
     let draw_fn = match config.commit_style.decoration_style {
-        Some(DecorationStyle::Box(style)) => {
+        DecorationStyle::Box(style) => {
             pad = true;
             decoration_ansi_term_style = style;
             draw::write_boxed_with_line
         }
-        Some(DecorationStyle::Underline(style)) => {
+        DecorationStyle::Underline(style) => {
             decoration_ansi_term_style = style;
             draw::write_underlined
         }
-        Some(DecorationStyle::Overline(style)) => {
+        DecorationStyle::Overline(style) => {
             decoration_ansi_term_style = style;
             draw::write_overlined
         }
-        Some(DecorationStyle::Underoverline(style)) => {
+        DecorationStyle::Underoverline(style) => {
             decoration_ansi_term_style = style;
             draw::write_underoverlined
         }
-        Some(DecorationStyle::Omit) => return Ok(()),
-        None => unreachable("Unreachable code path reached in handle_commit_meta_header_line."),
+        DecorationStyle::NoDecoration => {
+            decoration_ansi_term_style = ansi_term::Style::new();
+            draw::write_no_decoration
+        }
     };
     draw_fn(
         painter.writer,
@@ -239,29 +249,32 @@ fn handle_generic_file_meta_header_line(
     line: &str,
     config: &Config,
 ) -> std::io::Result<()> {
+    if config.file_style.is_omitted {
+        return Ok(());
+    }
     let decoration_ansi_term_style;
     let mut pad = false;
     let draw_fn = match config.file_style.decoration_style {
-        Some(DecorationStyle::Box(style)) => {
+        DecorationStyle::Box(style) => {
             pad = true;
             decoration_ansi_term_style = style;
             draw::write_boxed_with_line
         }
-        Some(DecorationStyle::Underline(style)) => {
+        DecorationStyle::Underline(style) => {
             decoration_ansi_term_style = style;
             draw::write_underlined
         }
-        Some(DecorationStyle::Overline(style)) => {
+        DecorationStyle::Overline(style) => {
             decoration_ansi_term_style = style;
             draw::write_overlined
         }
-        Some(DecorationStyle::Underoverline(style)) => {
+        DecorationStyle::Underoverline(style) => {
             decoration_ansi_term_style = style;
             draw::write_underoverlined
         }
-        Some(DecorationStyle::Omit) => return Ok(()),
-        None => {
-            unreachable("Unreachable code path reached in handle_generic_file_meta_header_line.")
+        DecorationStyle::NoDecoration => {
+            decoration_ansi_term_style = ansi_term::Style::new();
+            draw::write_no_decoration
         }
     };
     writeln!(painter.writer)?;
@@ -281,63 +294,82 @@ fn handle_generic_file_meta_header_line(
 fn handle_hunk_header_line(
     painter: &mut Painter,
     line: &str,
+    raw_line: &str,
     config: &Config,
 ) -> std::io::Result<()> {
+    if config.hunk_header_style.is_omitted {
+        return Ok(());
+    }
     let decoration_ansi_term_style;
     let draw_fn = match config.hunk_header_style.decoration_style {
-        Some(DecorationStyle::Box(style)) => {
+        DecorationStyle::Box(style) => {
             decoration_ansi_term_style = style;
             draw::write_boxed
         }
-        Some(DecorationStyle::Underline(style)) => {
+        DecorationStyle::Underline(style) => {
             decoration_ansi_term_style = style;
             draw::write_underlined
         }
-        Some(DecorationStyle::Overline(style)) => {
+        DecorationStyle::Overline(style) => {
             decoration_ansi_term_style = style;
             draw::write_overlined
         }
-        Some(DecorationStyle::Underoverline(style)) => {
+        DecorationStyle::Underoverline(style) => {
             decoration_ansi_term_style = style;
             draw::write_underoverlined
         }
-        Some(DecorationStyle::Omit) => return Ok(()),
-        None => unreachable("Unreachable code path reached in handle_hunk_header_line."),
+        DecorationStyle::NoDecoration => {
+            decoration_ansi_term_style = ansi_term::Style::new();
+            draw::write_no_decoration
+        }
     };
     let (raw_code_fragment, line_number) = parse::parse_hunk_metadata(&line);
-    let line = match prepare(raw_code_fragment, false, config) {
-        s if s.len() > 0 => format!("{} ", s),
-        s => s,
-    };
-    let lines = vec![line];
-    if !lines[0].is_empty() {
+    if config.hunk_header_style.is_raw {
         writeln!(painter.writer)?;
-        let syntax_style_sections = Painter::get_syntax_style_sections_for_lines(
-            &lines,
-            &State::HunkHeader,
-            &mut painter.highlighter,
-            &painter.config,
-        );
-        Painter::paint_lines(
-            syntax_style_sections,
-            vec![vec![(config.hunk_header_style, lines[0].as_str())]],
-            &mut painter.output_buffer,
-            config,
-            "",
-            config.null_style,
-            config.null_style,
-            Some(false),
-        );
-        painter.output_buffer.pop(); // trim newline
         draw_fn(
             painter.writer,
-            &painter.output_buffer,
+            &format!("{} ", raw_line),
             config.terminal_width,
             config.hunk_header_style.ansi_term_style,
             decoration_ansi_term_style,
         )?;
-        painter.output_buffer.clear();
-    }
+    } else {
+        let line = match prepare(raw_code_fragment, false, config) {
+            s if s.len() > 0 => format!("{} ", s),
+            s => s,
+        };
+        if !line.is_empty() {
+            writeln!(painter.writer)?;
+            let lines = vec![line];
+            let syntax_style_sections = Painter::get_syntax_style_sections_for_lines(
+                &lines,
+                &State::HunkHeader,
+                &mut painter.highlighter,
+                &painter.config,
+            );
+            Painter::paint_lines(
+                syntax_style_sections,
+                vec![vec![(config.hunk_header_style, &lines[0])]],
+                &mut painter.output_buffer,
+                config,
+                "",
+                config.null_style,
+                config.null_style,
+                Some(false),
+            );
+            painter.output_buffer.pop(); // trim newline
+            draw_fn(
+                painter.writer,
+                &painter.output_buffer,
+                config.terminal_width,
+                config.hunk_header_style.ansi_term_style,
+                decoration_ansi_term_style,
+            )?;
+            if !config.hunk_header_style.is_raw {
+                painter.output_buffer.clear()
+            };
+        }
+    };
     match config.hunk_header_style.decoration_ansi_term_style() {
         Some(style) => writeln!(painter.writer, "\n{}", style.paint(line_number))?,
         None => writeln!(painter.writer, "\n{}", line_number)?,
diff --git a/src/draw.rs b/src/draw.rs
index 67a15bb..0dae088 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -6,6 +6,17 @@ use box_drawing;
 use console::strip_ansi_codes;
 use unicode_width::UnicodeWidthStr;
 
+pub fn write_no_decoration(
+    writer: &mut dyn Write,
+    text: &str,
+    _line_width: usize, // ignored
+    _text_style: ansi_term::Style,
+    _decoration_style: ansi_term::Style,
+) -> std::io::Result<()> {
+    writeln!(writer, "{}", text)?;
+    Ok(())
+}
+
 /// Write text to stream, surrounded by a box, leaving the cursor just
 /// beyond the bottom right corner.
 pub fn write_boxed(
diff --git a/src/main.rs b/src/main.rs
index cb4a96d..9356f8d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -137,7 +137,7 @@ index f38589a..0f1bb83 100644
         writeln!(stdout, "\n\nTheme: {}\n", style.paint(theme))?;
         let config = cli::process_command_line_arguments(cli::Opt {
             theme: Some(theme.to_string()),
-            file_decoration_style: "omit".to_string(),
+            file_style: "omit".to_string(),
             hunk_header_style: "omit".to_string(),
             ..opt.clone()
         });
diff --git a/src/paint.rs b/src/paint.rs
index ca31170..b975193 100644
--- a/src/paint.rs
+++ b/src/paint.rs
@@ -400,7 +400,7 @@ mod superimpose_style_sections {
         use syntect::highlighting::FontStyle as SyntectFontStyle;
         use syntect::highlighting::Style as SyntectStyle;
 
-        use crate::style::Style;
+        use crate::style::{DecorationStyle, Style};
 
         lazy_static! {
             static ref SYNTAX_STYLE: SyntectStyle = SyntectStyle {
@@ -417,8 +417,10 @@ mod superimpose_style_sections {
                     is_underline: true,
                     ..ansi_term::Style::new()
                 },
+                is_omitted: false,
+                is_raw: false,
                 is_syntax_highlighted: true,
-                decoration_style: None,
+                decoration_style: DecorationStyle::NoDecoration,
             };
         }
         lazy_static! {
@@ -429,8 +431,10 @@ mod superimpose_style_sections {
                     is_underline: true,
                     ..ansi_term::Style::new()
                 },
+                is_omitted: false,
+                is_raw: false,
                 is_syntax_highlighted: false,
-                decoration_style: None,
+                decoration_style: DecorationStyle::NoDecoration,
             };
         }
         lazy_static! {
@@ -441,8 +445,10 @@ mod superimpose_style_sections {
                     is_underline: true,
                     ..ansi_term::Style::new()
                 },
+                is_omitted: false,
+                is_raw: false,
                 is_syntax_highlighted: true,
-                decoration_style: None,
+                decoration_style: DecorationStyle::NoDecoration,
             };
         }
 
diff --git a/src/rewrite.rs b/src/rewrite.rs
index b3e11e0..68d2b92 100644
--- a/src/rewrite.rs
+++ b/src/rewrite.rs
@@ -61,9 +61,12 @@ fn _rewrite_options_to_implement_color_only(opt: &mut cli::Opt) {
     if opt.color_only {
         opt.keep_plus_minus_markers = true;
         opt.tab_width = 0;
-        opt.commit_decoration_style = "".to_string();
-        opt.file_decoration_style = "".to_string();
-        opt.hunk_header_decoration_style = "".to_string();
+        opt.commit_style = "raw".to_string();
+        opt.commit_decoration_style = "omit".to_string();
+        opt.file_style = "raw".to_string();
+        opt.file_decoration_style = "omit".to_string();
+        opt.hunk_header_style = "raw".to_string();
+        opt.hunk_header_decoration_style = "omit".to_string();
     }
 }
 
diff --git a/src/style.rs b/src/style.rs
index 081aa5a..02c312d 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -8,8 +8,10 @@ use crate::color;
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub struct Style {
     pub ansi_term_style: ansi_term::Style,
+    pub is_omitted: bool,
+    pub is_raw: bool,
     pub is_syntax_highlighted: bool,
-    pub decoration_style: Option<DecorationStyle>,
+    pub decoration_style: DecorationStyle,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq)]
@@ -18,15 +20,17 @@ pub enum DecorationStyle {
     Underline(ansi_term::Style),
     Overline(ansi_term::Style),
     Underoverline(ansi_term::Style),
-    Omit,
+    NoDecoration,
 }
 
 impl Style {
     pub fn new() -> Self {
         Self {
             ansi_term_style: ansi_term::Style::new(),
+            is_omitted: false,
+            is_raw: false,
             is_syntax_highlighted: false,
-            decoration_style: None,
+            decoration_style: DecorationStyle::NoDecoration,
         }
     }
 
@@ -41,7 +45,7 @@ impl Style {
         decoration_style_string: Option<&str>,
         true_color: bool,
     ) -> Self {
-        let (ansi_term_style, is_syntax_highlighted) = parse_ansi_term_style(
+        let (ansi_term_style, is_omitted, is_raw, is_syntax_highlighted) = parse_ansi_term_style(
             &style_string,
             foreground_default,
             background_default,
@@ -49,10 +53,12 @@ impl Style {
         );
         let decoration_style = match decoration_style_string {
             Some(s) if s != "" => DecorationStyle::from_str(s, true_color),
-            _ => None,
+            _ => DecorationStyle::NoDecoration,
         };
         Style {
             ansi_term_style,
+            is_omitted,
+            is_raw,
             is_syntax_highlighted,
             decoration_style,
         }
@@ -76,13 +82,11 @@ impl Style {
             true_color,
         );
         if let Some(special_attribute) = special_attribute_from_style_string {
-            if let Some(decoration_style) = DecorationStyle::apply_special_decoration_attribute(
+            style.decoration_style = DecorationStyle::apply_special_decoration_attribute(
                 style.decoration_style,
                 &special_attribute,
                 true_color,
-            ) {
-                style.decoration_style = Some(decoration_style)
-            }
+            )
         }
         style
     }
@@ -112,23 +116,23 @@ impl Style {
                 .foreground;
             style.ansi_term_style.foreground = foreground_from_deprecated_arg;
             style.decoration_style = match style.decoration_style {
-                Some(DecorationStyle::Box(mut ansi_term_style)) => {
+                DecorationStyle::Box(mut ansi_term_style) => {
                     ansi_term_style.foreground = foreground_from_deprecated_arg;
-                    Some(DecorationStyle::Box(ansi_term_style))
+                    DecorationStyle::Box(ansi_term_style)
                 }
-                Some(DecorationStyle::Underline(mut ansi_term_style)) => {
+                DecorationStyle::Underline(mut ansi_term_style) => {
                     ansi_term_style.foreground = foreground_from_deprecated_arg;
-                    Some(DecorationStyle::Underline(ansi_term_style))
+                    DecorationStyle::Underline(ansi_term_style)
                 }
-                Some(DecorationStyle::Overline(mut ansi_term_style)) => {
+                DecorationStyle::Overline(mut ansi_term_style) => {
                     ansi_term_style.foreground = foreground_from_deprecated_arg;
-                    Some(DecorationStyle::Overline(ansi_term_style))
+                    DecorationStyle::Overline(ansi_term_style)
                 }
-                Some(DecorationStyle::Underoverline(mut ansi_term_style)) => {
+                DecorationStyle::Underoverline(mut ansi_term_style) => {
                     ansi_term_style.foreground = foreground_from_deprecated_arg;
-                    Some(DecorationStyle::Underoverline(ansi_term_style))
+                    DecorationStyle::Underoverline(ansi_term_style)
                 }
-                _ => style.decoration_style,
+                DecorationStyle::NoDecoration => style.decoration_style,
             };
         }
         style
@@ -136,68 +140,61 @@ impl Style {
 
     pub fn decoration_ansi_term_style(&self) -> Option<ansi_term::Style> {
         match self.decoration_style {
-            Some(DecorationStyle::Box(style)) => Some(style),
-            Some(DecorationStyle::Underline(style)) => Some(style),
-            Some(DecorationStyle::Overline(style)) => Some(style),
-            Some(DecorationStyle::Underoverline(style)) => Some(style),
-            _ => None,
+            DecorationStyle::Box(style) => Some(style),
+            DecorationStyle::Underline(style) => Some(style),
+            DecorationStyle::Overline(style) => Some(style),
+            DecorationStyle::Underoverline(style) => Some(style),
+            DecorationStyle::NoDecoration => None,
         }
     }
 }
 
 impl DecorationStyle {
-    pub fn from_str(style_string: &str, true_color: bool) -> Option<Self> {
+    pub fn from_str(style_string: &str, true_color: bool) -> Self {
         let (style_string, special_attribute) = extract_special_decoration_attribute(&style_string);
-        let special_attribute = special_attribute.unwrap_or_else(|| {
-            eprintln!(
-            "Invalid decoration style: '{}'. To specify a decoration style, you must supply one of \
-             the special attributes: 'box', 'ul', 'overline', 'underoverline', or 'omit'.",
-            style_string
-        );
-            process::exit(1);
-        });
-        let (style, is_syntax_highlighted): (ansi_term::Style, bool) =
+        let (style, is_omitted, is_raw, is_syntax_highlighted) =
             parse_ansi_term_style(&style_string, None, None, true_color);
+        if is_raw {
+            eprintln!("'raw' may not be used in a decoration style.");
+            process::exit(1);
+        };
         if is_syntax_highlighted {
-            eprintln!("'syntax' may not be used as a color name in a decoration style.");
+            eprintln!("'syntax' may not be used in a decoration style.");
             process::exit(1);
         };
-        match special_attribute.as_ref() {
-            "box" => Some(DecorationStyle::Box(style)),
-            "underline" => Some(DecorationStyle::Underline(style)),
-            "ul" => Some(DecorationStyle::Underline(style)),
-            "overline" => Some(DecorationStyle::Overline(style)),
-            "underoverline" => Some(DecorationStyle::Underoverline(style)),
-            "omit" => Some(DecorationStyle::Omit),
-            "plain" => None,
+        match special_attribute.as_deref() {
+            Some("box") => DecorationStyle::Box(style),
+            Some("underline") => DecorationStyle::Underline(style),
+            Some("ul") => DecorationStyle::Underline(style),
+            Some("overline") => DecorationStyle::Overline(style),
+            Some("underoverline") => DecorationStyle::Underoverline(style),
+            Some("omit") => DecorationStyle::NoDecoration,
+            Some("plain") => DecorationStyle::NoDecoration,
+            // TODO: Exit with error if --thing-decoration-style supplied without a decoration type
+            Some("") => DecorationStyle::NoDecoration,
+            _ if is_omitted => DecorationStyle::NoDecoration,
             _ => unreachable("Unreachable code path reached in parse_decoration_style."),
         }
     }
 
     fn apply_special_decoration_attribute(
-        decoration_style: Option<DecorationStyle>,
+        decoration_style: DecorationStyle,
         special_attribute: &str,
         true_color: bool,
-    ) -> Option<DecorationStyle> {
+    ) -> DecorationStyle {
         let ansi_term_style = match decoration_style {
-            None => ansi_term::Style::new(),
-            Some(DecorationStyle::Box(ansi_term_style)) => ansi_term_style,
-            Some(DecorationStyle::Underline(ansi_term_style)) => ansi_term_style,
-            Some(DecorationStyle::Overline(ansi_term_style)) => ansi_term_style,
-            Some(DecorationStyle::Underoverline(ansi_term_style)) => ansi_term_style,
-            Some(DecorationStyle::Omit) => ansi_term::Style::new(),
+            DecorationStyle::Box(ansi_term_style) => ansi_term_style,
+            DecorationStyle::Underline(ansi_term_style) => ansi_term_style,
+            DecorationStyle::Overline(ansi_term_style) => ansi_term_style,
+            DecorationStyle::Underoverline(ansi_term_style) => ansi_term_style,
+            DecorationStyle::NoDecoration => ansi_term::Style::new(),
         };
         match DecorationStyle::from_str(special_attribute, true_color) {
-            Some(DecorationStyle::Box(_)) => Some(DecorationStyle::Box(ansi_term_style)),
-            Some(DecorationStyle::Underline(_)) => {
-                Some(DecorationStyle::Underline(ansi_term_style))
-            }
-            Some(DecorationStyle::Overline(_)) => Some(DecorationStyle::Overline(ansi_term_style)),
-            Some(DecorationStyle::Underoverline(_)) => {
-                Some(DecorationStyle::Underoverline(ansi_term_style))
-            }
-            Some(DecorationStyle::Omit) => Some(DecorationStyle::Omit),
-            None => None,
+            DecorationStyle::Box(_) => DecorationStyle::Box(ansi_term_style),
+            DecorationStyle::Underline(_) => DecorationStyle::Underline(ansi_term_style),
+            DecorationStyle::Overline(_) => DecorationStyle::Overline(ansi_term_style),
+            DecorationStyle::Underoverline(_) => DecorationStyle::Underoverline(ansi_term_style),
+            DecorationStyle::NoDecoration => DecorationStyle::NoDecoration,
         }
     }
 }
@@ -207,10 +204,12 @@ fn parse_ansi_term_style(
     foreground_default: Option<ansi_term::Color>,
     background_default: Option<ansi_term::Color>,
     true_color: bool,
-) -> (ansi_term::Style, bool) {
+) -> (ansi_term::Style, bool, bool, bool) {
     let mut style = ansi_term::Style::new();
     let mut seen_foreground = false;
     let mut seen_background = false;
+    let mut is_omitted = false;
+    let mut is_raw = false;
     let mut is_syntax_highlighted = false;
     for word in s
         .to_lowercase()
@@ -227,8 +226,12 @@ fn parse_ansi_term_style(
             style.is_hidden = true;
         } else if word == "italic" {
             style.is_italic = true;
+        } else if word == "omit" {
+            is_omitted = true;
         } else if word == "reverse" {
             style.is_reverse = true;
+        } else if word == "raw" {
+            is_raw = true;
         } else if word == "strike" {
             style.is_strikethrough = true;
         } else if word == "ul" || word == "underline" {
@@ -268,7 +271,7 @@ fn parse_ansi_term_style(
             process::exit(1);
         }
     }
-    (style, is_syntax_highlighted)
+    (style, is_omitted, is_raw, is_syntax_highlighted)
 }
 
 /// If the style string contains a 'special decoration attribute' then extract it and return it
@@ -285,7 +288,6 @@ fn extract_special_decoration_attribute(style_string: &str) -> (String, Option<S
                 || token == "underline"
                 || token == "overline"
                 || token == "underoverline"
-                || token == "omit"
                 || token == "plain"
         });
     match special_attributes {
@@ -309,7 +311,7 @@ mod tests {
     fn test_parse_ansi_term_style() {
         assert_eq!(
             parse_ansi_term_style("", None, None, false),
-            (ansi_term::Style::new(), false)
+            (ansi_term::Style::new(), false, false, false)
         );
         assert_eq!(
             parse_ansi_term_style("red", None, None, false),
@@ -320,6 +322,8 @@ mod tests {
                     )),
                     ..ansi_term::Style::new()
                 },
+                false,
+                false,
                 false
             )
         );
@@ -335,6 +339,8 @@ mod tests {
                     )),
                     ..ansi_term::Style::new()
                 },
+                false,
+                false,
                 false
             )
         );
@@ -353,6 +359,8 @@ mod tests {
                     is_underline: true,
                     ..ansi_term::Style::new()
                 },
+                false,
+                false,
                 false
             )
         );
@@ -362,7 +370,7 @@ mod tests {
     fn test_parse_ansi_term_style_with_special_syntax_color() {
         assert_eq!(
             parse_ansi_term_style("syntax", None, None, false),
-            (ansi_term::Style::new(), true)
+            (ansi_term::Style::new(), false, false, true)
         );
         assert_eq!(
             parse_ansi_term_style("syntax italic white hidden", None, None, false),
@@ -375,6 +383,8 @@ mod tests {
                     is_hidden: true,
                     ..ansi_term::Style::new()
                 },
+                false,
+                false,
                 true
             )
         );
@@ -390,6 +400,58 @@ mod tests {
                     is_hidden: true,
                     ..ansi_term::Style::new()
                 },
+                false,
+                false,
+                true
+            )
+        );
+    }
+
+    #[test]
+    fn test_parse_ansi_term_style_with_special_omit_attribute() {
+        assert_eq!(
+            parse_ansi_term_style("omit", None, None, false),
+            (ansi_term::Style::new(), true, false, false)
+        );
+        // It doesn't make sense for omit to be combined with anything else, but it is not an error.
+        assert_eq!(
+            parse_ansi_term_style("omit syntax italic white hidden", None, None, false),
+            (
+                ansi_term::Style {
+                    background: Some(ansi_term::Color::Fixed(
+                        ansi_color_name_to_number("white").unwrap()
+                    )),
+                    is_italic: true,
+                    is_hidden: true,
+                    ..ansi_term::Style::new()
+                },
+                true,
+                false,
+                true
+            )
+        );
+    }
+
+    #[test]
+    fn test_parse_ansi_term_style_with_special_raw_attribute() {
+        assert_eq!(
+            parse_ansi_term_style("raw", None, None, false),
+            (ansi_term::Style::new(), false, true, false)
+        );
+        // It doesn't make sense for raw to be combined with anything else, but it is not an error.
+        assert_eq!(
+            parse_ansi_term_style("raw syntax italic white hidden", None, None, false),
+            (
+                ansi_term::Style {
+                    background: Some(ansi_term::Color::Fixed(
+                        ansi_color_name_to_number("white").unwrap()
+                    )),
+                    is_italic: true,
+                    is_hidden: true,
+                    ..ansi_term::Style::new()
+                },
+                false,
+                true,
                 true
             )
         );


Test patch:
diff --git a/src/tests/test_example_diffs.rs b/src/tests/test_example_diffs.rs
index dc52e37..385d2a5 100644
--- a/src/tests/test_example_diffs.rs
+++ b/src/tests/test_example_diffs.rs
@@ -2,6 +2,7 @@
 mod tests {
     use console::strip_ansi_codes;
 
+    use crate::cli;
     use crate::tests::ansi_test_utils::ansi_test_utils;
     use crate::tests::integration_test_utils::integration_test_utils;
 
@@ -204,17 +205,16 @@ mod tests {
     }
 
     #[test]
-    fn test_commit_style_plain() {
+    fn test_commit_style_raw_no_decoration() {
         let mut options = integration_test_utils::get_command_line_options();
-        options.commit_decoration_style = "".to_string();
-        // TODO: --commit-color has no effect in conjunction with --commit-style plain
+        options.commit_style = "raw".to_string();
+        options.commit_decoration_style = "omit".to_string();
         let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
         ansi_test_utils::assert_line_has_no_color(
             &output,
             0,
             "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
         );
-        let output = strip_ansi_codes(&output);
         assert!(output.contains(
             "\
 commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
@@ -222,6 +222,57 @@ commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
         ));
     }
 
+    #[test]
+    fn test_commit_decoration_style_omit() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.commit_style = "blue".to_string();
+        options.commit_decoration_style = "omit".to_string();
+        _do_test_commit_style_no_decoration(options);
+    }
+
+    #[test]
+    fn test_commit_decoration_style_empty_string() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.commit_style = "blue".to_string();
+        options.commit_decoration_style = "".to_string();
+        _do_test_commit_style_no_decoration(options);
+    }
+
+    fn _do_test_commit_style_no_decoration(options: cli::Opt) {
+        let (output, config) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
+        if false {
+            // `--commit-style xxx` is not honored yet: always behaves like xxx=raw
+            ansi_test_utils::assert_line_has_foreground_color(
+                &output,
+                0,
+                "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
+                "blue",
+                &config,
+            );
+        }
+        let output = strip_ansi_codes(&output);
+        assert!(output.contains("commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e"));
+        assert!(!output.contains("commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │"));
+        assert!(!output.contains(
+            "\
+commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
+───────────────────────────────────────────────"
+        ));
+    }
+
+    #[test]
+    fn test_commit_style_omit() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.commit_style = "omit".to_string();
+        let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
+        let output = strip_ansi_codes(&output);
+        assert!(!output.contains(
+            "\
+commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
+"
+        ));
+    }
+
     #[test]
     fn test_commit_style_box() {
         let mut options = integration_test_utils::get_command_line_options();
@@ -258,6 +309,25 @@ commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │
         ));
     }
 
+    #[test]
+    fn test_commit_style_box_raw() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.commit_style = "raw".to_string();
+        options.commit_decoration_style = "box".to_string();
+        let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
+        ansi_test_utils::assert_line_has_no_color(
+            &output,
+            1,
+            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │",
+        );
+        assert!(output.contains(
+            "\
+────────────────────────────────────────────────┐
+commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │
+────────────────────────────────────────────────┴─"
+        ));
+    }
+
     #[test]
     fn test_commit_style_underline() {
         let mut options = integration_test_utils::get_command_line_options();
@@ -287,10 +357,10 @@ commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
     }
 
     #[test]
-    fn test_file_style_plain() {
+    fn test_file_style_raw_no_decoration() {
         let mut options = integration_test_utils::get_command_line_options();
-        options.file_decoration_style = "".to_string();
-        // TODO: --file-color has no effect in conjunction with --file-style plain
+        options.file_style = "raw".to_string();
+        options.file_decoration_style = "omit".to_string();
         let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
         for (i, line) in vec![
             "diff --git a/src/align.rs b/src/align.rs",
@@ -303,7 +373,6 @@ commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
         {
             ansi_test_utils::assert_line_has_no_color(&output, 6 + i, line);
         }
-        let output = strip_ansi_codes(&output);
         assert!(output.contains(
             "
 diff --git a/src/align.rs b/src/align.rs
@@ -314,6 +383,52 @@ index 8e37a9e..6ce4863 100644
         ));
     }
 
+    #[test]
+    fn test_file_decoration_style_omit() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.file_style = "green".to_string();
+        options.file_decoration_style = "omit".to_string();
+        _do_test_file_style_no_decoration(options);
+    }
+
+    #[test]
+    fn test_file_decoration_style_empty_string() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.file_style = "green".to_string();
+        options.file_decoration_style = "".to_string();
+        _do_test_file_style_no_decoration(options);
+    }
+
+    fn _do_test_file_style_no_decoration(options: cli::Opt) {
+        let (output, config) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
+        if false {
+            // FIXME
+            ansi_test_utils::assert_line_has_foreground_color(
+                &output,
+                7,
+                "src/align.rs",
+                "green",
+                &config,
+            );
+        }
+        let output = strip_ansi_codes(&output);
+        assert!(output.contains("src/align.rs"));
+        assert!(!output.contains("src/align.rs │"));
+        assert!(!output.contains(
+            "
+src/align.rs
+────────────"
+        ));
+    }
+
+    #[test]
+    fn test_file_style_omit() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.file_style = "omit".to_string();
+        let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
+        assert!(!output.contains("src/align.rs"));
+    }
+
     #[test]
     fn test_file_style_box() {
         let mut options = integration_test_utils::get_command_line_options();
@@ -350,6 +465,21 @@ src/align.rs │
         ));
     }
 
+    #[test]
+    fn test_file_style_box_raw() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.file_style = "raw".to_string();
+        options.file_decoration_style = "box".to_string();
+        let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
+        ansi_test_utils::assert_line_has_no_color(&output, 8, "src/align.rs │");
+        assert!(output.contains(
+            "
+─────────────┐
+src/align.rs │
+─────────────┴─"
+        ));
+    }
+
     #[test]
     fn test_file_style_underline() {
         let mut options = integration_test_utils::get_command_line_options();
@@ -379,22 +509,65 @@ src/align.rs
     }
 
     #[test]
-    fn test_hunk_style_plain() {
+    fn test_hunk_header_style_raw_no_decoration() {
         let mut options = integration_test_utils::get_command_line_options();
-        options.hunk_header_decoration_style = "".to_string();
-        // TODO: --hunk-color has no effect in conjunction with --hunk-style plain
+        options.hunk_header_style = "raw".to_string();
+        options.hunk_header_decoration_style = "omit".to_string();
         let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
         ansi_test_utils::assert_line_has_no_color(
             &output,
             9,
             "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {",
         );
-        let output = strip_ansi_codes(&output);
         assert!(output.contains("@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {"));
     }
 
     #[test]
-    fn test_hunk_style_box() {
+    fn test_hunk_header_decoration_style_omit() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.hunk_header_decoration_style = "omit".to_string();
+        _do_test_hunk_header_style_no_decoration(options);
+    }
+
+    #[test]
+    fn test_hunk_header_decoration_style_empty_string() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.hunk_header_decoration_style = "".to_string();
+        _do_test_hunk_header_style_no_decoration(options);
+    }
+
+    fn _do_test_hunk_header_style_no_decoration(options: cli::Opt) {
+        let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
+        let output = strip_ansi_codes(&output);
+        assert!(output.contains(" impl<'a> Alignment<'a> {"));
+        assert!(!output.contains(" impl<'a> Alignment<'a> { │"));
+        assert!(!output.contains(
+            "
+ impl<'a> Alignment<'a> {
+─────────────────────────"
+        ));
+    }
+
+    #[test]
+    fn test_hunk_header_style_omit() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.hunk_header_style = "omit".to_string();
+        let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
+        let output = strip_ansi_codes(&output);
+        assert!(!output.contains("impl<'a> Alignment<'a> {"));
+    }
+
+    #[test]
+    fn test_hunk_header_style_empty_string() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.hunk_header_style = "".to_string();
+        let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
+        assert!(output.contains(" impl<'a> Alignment<'a> {"));
+        assert!(!output.contains("@@"));
+    }
+
+    #[test]
+    fn test_hunk_header_style_box() {
         let mut options = integration_test_utils::get_command_line_options();
         options.hunk_header_decoration_style = "white box".to_string();
         let (output, config) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
@@ -423,7 +596,27 @@ src/align.rs
     }
 
     #[test]
-    fn test_hunk_style_underline() {
+    fn test_hunk_header_style_box_raw() {
+        let mut options = integration_test_utils::get_command_line_options();
+        options.hunk_header_style = "raw".to_string();
+        options.hunk_header_decoration_style = "box".to_string();
+        let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
+        ansi_test_utils::assert_line_has_no_color(
+            &output,
+            11,
+            "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> { │",
+        );
+        assert!(output.contains(
+            "
+────────────────────────────────────────────┐
+@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> { │
+────────────────────────────────────────────┘
+"
+        ));
+    }
+
+    #[test]
+    fn test_hunk_header_style_underline() {
         let mut options = integration_test_utils::get_command_line_options();
         options.hunk_header_decoration_style = "black underline".to_string();
         let (output, config) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options);
@@ -443,7 +636,7 @@ src/align.rs
     }
 
     #[test]
-    fn test_hunk_style_box_with_syntax_highlighting() {
+    fn test_hunk_header_style_box_with_syntax_highlighting() {
         let mut options = integration_test_utils::get_command_line_options();
         options.hunk_header_style = "syntax".to_string();
         // For this test we are currently forced to disable styling of the decoration, since