Show More
@@ -0,0 +1,255 b'' | |||||
|
1 | use crate::ui::formatted; | |||
|
2 | use crate::ui::plain; | |||
|
3 | use format_bytes::write_bytes; | |||
|
4 | use hg::config::Config; | |||
|
5 | use hg::config::ConfigOrigin; | |||
|
6 | use hg::errors::HgError; | |||
|
7 | use std::collections::HashMap; | |||
|
8 | ||||
|
9 | pub type Effect = u32; | |||
|
10 | ||||
|
11 | pub type EffectsMap = HashMap<Vec<u8>, Vec<Effect>>; | |||
|
12 | ||||
|
13 | macro_rules! effects { | |||
|
14 | ($( $name: ident: $value: expr ,)+) => { | |||
|
15 | ||||
|
16 | #[allow(non_upper_case_globals)] | |||
|
17 | mod effects { | |||
|
18 | $( | |||
|
19 | pub const $name: super::Effect = $value; | |||
|
20 | )+ | |||
|
21 | } | |||
|
22 | ||||
|
23 | fn effect(name: &[u8]) -> Option<Effect> { | |||
|
24 | $( | |||
|
25 | if name == stringify!($name).as_bytes() { | |||
|
26 | Some(effects::$name) | |||
|
27 | } else | |||
|
28 | )+ | |||
|
29 | { | |||
|
30 | None | |||
|
31 | } | |||
|
32 | } | |||
|
33 | }; | |||
|
34 | } | |||
|
35 | ||||
|
36 | effects! { | |||
|
37 | none: 0, | |||
|
38 | black: 30, | |||
|
39 | red: 31, | |||
|
40 | green: 32, | |||
|
41 | yellow: 33, | |||
|
42 | blue: 34, | |||
|
43 | magenta: 35, | |||
|
44 | cyan: 36, | |||
|
45 | white: 37, | |||
|
46 | bold: 1, | |||
|
47 | italic: 3, | |||
|
48 | underline: 4, | |||
|
49 | inverse: 7, | |||
|
50 | dim: 2, | |||
|
51 | black_background: 40, | |||
|
52 | red_background: 41, | |||
|
53 | green_background: 42, | |||
|
54 | yellow_background: 43, | |||
|
55 | blue_background: 44, | |||
|
56 | purple_background: 45, | |||
|
57 | cyan_background: 46, | |||
|
58 | white_background: 47, | |||
|
59 | } | |||
|
60 | ||||
|
61 | macro_rules! default_styles { | |||
|
62 | ($( $key: expr => [$($value: expr),*],)+) => { | |||
|
63 | fn default_styles() -> EffectsMap { | |||
|
64 | use effects::*; | |||
|
65 | let mut map = HashMap::new(); | |||
|
66 | $( | |||
|
67 | map.insert($key[..].to_owned(), vec![$( $value ),*]); | |||
|
68 | )+ | |||
|
69 | map | |||
|
70 | } | |||
|
71 | }; | |||
|
72 | } | |||
|
73 | ||||
|
74 | default_styles! { | |||
|
75 | b"grep.match" => [red, bold], | |||
|
76 | b"grep.linenumber" => [green], | |||
|
77 | b"grep.rev" => [blue], | |||
|
78 | b"grep.sep" => [cyan], | |||
|
79 | b"grep.filename" => [magenta], | |||
|
80 | b"grep.user" => [magenta], | |||
|
81 | b"grep.date" => [magenta], | |||
|
82 | b"grep.inserted" => [green, bold], | |||
|
83 | b"grep.deleted" => [red, bold], | |||
|
84 | b"bookmarks.active" => [green], | |||
|
85 | b"branches.active" => [none], | |||
|
86 | b"branches.closed" => [black, bold], | |||
|
87 | b"branches.current" => [green], | |||
|
88 | b"branches.inactive" => [none], | |||
|
89 | b"diff.changed" => [white], | |||
|
90 | b"diff.deleted" => [red], | |||
|
91 | b"diff.deleted.changed" => [red, bold, underline], | |||
|
92 | b"diff.deleted.unchanged" => [red], | |||
|
93 | b"diff.diffline" => [bold], | |||
|
94 | b"diff.extended" => [cyan, bold], | |||
|
95 | b"diff.file_a" => [red, bold], | |||
|
96 | b"diff.file_b" => [green, bold], | |||
|
97 | b"diff.hunk" => [magenta], | |||
|
98 | b"diff.inserted" => [green], | |||
|
99 | b"diff.inserted.changed" => [green, bold, underline], | |||
|
100 | b"diff.inserted.unchanged" => [green], | |||
|
101 | b"diff.tab" => [], | |||
|
102 | b"diff.trailingwhitespace" => [bold, red_background], | |||
|
103 | b"changeset.public" => [], | |||
|
104 | b"changeset.draft" => [], | |||
|
105 | b"changeset.secret" => [], | |||
|
106 | b"diffstat.deleted" => [red], | |||
|
107 | b"diffstat.inserted" => [green], | |||
|
108 | b"formatvariant.name.mismatchconfig" => [red], | |||
|
109 | b"formatvariant.name.mismatchdefault" => [yellow], | |||
|
110 | b"formatvariant.name.uptodate" => [green], | |||
|
111 | b"formatvariant.repo.mismatchconfig" => [red], | |||
|
112 | b"formatvariant.repo.mismatchdefault" => [yellow], | |||
|
113 | b"formatvariant.repo.uptodate" => [green], | |||
|
114 | b"formatvariant.config.special" => [yellow], | |||
|
115 | b"formatvariant.config.default" => [green], | |||
|
116 | b"formatvariant.default" => [], | |||
|
117 | b"histedit.remaining" => [red, bold], | |||
|
118 | b"ui.addremove.added" => [green], | |||
|
119 | b"ui.addremove.removed" => [red], | |||
|
120 | b"ui.error" => [red], | |||
|
121 | b"ui.prompt" => [yellow], | |||
|
122 | b"log.changeset" => [yellow], | |||
|
123 | b"patchbomb.finalsummary" => [], | |||
|
124 | b"patchbomb.from" => [magenta], | |||
|
125 | b"patchbomb.to" => [cyan], | |||
|
126 | b"patchbomb.subject" => [green], | |||
|
127 | b"patchbomb.diffstats" => [], | |||
|
128 | b"rebase.rebased" => [blue], | |||
|
129 | b"rebase.remaining" => [red, bold], | |||
|
130 | b"resolve.resolved" => [green, bold], | |||
|
131 | b"resolve.unresolved" => [red, bold], | |||
|
132 | b"shelve.age" => [cyan], | |||
|
133 | b"shelve.newest" => [green, bold], | |||
|
134 | b"shelve.name" => [blue, bold], | |||
|
135 | b"status.added" => [green, bold], | |||
|
136 | b"status.clean" => [none], | |||
|
137 | b"status.copied" => [none], | |||
|
138 | b"status.deleted" => [cyan, bold, underline], | |||
|
139 | b"status.ignored" => [black, bold], | |||
|
140 | b"status.modified" => [blue, bold], | |||
|
141 | b"status.removed" => [red, bold], | |||
|
142 | b"status.unknown" => [magenta, bold, underline], | |||
|
143 | b"tags.normal" => [green], | |||
|
144 | b"tags.local" => [black, bold], | |||
|
145 | b"upgrade-repo.requirement.preserved" => [cyan], | |||
|
146 | b"upgrade-repo.requirement.added" => [green], | |||
|
147 | b"upgrade-repo.requirement.removed" => [red], | |||
|
148 | } | |||
|
149 | ||||
|
150 | fn parse_effect(config_key: &[u8], effect_name: &[u8]) -> Option<Effect> { | |||
|
151 | let found = effect(effect_name); | |||
|
152 | if found.is_none() { | |||
|
153 | // TODO: have some API for warnings | |||
|
154 | // TODO: handle IO errors during warnings | |||
|
155 | let stderr = std::io::stderr(); | |||
|
156 | let _ = write_bytes!( | |||
|
157 | &mut stderr.lock(), | |||
|
158 | b"ignoring unknown color/effect '{}' \ | |||
|
159 | (configured in color.{})\n", | |||
|
160 | effect_name, | |||
|
161 | config_key, | |||
|
162 | ); | |||
|
163 | } | |||
|
164 | found | |||
|
165 | } | |||
|
166 | ||||
|
167 | fn effects_from_config(config: &Config) -> EffectsMap { | |||
|
168 | let mut styles = default_styles(); | |||
|
169 | for (key, _value) in config.iter_section(b"color") { | |||
|
170 | if !key.contains(&b'.') | |||
|
171 | || key.starts_with(b"color.") | |||
|
172 | || key.starts_with(b"terminfo.") | |||
|
173 | { | |||
|
174 | continue; | |||
|
175 | } | |||
|
176 | // `unwrap` shouldn’t panic since we just got this key from | |||
|
177 | // iteration | |||
|
178 | let list = config.get_list(b"color", key).unwrap(); | |||
|
179 | let parsed = list | |||
|
180 | .iter() | |||
|
181 | .filter_map(|name| parse_effect(key, name)) | |||
|
182 | .collect(); | |||
|
183 | styles.insert(key.to_owned(), parsed); | |||
|
184 | } | |||
|
185 | styles | |||
|
186 | } | |||
|
187 | ||||
|
188 | enum ColorMode { | |||
|
189 | // TODO: support other modes | |||
|
190 | Ansi, | |||
|
191 | } | |||
|
192 | ||||
|
193 | impl ColorMode { | |||
|
194 | // Similar to _modesetup in mercurial/color.py | |||
|
195 | fn get(config: &Config) -> Result<Option<Self>, HgError> { | |||
|
196 | if plain(Some("color")) { | |||
|
197 | return Ok(None); | |||
|
198 | } | |||
|
199 | let enabled_default = b"auto"; | |||
|
200 | // `origin` is only used when `!auto`, so its default doesn’t matter | |||
|
201 | let (enabled, origin) = config | |||
|
202 | .get_with_origin(b"ui", b"color") | |||
|
203 | .unwrap_or((enabled_default, &ConfigOrigin::CommandLineColor)); | |||
|
204 | if enabled == b"debug" { | |||
|
205 | return Err(HgError::unsupported("debug color mode")); | |||
|
206 | } | |||
|
207 | let auto = enabled == b"auto"; | |||
|
208 | let always; | |||
|
209 | if !auto { | |||
|
210 | let enabled_bool = config.get_bool(b"ui", b"color")?; | |||
|
211 | if !enabled_bool { | |||
|
212 | return Ok(None); | |||
|
213 | } | |||
|
214 | always = enabled == b"always" | |||
|
215 | || *origin == ConfigOrigin::CommandLineColor | |||
|
216 | } else { | |||
|
217 | always = false | |||
|
218 | }; | |||
|
219 | let formatted = always | |||
|
220 | || (std::env::var_os("TERM").unwrap_or_default() != "dumb" | |||
|
221 | && formatted(config)?); | |||
|
222 | ||||
|
223 | let mode_default = b"auto"; | |||
|
224 | let mode = config.get(b"color", b"mode").unwrap_or(mode_default); | |||
|
225 | ||||
|
226 | if formatted { | |||
|
227 | match mode { | |||
|
228 | b"ansi" | b"auto" => Ok(Some(ColorMode::Ansi)), | |||
|
229 | // TODO: support other modes | |||
|
230 | _ => Err(HgError::UnsupportedFeature(format!( | |||
|
231 | "color mode {}", | |||
|
232 | String::from_utf8_lossy(mode) | |||
|
233 | ))), | |||
|
234 | } | |||
|
235 | } else { | |||
|
236 | Ok(None) | |||
|
237 | } | |||
|
238 | } | |||
|
239 | } | |||
|
240 | ||||
|
241 | pub struct ColorConfig { | |||
|
242 | pub styles: EffectsMap, | |||
|
243 | } | |||
|
244 | ||||
|
245 | impl ColorConfig { | |||
|
246 | // Similar to _modesetup in mercurial/color.py | |||
|
247 | pub fn new(config: &Config) -> Result<Option<Self>, HgError> { | |||
|
248 | Ok(match ColorMode::get(config)? { | |||
|
249 | None => None, | |||
|
250 | Some(ColorMode::Ansi) => Some(ColorConfig { | |||
|
251 | styles: effects_from_config(config), | |||
|
252 | }), | |||
|
253 | }) | |||
|
254 | } | |||
|
255 | } |
@@ -876,6 +876,7 b' dependencies = [' | |||||
876 | name = "rhg" |
|
876 | name = "rhg" | |
877 | version = "0.1.0" |
|
877 | version = "0.1.0" | |
878 | dependencies = [ |
|
878 | dependencies = [ | |
|
879 | "atty", | |||
879 | "chrono", |
|
880 | "chrono", | |
880 | "clap", |
|
881 | "clap", | |
881 | "derive_more", |
|
882 | "derive_more", |
@@ -13,4 +13,4 b' mod config;' | |||||
13 | mod layer; |
|
13 | mod layer; | |
14 | mod values; |
|
14 | mod values; | |
15 | pub use config::{Config, ConfigSource, ConfigValueParseError}; |
|
15 | pub use config::{Config, ConfigSource, ConfigValueParseError}; | |
16 | pub use layer::{ConfigError, ConfigParseError}; |
|
16 | pub use layer::{ConfigError, ConfigOrigin, ConfigParseError}; |
@@ -398,6 +398,16 b' impl Config {' | |||||
398 | .map(|(_, value)| value.bytes.as_ref()) |
|
398 | .map(|(_, value)| value.bytes.as_ref()) | |
399 | } |
|
399 | } | |
400 |
|
400 | |||
|
401 | /// Returns the raw value bytes of the first one found, or `None`. | |||
|
402 | pub fn get_with_origin( | |||
|
403 | &self, | |||
|
404 | section: &[u8], | |||
|
405 | item: &[u8], | |||
|
406 | ) -> Option<(&[u8], &ConfigOrigin)> { | |||
|
407 | self.get_inner(section, item) | |||
|
408 | .map(|(layer, value)| (value.bytes.as_ref(), &layer.origin)) | |||
|
409 | } | |||
|
410 | ||||
401 | /// Returns the layer and the value of the first one found, or `None`. |
|
411 | /// Returns the layer and the value of the first one found, or `None`. | |
402 | fn get_inner( |
|
412 | fn get_inner( | |
403 | &self, |
|
413 | &self, |
@@ -295,7 +295,7 b' pub struct ConfigValue {' | |||||
295 | pub line: Option<usize>, |
|
295 | pub line: Option<usize>, | |
296 | } |
|
296 | } | |
297 |
|
297 | |||
298 | #[derive(Clone, Debug)] |
|
298 | #[derive(Clone, Debug, PartialEq, Eq)] | |
299 | pub enum ConfigOrigin { |
|
299 | pub enum ConfigOrigin { | |
300 | /// From a configuration file |
|
300 | /// From a configuration file | |
301 | File(PathBuf), |
|
301 | File(PathBuf), |
@@ -8,6 +8,7 b' authors = [' | |||||
8 | edition = "2018" |
|
8 | edition = "2018" | |
9 |
|
9 | |||
10 | [dependencies] |
|
10 | [dependencies] | |
|
11 | atty = "0.2" | |||
11 | hg-core = { path = "../hg-core"} |
|
12 | hg-core = { path = "../hg-core"} | |
12 | chrono = "0.4.19" |
|
13 | chrono = "0.4.19" | |
13 | clap = "2.33.1" |
|
14 | clap = "2.33.1" |
@@ -17,6 +17,7 b' use std::path::PathBuf;' | |||||
17 | use std::process::Command; |
|
17 | use std::process::Command; | |
18 |
|
18 | |||
19 | mod blackbox; |
|
19 | mod blackbox; | |
|
20 | mod color; | |||
20 | mod error; |
|
21 | mod error; | |
21 | mod ui; |
|
22 | mod ui; | |
22 | pub mod utils { |
|
23 | pub mod utils { |
@@ -1,4 +1,7 b'' | |||||
|
1 | use crate::color::ColorConfig; | |||
|
2 | use crate::color::Effect; | |||
1 | use format_bytes::format_bytes; |
|
3 | use format_bytes::format_bytes; | |
|
4 | use format_bytes::write_bytes; | |||
2 | use hg::config::Config; |
|
5 | use hg::config::Config; | |
3 | use hg::errors::HgError; |
|
6 | use hg::errors::HgError; | |
4 | use hg::utils::files::get_bytes_from_os_string; |
|
7 | use hg::utils::files::get_bytes_from_os_string; | |
@@ -7,10 +10,10 b' use std::env;' | |||||
7 | use std::io; |
|
10 | use std::io; | |
8 | use std::io::{ErrorKind, Write}; |
|
11 | use std::io::{ErrorKind, Write}; | |
9 |
|
12 | |||
10 | #[derive(Debug)] |
|
|||
11 | pub struct Ui { |
|
13 | pub struct Ui { | |
12 | stdout: std::io::Stdout, |
|
14 | stdout: std::io::Stdout, | |
13 | stderr: std::io::Stderr, |
|
15 | stderr: std::io::Stderr, | |
|
16 | colors: Option<ColorConfig>, | |||
14 | } |
|
17 | } | |
15 |
|
18 | |||
16 | /// The kind of user interface error |
|
19 | /// The kind of user interface error | |
@@ -23,20 +26,26 b' pub enum UiError {' | |||||
23 |
|
26 | |||
24 | /// The commandline user interface |
|
27 | /// The commandline user interface | |
25 | impl Ui { |
|
28 | impl Ui { | |
26 |
pub fn new( |
|
29 | pub fn new(config: &Config) -> Result<Self, HgError> { | |
27 | Ok(Ui { |
|
30 | Ok(Ui { | |
|
31 | // If using something else, also adapt `isatty()` below. | |||
28 | stdout: std::io::stdout(), |
|
32 | stdout: std::io::stdout(), | |
|
33 | ||||
29 | stderr: std::io::stderr(), |
|
34 | stderr: std::io::stderr(), | |
|
35 | colors: ColorConfig::new(config)?, | |||
30 | }) |
|
36 | }) | |
31 | } |
|
37 | } | |
32 |
|
38 | |||
33 | /// Default to no color if color configuration errors. |
|
39 | /// Default to no color if color configuration errors. | |
34 | /// |
|
40 | /// | |
35 | /// Useful when we’re already handling another error. |
|
41 | /// Useful when we’re already handling another error. | |
36 |
pub fn new_infallible( |
|
42 | pub fn new_infallible(config: &Config) -> Self { | |
37 | Ui { |
|
43 | Ui { | |
|
44 | // If using something else, also adapt `isatty()` below. | |||
38 | stdout: std::io::stdout(), |
|
45 | stdout: std::io::stdout(), | |
|
46 | ||||
39 | stderr: std::io::stderr(), |
|
47 | stderr: std::io::stderr(), | |
|
48 | colors: ColorConfig::new(config).unwrap_or(None), | |||
40 | } |
|
49 | } | |
41 | } |
|
50 | } | |
42 |
|
51 | |||
@@ -48,6 +57,11 b' impl Ui {' | |||||
48 |
|
57 | |||
49 | /// Write bytes to stdout |
|
58 | /// Write bytes to stdout | |
50 | pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> { |
|
59 | pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> { | |
|
60 | // Hack to silence "unused" warnings | |||
|
61 | if false { | |||
|
62 | return self.write_stdout_labelled(bytes, ""); | |||
|
63 | } | |||
|
64 | ||||
51 | let mut stdout = self.stdout.lock(); |
|
65 | let mut stdout = self.stdout.lock(); | |
52 |
|
66 | |||
53 | stdout.write_all(bytes).or_else(handle_stdout_error)?; |
|
67 | stdout.write_all(bytes).or_else(handle_stdout_error)?; | |
@@ -64,6 +78,61 b' impl Ui {' | |||||
64 | stderr.flush().or_else(handle_stderr_error) |
|
78 | stderr.flush().or_else(handle_stderr_error) | |
65 | } |
|
79 | } | |
66 |
|
80 | |||
|
81 | /// Write bytes to stdout with the given label | |||
|
82 | /// | |||
|
83 | /// Like the optional `label` parameter in `mercurial/ui.py`, | |||
|
84 | /// this label influences the color used for this output. | |||
|
85 | pub fn write_stdout_labelled( | |||
|
86 | &self, | |||
|
87 | bytes: &[u8], | |||
|
88 | label: &str, | |||
|
89 | ) -> Result<(), UiError> { | |||
|
90 | if let Some(colors) = &self.colors { | |||
|
91 | if let Some(effects) = colors.styles.get(label.as_bytes()) { | |||
|
92 | if !effects.is_empty() { | |||
|
93 | return self | |||
|
94 | .write_stdout_with_effects(bytes, effects) | |||
|
95 | .or_else(handle_stdout_error); | |||
|
96 | } | |||
|
97 | } | |||
|
98 | } | |||
|
99 | self.write_stdout(bytes) | |||
|
100 | } | |||
|
101 | ||||
|
102 | fn write_stdout_with_effects( | |||
|
103 | &self, | |||
|
104 | bytes: &[u8], | |||
|
105 | effects: &[Effect], | |||
|
106 | ) -> io::Result<()> { | |||
|
107 | let stdout = &mut self.stdout.lock(); | |||
|
108 | let mut write_line = |line: &[u8], first: bool| { | |||
|
109 | // `line` does not include the newline delimiter | |||
|
110 | if !first { | |||
|
111 | stdout.write_all(b"\n")?; | |||
|
112 | } | |||
|
113 | if line.is_empty() { | |||
|
114 | return Ok(()); | |||
|
115 | } | |||
|
116 | /// 0x1B == 27 == 0o33 | |||
|
117 | const ASCII_ESCAPE: &[u8] = b"\x1b"; | |||
|
118 | write_bytes!(stdout, b"{}[0", ASCII_ESCAPE)?; | |||
|
119 | for effect in effects { | |||
|
120 | write_bytes!(stdout, b";{}", effect)?; | |||
|
121 | } | |||
|
122 | write_bytes!(stdout, b"m")?; | |||
|
123 | stdout.write_all(line)?; | |||
|
124 | write_bytes!(stdout, b"{}[0m", ASCII_ESCAPE) | |||
|
125 | }; | |||
|
126 | let mut lines = bytes.split(|&byte| byte == b'\n'); | |||
|
127 | if let Some(first) = lines.next() { | |||
|
128 | write_line(first, true)?; | |||
|
129 | for line in lines { | |||
|
130 | write_line(line, false)? | |||
|
131 | } | |||
|
132 | } | |||
|
133 | stdout.flush() | |||
|
134 | } | |||
|
135 | ||||
67 | /// Return whether plain mode is active. |
|
136 | /// Return whether plain mode is active. | |
68 | /// |
|
137 | /// | |
69 | /// Plain mode means that all configuration variables which affect |
|
138 | /// Plain mode means that all configuration variables which affect | |
@@ -83,7 +152,7 b' impl Ui {' | |||||
83 | } |
|
152 | } | |
84 | } |
|
153 | } | |
85 |
|
154 | |||
86 | fn plain(opt_feature: Option<&str>) -> bool { |
|
155 | pub fn plain(opt_feature: Option<&str>) -> bool { | |
87 | if let Some(except) = env::var_os("HGPLAINEXCEPT") { |
|
156 | if let Some(except) = env::var_os("HGPLAINEXCEPT") { | |
88 | opt_feature.map_or(true, |feature| { |
|
157 | opt_feature.map_or(true, |feature| { | |
89 | get_bytes_from_os_string(except) |
|
158 | get_bytes_from_os_string(except) | |
@@ -154,3 +223,23 b' pub fn utf8_to_local(s: &str) -> Cow<[u8' | |||||
154 | let bytes = s.as_bytes(); |
|
223 | let bytes = s.as_bytes(); | |
155 | Cow::Borrowed(bytes) |
|
224 | Cow::Borrowed(bytes) | |
156 | } |
|
225 | } | |
|
226 | ||||
|
227 | /// Should formatted output be used? | |||
|
228 | /// | |||
|
229 | /// Note: rhg does not have the formatter mechanism yet, | |||
|
230 | /// but this is also used when deciding whether to use color. | |||
|
231 | pub fn formatted(config: &Config) -> Result<bool, HgError> { | |||
|
232 | if let Some(formatted) = config.get_option(b"ui", b"formatted")? { | |||
|
233 | Ok(formatted) | |||
|
234 | } else { | |||
|
235 | isatty(config) | |||
|
236 | } | |||
|
237 | } | |||
|
238 | ||||
|
239 | fn isatty(config: &Config) -> Result<bool, HgError> { | |||
|
240 | Ok(if config.get_bool(b"ui", b"nontty")? { | |||
|
241 | false | |||
|
242 | } else { | |||
|
243 | atty::is(atty::Stream::Stdout) | |||
|
244 | }) | |||
|
245 | } |
@@ -313,6 +313,7 b' test unknown color' | |||||
313 | ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) |
|
313 | ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) | |
314 | ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) |
|
314 | ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) | |
315 | ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) |
|
315 | ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) | |
|
316 | ignoring unknown color/effect 'periwinkle' (configured in color.status.modified) (rhg !) | |||
316 | M modified |
|
317 | M modified | |
317 | \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc) |
|
318 | \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc) | |
318 | \x1b[0;32;1mA \x1b[0m\x1b[0;32;1mcopied\x1b[0m (esc) |
|
319 | \x1b[0;32;1mA \x1b[0m\x1b[0;32;1mcopied\x1b[0m (esc) |
General Comments 0
You need to be logged in to leave comments.
Login now