summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Oram <dev@mitmaro.ca>2020-01-26 14:19:02 -0330
committerTim Oram <dev@mitmaro.ca>2020-01-26 22:21:07 -0330
commitc954302cfff92f37f361a191de85a50f0d42653a (patch)
tree49cdd79e258b9efef1521e97e047a63e5b70416e
parentcd7052f17e75ced4dba54d03c8ded43e3fbb501f (diff)
Fix ANSI colour support
The version of curses that is used on Unix like systems does not properly support truecolor. Because of this, existing colour codes indexes were being overwritten to provide support for arbitrary custom colours. This worked fine in the terminal emulators tested on Linux, as the colours were reset back to their originals when the tool exited. However, on MacOS, the colour changes would remain after the tool exited, until the terminal was restarted. To fix this, in terminals that do not support overwriting colours, a provided colour triplet will be matched against the closest ANSI indexed colour.
-rw-r--r--.github/workflows/pull-request.yml8
-rw-r--r--appveyor.yml1
-rwxr-xr-xscripts/format.bash2
-rwxr-xr-xscripts/test.bash4
-rw-r--r--src/config/mod.rs33
-rw-r--r--src/display/color.rs219
-rw-r--r--src/display/color_mode.rs79
-rw-r--r--src/display/curses.rs171
-rw-r--r--src/display/mod.rs2
-rw-r--r--src/display/utils.rs218
-rw-r--r--src/external_editor/argument_tolkenizer.rs1
11 files changed, 642 insertions, 96 deletions
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index e693733..a3aeffe 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -10,7 +10,7 @@ jobs:
- name: Install dependencies
run: sudo apt-get install build-essential libncursesw5-dev pkg-config liblzma-dev
- name: Test
- run: cargo test
+ run: cargo test -- --test-threads=1
- name: Build
run: cargo build --release
- name: Test Run
@@ -30,7 +30,7 @@ jobs:
with:
toolchain: stable
- name: Test
- run: cargo test
+ run: cargo test -- --test-threads=1
- name: Build
run: cargo build --release
- name: Test Run
@@ -41,8 +41,8 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Test
- run: cargo test --target x86_64-pc-windows-msvc --release
+ run: cargo test --target x86_64-pc-windows-msvc --release -- --test-threads=1
- name: Build
run: cargo build --target x86_64-pc-windows-msvc --release
- name: Test Run
- run: cargo run --target x86_64-pc-windows-msvc --release -- --version \ No newline at end of file
+ run: cargo run --target x86_64-pc-windows-msvc --release -- --version
diff --git a/appveyor.yml b/appveyor.yml
index 9366b58..549419b 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -23,6 +23,7 @@ install:
test_script:
- if [%APPVEYOR_REPO_TAG%]==[false] (
+ cargo test --target x86_64-pc-windows-msvc -- --test-threads=1 &&
cargo run --target x86_64-pc-windows-msvc --release -- --version
)
diff --git a/scripts/format.bash b/scripts/format.bash
index b2accaf..ba76919 100755
--- a/scripts/format.bash
+++ b/scripts/format.bash
@@ -8,4 +8,4 @@ rust_version="nightly-2019-09-13"
rustup update "$rust_version"
rustup component add rustfmt --toolchain "$rust_version"
-cargo +"$rust_version" fmt --all -- --check
+cargo +"$rust_version" fmt --all -- --check
diff --git a/scripts/test.bash b/scripts/test.bash
index 242ee6f..bc17819 100755
--- a/scripts/test.bash
+++ b/scripts/test.bash
@@ -4,5 +4,5 @@ set -e
set -u
set -o pipefail
-cargo test
-cargo test --release
+cargo test -- --test-threads=1
+cargo test --release -- --test-threads=1
diff --git a/src/config/mod.rs b/src/config/mod.rs
index f786701..b5ea7b4 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -4,7 +4,6 @@ mod utils;
use crate::config::theme::Theme;
use crate::config::utils::{editor_from_env, get_bool, get_color, get_input, get_string, open_git_config};
use crate::display::color::Color;
-use std::convert::TryFrom;
#[derive(Clone, Debug)]
pub(crate) struct Config {
@@ -45,25 +44,29 @@ impl Config {
let git_config = open_git_config()?;
Ok(Config {
theme: Theme {
- color_foreground: get_color(&git_config, "interactive-rebase-tool.foregroundColor", Color::White)?,
+ color_foreground: get_color(&git_config, "interactive-rebase-tool.foregroundColor", Color::Default)?,
color_background: get_color(&git_config, "interactive-rebase-tool.backgroundColor", Color::Default)?,
color_selected_background: get_color(
&git_config,
"interactive-rebase-tool.selectedBackgroundColor",
- Color::try_from("35,35,40").unwrap(),
+ Color::Index(237),
)?,
- color_indicator: get_color(&git_config, "interactive-rebase-tool.indicatorColor", Color::Cyan)?,
- color_action_break: get_color(&git_config, "interactive-rebase-tool.breakColor", Color::White)?,
- color_action_drop: get_color(&git_config, "interactive-rebase-tool.dropColor", Color::Red)?,
- color_action_edit: get_color(&git_config, "interactive-rebase-tool.editColor", Color::Blue)?,
- color_action_exec: get_color(&git_config, "interactive-rebase-tool.execColor", Color::White)?,
- color_action_fixup: get_color(&git_config, "interactive-rebase-tool.fixupColor", Color::Magenta)?,
- color_action_pick: get_color(&git_config, "interactive-rebase-tool.pickColor", Color::Green)?,
- color_action_reword: get_color(&git_config, "interactive-rebase-tool.rewordColor", Color::Yellow)?,
- color_action_squash: get_color(&git_config, "interactive-rebase-tool.squashColor", Color::Cyan)?,
- color_diff_add: get_color(&git_config, "interactive-rebase-tool.diffAddColor", Color::Green)?,
- color_diff_change: get_color(&git_config, "interactive-rebase-tool.diffChangeColor", Color::Yellow)?,
- color_diff_remove: get_color(&git_config, "interactive-rebase-tool.diffRemoveColor", Color::Red)?,
+ color_indicator: get_color(&git_config, "interactive-rebase-tool.indicatorColor", Color::LightCyan)?,
+ color_action_break: get_color(&git_config, "interactive-rebase-tool.breakColor", Color::LightWhite)?,
+ color_action_drop: get_color(&git_config, "interactive-rebase-tool.dropColor", Color::LightRed)?,
+ color_action_edit: get_color(&git_config, "interactive-rebase-tool.editColor", Color::LightBlue)?,
+ color_action_exec: get_color(&git_config, "interactive-rebase-tool.execColor", Color::LightWhite)?,
+ color_action_fixup: get_color(&git_config, "interactive-rebase-tool.fixupColor", Color::LightMagenta)?,
+ color_action_pick: get_color(&git_config, "interactive-rebase-tool.pickColor", Color::LightGreen)?,
+ color_action_reword: get_color(&git_config, "interactive-rebase-tool.rewordColor", Color::LightYellow)?,
+ color_action_squash: get_color(&git_config, "interactive-rebase-tool.squashColor", Color::LightCyan)?,
+ color_diff_add: get_color(&git_config, "interactive-rebase-tool.diffAddColor", Color::LightGreen)?,
+ color_diff_change: get_color(
+ &git_config,
+ "interactive-rebase-tool.diffChangeColor",
+ Color::LightYellow,
+ )?,
+ color_diff_remove: get_color(&git_config, "interactive-rebase-tool.diffRemoveColor", Color::LightRed)?,
character_vertical_spacing: get_string(
&git_config,
"interactive-rebase-tool.verticalSpacingCharacter",
diff --git a/src/display/color.rs b/src/display/color.rs
index c311431..5e8fbb9 100644
--- a/src/display/color.rs
+++ b/src/display/color.rs
@@ -2,15 +2,24 @@ use std::convert::TryFrom;
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum Color {
- White,
- Black,
- Blue,
- Cyan,
- Green,
- Magenta,
- Red,
- Yellow,
+ LightWhite,
+ LightBlack,
+ LightBlue,
+ LightCyan,
+ LightGreen,
+ LightMagenta,
+ LightRed,
+ LightYellow,
+ DarkWhite,
+ DarkBlack,
+ DarkBlue,
+ DarkCyan,
+ DarkGreen,
+ DarkMagenta,
+ DarkRed,
+ DarkYellow,
Default,
+ Index(i16),
RGB { red: i16, green: i16, blue: i16 },
}
@@ -19,34 +28,54 @@ impl TryFrom<&str> for Color {
fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
- "black" => Ok(Color::Black),
- "blue" => Ok(Color::Blue),
- "cyan" => Ok(Color::Cyan),
- "green" => Ok(Color::Green),
- "magenta" => Ok(Color::Magenta),
- "red" => Ok(Color::Red),
- "white" => Ok(Color::White),
- "yellow" => Ok(Color::Yellow),
+ "black" => Ok(Color::LightBlack),
+ "blue" => Ok(Color::LightBlue),
+ "cyan" => Ok(Color::LightCyan),
+ "green" => Ok(Color::LightGreen),
+ "magenta" => Ok(Color::LightMagenta),
+ "red" => Ok(Color::LightRed),
+ "white" => Ok(Color::LightWhite),
+ "yellow" => Ok(Color::LightYellow),
+ "light black" => Ok(Color::LightBlack),
+ "light blue" => Ok(Color::LightBlue),
+ "light cyan" => Ok(Color::LightCyan),
+ "light green" => Ok(Color::LightGreen),
+ "light magenta" => Ok(Color::LightMagenta),
+ "light red" => Ok(Color::LightRed),
+ "light white" => Ok(Color::LightWhite),
+ "light yellow" => Ok(Color::LightYellow),
+ "dark black" => Ok(Color::DarkBlack),
+ "dark blue" => Ok(Color::DarkBlue),
+ "dark cyan" => Ok(Color::DarkCyan),
+ "dark green" => Ok(Color::DarkGreen),
+ "dark magenta" => Ok(Color::DarkMagenta),
+ "dark red" => Ok(Color::DarkRed),
+ "dark white" => Ok(Color::DarkWhite),
+ "dark yellow" => Ok(Color::DarkYellow),
"transparent" | "-1" => Ok(Color::Default),
_ => {
let matches: Vec<&str> = s.split(',').collect();
- if matches.len() == 3 {
- let red = matches.get(0).unwrap().parse::<i16>().unwrap_or(-1);
- let green = matches.get(1).unwrap().parse::<i16>().unwrap_or(-1);
- let blue = matches.get(2).unwrap().parse::<i16>().unwrap_or(-1);
-
- if red > -1 && green > -1 && blue > -1 && red < 256 && green < 256 && blue < 256 {
- // values need to be mapped to 0-1000
- return Ok(Color::RGB {
- red: ((f64::from(red) / 255.0) * 1000.0) as i16,
- green: ((f64::from(green) / 255.0) * 1000.0) as i16,
- blue: ((f64::from(blue) / 255.0) * 1000.0) as i16,
- });
- }
- return Err(format!("Invalid color string: {}. Values must be within 0-255.", s));
+ match matches.len() {
+ 1 => {
+ let color_index = s.parse::<i16>();
+ match color_index {
+ Ok(i) if i >= 0 && i < 256 => Ok(Color::Index(i)),
+ _ => Err(format!("Invalid color value: {}", s)),
+ }
+ },
+ 3 => {
+ let red = matches.get(0).unwrap().parse::<i16>().unwrap_or(-1);
+ let green = matches.get(1).unwrap().parse::<i16>().unwrap_or(-1);
+ let blue = matches.get(2).unwrap().parse::<i16>().unwrap_or(-1);
+
+ if red > -1 && green > -1 && blue > -1 && red < 256 && green < 256 && blue < 256 {
+ return Ok(Color::RGB { red, green, blue });
+ }
+ Err(format!("Invalid color string: {}. Values must be within 0-255.", s))
+ },
+ _ => Err(format!("Invalid color value: {}", s)),
}
- Err(format!("Invalid color string: {}", s))
},
}
}
@@ -59,42 +88,131 @@ mod tests {
#[test]
fn action_try_from_str_black() {
- assert_eq!(Color::try_from("black").unwrap(), Color::Black);
+ assert_eq!(Color::try_from("black").unwrap(), Color::LightBlack);
+ }
+
+ #[test]
+ fn action_try_from_str_light_black() {
+ assert_eq!(Color::try_from("light black").unwrap(), Color::LightBlack);
+ }
+
+ #[test]
+ fn action_try_from_str_dark_black() {
+ assert_eq!(Color::try_from("dark black").unwrap(), Color::DarkBlack);
}
#[test]
fn action_try_from_str_blue() {
- assert_eq!(Color::try_from("blue").unwrap(), Color::Blue);
+ assert_eq!(Color::try_from("blue").unwrap(), Color::LightBlue);
+ }
+
+ #[test]
+ fn action_try_from_str_light_blue() {
+ assert_eq!(Color::try_from("light blue").unwrap(), Color::LightBlue);
+ }
+
+ #[test]
+ fn action_try_from_str_dark_blue() {
+ assert_eq!(Color::try_from("dark blue").unwrap(), Color::DarkBlue);
}
#[test]
fn action_try_from_str_cyan() {
- assert_eq!(Color::try_from("cyan").unwrap(), Color::Cyan);
+ assert_eq!(Color::try_from("cyan").unwrap(), Color::LightCyan);
+ }
+
+ #[test]
+ fn action_try_from_str_light_cyan() {
+ assert_eq!(Color::try_from("light cyan").unwrap(), Color::LightCyan);
+ }
+
+ #[test]
+ fn action_try_from_str_dark_cyan() {
+ assert_eq!(Color::try_from("dark cyan").unwrap(), Color::DarkCyan);
}
#[test]
fn action_try_from_str_green() {
- assert_eq!(Color::try_from("green").unwrap(), Color::Green);
+ assert_eq!(Color::try_from("green").unwrap(), Color::LightGreen);
+ }
+
+ #[test]
+ fn action_try_from_str_light_green() {
+ assert_eq!(Color::try_from("light green").unwrap(), Color::LightGreen);
+ }
+
+ #[test]
+ fn action_try_from_str_dark_green() {
+ assert_eq!(Color::try_from("dark green").unwrap(), Color::DarkGreen);
}
#[test]
fn action_try_from_str_magenta() {
- assert_eq!(Color::try_from("magenta").unwrap(), Color::Magenta);
+ assert_eq!(Color::try_from("magenta").unwrap(), Color::LightMagenta);
+ }
+
+ #[test]
+ fn action_try_from_str_light_magenta() {
+ assert_eq!(Color::try_from("light magenta").unwrap(), Color::LightMagenta);
+ }
+
+ #[test]
+ fn action_try_from_str_dark_magenta() {
+ assert_eq!(Color::try_from("dark magenta").unwrap(), Color::DarkMagenta);
}
#[test]
fn action_try_from_str_red() {
- assert_eq!(Color::try_from("red").unwrap(), Color::Red);
+ assert_eq!(Color::try_from("red").unwrap(), Color::LightRed);
+ }
+
+ #[test]
+ fn action_try_from_str_light_red() {
+ assert_eq!(Color::try_from("light red").unwrap(), Color::LightRed);
+ }
+
+ #[test]
+ fn action_try_from_str_dark_red() {
+ assert_eq!(Color::try_from("dark red").unwrap(), Color::DarkRed);
}
#[test]
fn action_try_from_str_white() {
- assert_eq!(Color::try_from("white").unwrap(), Color::White);
+ assert_eq!(Color::try_from("white").unwrap(), Color::LightWhite);
}
#[test]
fn action_try_from_str_yellow() {
- assert_eq!(Color::try_from("yellow").unwrap(), Color::Yellow);
+ assert_eq!(Color::try_from("yellow").unwrap(), Color::LightYellow);
+ }
+
+ #[test]
+ fn action_try_from_str_light_yellow() {
+ assert_eq!(Color::try_from("light yellow").unwrap(), Color::LightYellow);
+ }
+
+ #[test]
+ fn action_try_from_str_dark_yellow() {
+ assert_eq!(Color::try_from("dark yellow").unwrap(), Color::DarkYellow);
+ }
+
+ #[test]
+ fn action_try_from_color_index_minimum() {
+ assert_eq!(Color::try_from("0").unwrap(), Color::Index(0));
+ }
+
+ #[test]
+ fn action_try_from_color_index_maximum() {
+ assert_eq!(Color::try_from("255").unwrap(), Color::Index(255));
+ }
+
+ #[test]
+ fn action_try_from_color_rgb_color() {
+ assert_eq!(Color::try_from("100,101,102").unwrap(), Color::RGB {
+ red: 100,
+ green: 101,
+ blue: 102
+ });
}
#[test]
@@ -170,7 +288,26 @@ mod tests {
}
#[test]
- fn action_try_from_str_invalid() {
- assert_eq!(Color::try_from("invalid").unwrap_err(), "Invalid color string: invalid");
+ fn action_try_from_color_index_invalid_upper_limit() {
+ assert_eq!(Color::try_from("256").unwrap_err(), "Invalid color value: 256");
+ }
+
+ #[test]
+ fn action_try_from_color_index_invalid_lower_limit() {
+ // -1 is transparent/default and a valid value
+ assert_eq!(Color::try_from("-2").unwrap_err(), "Invalid color value: -2");
+ }
+
+ #[test]
+ fn action_try_from_str_invalid_single_value() {
+ assert_eq!(Color::try_from("invalid").unwrap_err(), "Invalid color value: invalid");
+ }
+
+ #[test]
+ fn action_try_from_str_invalid_multiple_value() {
+ assert_eq!(
+ Color::try_from("invalid,invalid").unwrap_err(),
+ "Invalid color value: invalid,invalid"
+ );
}
}
diff --git a/src/display/color_mode.rs b/src/display/color_mode.rs
new file mode 100644
index 0000000..77f7ace
--- /dev/null
+++ b/src/display/color_mode.rs
@@ -0,0 +1,79 @@
+use crate::display::color_mode::ColorMode::{EightBit, FourBit, TrueColor};
+
+#[derive(Debug, PartialEq)]
+pub(super) enum ColorMode {
+ TwoTone,
+ ThreeBit,
+ FourBit,
+ EightBit,
+ TrueColor,
+}
+
+impl ColorMode {
+ pub(super) fn has_minimum_four_bit_color(&self) -> bool {
+ *self == FourBit || *self == EightBit || *self == TrueColor
+ }
+
+ pub(super) fn has_true_color(&self) -> bool {
+ *self == TrueColor
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::display::color_mode::ColorMode;
+
+ #[test]
+ fn color_mode_has_minimum_four_bit_color_two_tone() {
+ assert!(!ColorMode::TwoTone.has_minimum_four_bit_color());
+ }
+
+ #[test]
+ fn color_mode_has_minimum_four_bit_color_three_bit() {
+ assert!(!ColorMode::ThreeBit.has_minimum_four_bit_color());
+ }
+
+ #[test]
+ fn color_mode_has_minimum_four_bit_color_four_bit() {
+ assert!(ColorMode::FourBit.has_minimum_four_bit_color());
+ }
+
+ #[test]
+ fn color_mode_has_minimum_four_bit_color_eight_bit() {
+ assert!(ColorMode::EightBit.has_minimum_four_bit_color());
+ }
+
+ #[test]
+ fn color_mode_has_minimum_four_bit_color_true_color() {
+ assert!(ColorMode::TrueColor.has_minimum_four_bit_color());
+ }
+ #[test]
+ fn color_mode_has_true_color_two_tone() {
+ assert!(!ColorMode::TwoTone.has_true_color());
+ }
+
+ #[test]
+ fn color_mode_has_true_color_three_bit() {
+ assert!(!ColorMode::ThreeBit.has_true_color());
+ }
+
+ #[test]
+ fn color_mode_has_true_color_four_bit() {
+ assert!(!ColorMode::FourBit.has_true_color());
+ }
+
+ #[test]
+ fn color_mode_has_true_color_eight_bit() {
+ assert!(!ColorMode::EightBit.has_true_color());
+ }
+
+ #[test]
+ fn color_mode_has_true_color_true_color() {
+ assert!(ColorMode::TrueColor.has_true_color());
+ }
+
+ #[test]
+ fn color_mode_equals_other_color_mode() {
+ assert!(ColorMode::TrueColor == ColorMode::TrueColor);
+ }
+}
diff --git a/src/display/curses.rs b/src/display/curses.rs
index c11220c..53e8233 100644
--- a/src/display/curses.rs
+++ b/src/display/curses.rs
@@ -1,4 +1,6 @@
use crate::display::color::Color;
+use crate::display::color_mode::ColorMode;
+use crate::display::utils::detect_color_mode;
use pancurses::{
chtype,
Input,
@@ -12,14 +14,13 @@ use pancurses::{
COLOR_YELLOW,
};
use std::collections::HashMap;
-use std::env::var;
pub(crate) struct Curses {
- color_lookup: HashMap<(i16, i16, i16), i16>,
color_index: i16,
+ color_lookup: HashMap<(i16, i16, i16), i16>,
+ color_mode: ColorMode,
color_pair_index: i16,
window: pancurses::Window,
- selected_line_enabled: bool,
}
impl Curses {
@@ -33,45 +34,151 @@ impl Curses {
let has_colors = pancurses::has_colors();
if has_colors {
pancurses::start_color();
- }
- pancurses::use_default_colors();
+ pancurses::use_default_colors();
- // pair zero should always be default
- pancurses::init_pair(0, -1, -1);
+ // pair zero should always be default
+ pancurses::init_pair(0, -1, -1);
+ }
- let number_of_colors = pancurses::COLORS() as usize;
+ let color_mode = if has_colors {
+ detect_color_mode(pancurses::COLORS() as i16)
+ }
+ else {
+ ColorMode::TwoTone
+ };
Self {
+ color_index: 16, // we only create new colors in true color mode
window,
+ color_pair_index: 16, // skip the default color pairs
color_lookup: HashMap::new(),
- color_index: 8,
- color_pair_index: 1,
- // Terminal.app on MacOS doesn't not properly support the color pairs needed for selected line
- selected_line_enabled: number_of_colors > 16 && var("TERM_PROGRAM").unwrap_or_default() != "Apple_Terminal",
+ color_mode,
}
}
- fn init_color(&mut self, color: Color) -> i16 {
+ fn init_color(&mut self, red: i16, green: i16, blue: i16) -> i16 {
+ match self.color_lookup.get(&(red, green, blue)) {
+ Some(index) => *index,
+ None => {
+ let index = self.color_index;
+ self.color_index += 1;
+ pancurses::init_color(
+ index,
+ // convert from 0-255 range to 0 - 1000
+ ((f64::from(red) / 255.0) * 1000.0) as i16,
+ ((f64::from(green) / 255.0) * 1000.0) as i16,
+ ((f64::from(blue) / 255.0) * 1000.0) as i16,
+ );
+ self.color_lookup.insert((red, green, blue), index);
+ index
+ },
+ }
+ }
+
+ // Modified version from gyscos/cursive (https://github.com/gyscos/cursive)
+ // Copyright (c) 2015 Alexandre Bury - MIT License
+ fn find_color(&mut self, color: Color) -> i16 {
match color {
- Color::Black => COLOR_BLACK,
- Color::Blue => COLOR_BLUE,
- Color::Cyan => COLOR_CYAN,
- Color::Green => COLOR_GREEN,
- Color::Magenta => COLOR_MAGENTA,
- Color::Red => COLOR_RED,
- Color::Yellow => COLOR_YELLOW,
- Color::White => COLOR_WHITE,
Color::Default => -1,
- Color::RGB { red, green, blue } => {
- match self.color_lookup.get(&(red, green, blue)) {
- Some(index) => *index,
- None => {
- pancurses::init_color(self.color_index, red, green, blue);
- let index = self.color_index;
- self.color_index += 1;
- index
- },
+ Color::LightBlack => COLOR_BLACK,
+ Color::LightBlue => COLOR_BLUE,
+ Color::LightCyan => COLOR_CYAN,
+ Color::LightGreen => COLOR_GREEN,
+ Color::LightMagenta => COLOR_MAGENTA,
+ Color::LightRed => COLOR_RED,
+ Color::LightYellow => COLOR_YELLOW,
+ Color::LightWhite => COLOR_WHITE,
+ // for dark colors, just the light color when there isn't deep enough color support
+ Color::DarkBlack => {
+ if self.color_mode.has_minimum_four_bit_color() {
+ COLOR_BLACK + 8
+ }
+ else {
+ COLOR_BLACK
+ }
+ },
+ Color::DarkBlue => {
+ if self.color_mode.has_minimum_four_bit_color() {
+ COLOR_BLUE + 8
+ }
+ else {
+ COLOR_BLUE
+ }
+ },
+ Color::DarkCyan => {
+ if self.color_mode.has_minimum_four_bit_color() {
+ COLOR_CYAN + 8
+ }
+ else {
+ COLOR_CYAN
+ }
+ },
+ Color::DarkGreen => {
+ if self.color_mode.has_minimum_four_bit_color() {
+ COLOR_GREEN + 8
}
+ else {
+ COLOR_GREEN
+ }
+ },
+ Color::DarkMagenta => {
+ if self.color_mode.has_minimum_four_bit_color() {
+ COLOR_MAGENTA + 8
+ }
+ else {
+ COLOR_MAGENTA
+ }
+ },
+ Color::DarkRed => {
+ if self.color_mode.has_minimum_four_bit_color() {
+ COLOR_RED + 8
+ }
+ else {
+ COLOR_RED
+ }
+ },
+ Color::DarkYellow => {
+ if self.color_mode.has_minimum_four_bit_color() {
+ COLOR_YELLOW + 8
+ }
+ else {
+ COLOR_YELLOW
+ }
+ },
+ Color::DarkWhite => {
+ if self.color_mode.has_minimum_four_bit_color() {
+ COLOR_WHITE + 8
+ }
+ else {
+ COLOR_WHITE
+ }
+ },
+ // for indexed colored we assume 8bit color
+ Color::Index(i) => i,
+ Color::RGB { red, green, blue } if self.color_mode.has_true_color() => self.init_color(red, green, blue),
+ Color::RGB { red, green, blue } if self.color_mode.has_minimum_four_bit_color() => {
+ // If red, green and blue are equal then we assume a grey scale color
+ // shades less than 8 should go to pure black, while shades greater than 247 should go to pure white
+ if red == green && green == blue && red >= 8 && red < 247 {
+ // The grayscale palette says the colors 232 + n are: (red = green = blue) = 8 + 10 * n
+ // With 0 <= n <= 23. This gives: (red - 8) / 10 = n
+ let n = (red - 8) / 10;
+ 232 + n
+ }
+ else {
+ // Generic RGB
+ let r = 6 * red / 256;
+ let g = 6 * green / 256;
+ let b = 6 * blue / 256;
+ 16 + 36 * r + 6 * g + b
+ }
+ },
+ Color::RGB { red, green, blue } => {
+ // Have to hack it down to 8 colors.
+ let r = if red > 127 { 1 } else { 0 };
+ let g = if green > 127 { 1 } else { 0 };
+ let b = if blue > 127 { 1 } else { 0 };
+ (r + 2 * g + 4 * b) as i16
},
}
}
@@ -79,7 +186,7 @@ impl Curses {
fn init_color_pair(&mut self, foreground: Color, background: Color) -> chtype {
let index = self.color_pair_index;
self.color_pair_index += 1;
- pancurses::init_pair(index, self.init_color(foreground), self.init_color(background));
+ pancurses::init_pair(index, self.find_color(foreground), self.find_color(background));
// curses seems to init a pair for i16 but read with u64
pancurses::COLOR_PAIR(index as chtype)
}
@@ -92,7 +199,7 @@ impl Curses {
) -> (chtype, chtype)
{
let standard_pair = self.init_color_pair(foreground, background);
- if self.selected_line_enabled {
+ if self.color_mode.has_minimum_four_bit_color() {
return (standard_pair, self.init_color_pair(foreground, selected_background));
}
// when there is not enough color pairs to support selected
diff --git a/src/display/mod.rs b/src/display/mod.rs
index 556232e..1c9ba86 100644
--- a/src/display/mod.rs
+++ b/src/display/mod.rs
@@ -1,7 +1,9 @@
pub(crate) mod color;
mod color_manager;
+mod color_mode;
pub(crate) mod curses;
pub(crate) mod display_color;
+mod utils;
use crate::config::Config;
use crate::display::color_manager::ColorManager;
diff --git a/src/display/utils.rs b/src/display/utils.rs
new file mode 100644
index 0000000..2c1a5bc
--- /dev/null
+++ b/src/display/utils.rs
@@ -0,0 +1,218 @@
+use crate::display::color_mode::ColorMode;
+use std::env::var;
+
+pub(super) fn detect_color_mode(number_of_colors: i16) -> ColorMode {
+ // assume 16 colors on Windows
+ if cfg!(windows) {
+ // TODO Windows 10 build 14931 and higher support true color
+ // TODO Windows 10 build 10586 and higher support 256 colors
+ return ColorMode::ThreeBit;
+ }
+
+ // respect COLORTERM being truecolor or 24bit
+ if let Ok(color_term) = var("COLORTERM") {
+ if color_term == "truecolor" || color_term == "24bit" {
+ return ColorMode::TrueColor;
+ }
+ }
+
+ // VTE based terms should all be setting COLORTERM, but just in case
+ if let Ok(vte_version) = var("VTE_VERSION") {
+ let vte_version = vte_version.parse::<i32>().unwrap_or(0);
+
+ if vte_version >= 3600 {
+ // version 0.36.00
+ return ColorMode::TrueColor;
+ }
+ else if vte_version > 0 {
+ return ColorMode::EightBit;
+ }
+ }
+
+ // Apple has some special cases
+ if let Ok(term_program) = var("TERM_PROGRAM") {
+ // Apple Terminal sometimes pretends to support TrueColor, but it's 8bit
+ // TODO iTerm does support truecolor, but does not support the way that colors are set
+ if term_program == "Apple_Terminal" || term_program == "iTerm.app" {
+ return ColorMode::EightBit;
+ }
+ }
+
+ // Assume terminals with `-256` are 8bit, this is technically what curses does internally
+ if let Ok(term) = var("TERM") {
+ if term.contains("-256") {
+ return ColorMode::EightBit;
+ }
+ }
+
+ // at this point there is no way to detect truecolor support, so the best we can get is 8bit
+ match number_of_colors {
+ n if n >= 256 => ColorMode::EightBit,
+ n if n >= 16 => ColorMode::FourBit,
+ n if n >= 8 => ColorMode::ThreeBit,
+ _ => ColorMode::TwoTone,
+ }
+}
+
+#[cfg(all(windows, test))]
+mod tests {
+ use crate::display::color_mode::ColorMode;
+ use crate::display::utils::detect_color_mode;
+
+ #[test]
+ fn detect_color_mode_windows() {
+ assert_eq!(detect_color_mode(2), ColorMode::ThreeBit);
+ }
+}
+
+#[cfg(all(unix, test))]
+mod tests {
+ use crate::display::color_mode::ColorMode;
+ use crate::display::utils::detect_color_mode;
+ use std::env::{remove_var, set_var};
+
+ fn clear_env() {
+ remove_var("COLORTERM");
+ remove_var("VTE_VERSION");
+ remove_var("TERM_PROGRAM");
+ remove_var("TERM");
+ }
+
+ #[test]
+ fn detect_color_mode_no_env_2_colors() {
+ clear_env();
+ assert_eq!(detect_color_mode(2), ColorMode::TwoTone);
+ }
+
+ #[test]
+ fn detect_color_mode_no_env_8_colors() {
+ clear_env();
+ assert_eq!(detect_color_mode(8), ColorMode::ThreeBit);
+ }
+
+ #[test]
+ fn detect_color_mode_no_env_less_8_colors() {
+ clear_env();
+ assert_eq!(detect_color_mode(7), ColorMode::TwoTone);
+ }
+
+ #[test]
+ fn detect_color_mode_no_env_16_colors() {
+ clear_env();
+ assert_eq!(detect_color_mode(16), ColorMode::FourBit);
+ }
+
+ #[test]
+ fn detect_color_mode_no_env_less_16_colors() {
+ clear_env();
+ assert_eq!(detect_color_mode(15), ColorMode::ThreeBit);
+ }
+
+ #[test]
+ fn detect_color_mode_no_env_256_colors() {
+ clear_env();
+ assert_eq!(detect_color_mode(256), ColorMode::EightBit);
+ }
+
+ #[test]
+ fn detect_color_mode_no_env_less_256_colors() {
+ clear_env();
+ assert_eq!(detect_color_mode(255), ColorMode::FourBit);
+ }
+
+ #[test]
+ fn detect_color_mode_no_env_more_256_colors() {
+ clear_env();
+ assert_eq!(detect_color_mode(257), ColorMode::EightBit);
+ }
+
+ #[test]
+ fn detect_color_mode_term_env_no_256() {
+ clear_env();
+ set_var("TERM", "XTERM");
+ assert_eq!(detect_color_mode(0), ColorMode::TwoTone);
+ }
+
+ #[test]
+ fn detect_color_mode_term_env_with_256() {
+ clear_env();
+ set_var("TERM", "XTERM-256");
+ assert_eq!(detect_color_mode(0), ColorMode::EightBit);
+ }
+
+ #[test]
+ fn detect_color_mode_term_program_env_apple_terminal() {
+ clear_env();
+ set_var("TERM_PROGRAM", "Apple_Terminal");
+ assert_eq!(detect_color_mode(0), ColorMode::EightBit);
+ }
+
+ #[test]
+ fn detect_color_mode_term_program_env_iterm() {
+ clear_env();
+ set_var("TERM_PROGRAM", "iTerm.app");
+ assert_eq!(detect_color_mode(0), ColorMode::EightBit);
+ }
+
+ #[test]
+ fn detect_color_mode_term_program_env_other() {
+ clear_env();
+ set_var("TERM_PROGRAM", "other");
+ assert_eq!(detect_color_mode(0), ColorMode::TwoTone);
+ }
+
+ #[test]
+ fn detect_color_mode_vte_version_0_36_00() {
+ clear_env();
+ set_var("VTE_VERSION", "3600");
+ assert_eq!(detect_color_mode(0), ColorMode::TrueColor);
+ }
+
+ #[test]
+ fn detect_color_mode_vte_version_greater_0_36_00() {
+ clear_env();
+ set_var("VTE_VERSION", "3601");
+ assert_eq!(detect_color_mode(0), ColorMode::TrueColor);
+ }
+
+ #[test]
+ fn detect_color_mode_vte_version_less_0_36_00() {
+ clear_env();
+ set_var("VTE_VERSION", "1");
+ assert_eq!(detect_color_mode(0), ColorMode::EightBit);
+ }
+
+ #[test]
+ fn detect_color_mode_vte_version_0() {
+ clear_env();
+ set_var("VTE_VERSION", "0");
+ assert_eq!(detect_color_mode(0), ColorMode::TwoTone);
+ }
+ #[test]
+ fn detect_color_mode_vte_version_invalid() {
+ clear_env();
+ set_var("VTE_VERSION", "invalid");
+ assert_eq!(detect_color_mode(0), ColorMode::TwoTone);
+ }
+
+ #[test]
+ fn detect_color_mode_colorterm_env_is_truecolor() {
+ clear_env();
+ set_var("COLORTERM", "truecolor");
+ assert_eq!(detect_color_mode(0), ColorMode::TrueColor);
+ }
+
+ #[test]
+ fn detect_color_mode_colorterm_env_is_24bit() {
+ clear_env();
+ set_var("COLORTERM", "24bit");
+ assert_eq!(detect_color_mode(0), ColorMode::TrueColor);
+ }
+
+ #[test]
+ fn detect_color_mode_colorterm_env_is_other() {
+ clear_env();
+ set_var("COLORTERM", "other");
+ assert_eq!(detect_color_mode(0), ColorMode::TwoTone);
+ }
+}
diff --git a/src/external_editor/argument_tolkenizer.rs b/src/external_editor/argument_tolkenizer.rs
index e3c0e54..d335012 100644
--- a/src/external_editor/argument_tolkenizer.rs
+++ b/src/external_editor/argument_tolkenizer.rs
@@ -18,7 +18,6 @@ pub(super) fn tolkenize(input: &str) -> Option<Vec<String>> {
let mut tokens = vec![];
for (i, c) in input.chars().enumerate() {
- // eprintln!("'{}' '{}'", i, c);
match state {
State::Normal => {
if c == '\\' {