control_code/
sgr.rs

1//            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2//                    Version 2, December 2004
3//
4// Copyleft (ↄ) meh. <meh@schizofreni.co> | http://meh.schizofreni.co
5//
6// Everyone is permitted to copy and distribute verbatim or modified
7// copies of this license document, and changing it is allowed as long
8// as the name is changed.
9//
10//            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11//   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12//
13//  0. You just DO WHAT THE FUCK YOU WANT TO.
14
15use nom;
16use smallvec::SmallVec;
17use CSI;
18
19#[derive(Eq, PartialEq, Copy, Clone, Debug)]
20pub enum SGR {
21	Reset,
22	Font(Weight),
23	Italic(bool),
24	Underline(bool),
25	Blink(bool),
26	Reverse(bool),
27	Invisible(bool),
28	Struck(bool),
29	Foreground(Color),
30	Background(Color),
31}
32
33use self::SGR::*;
34
35#[derive(Eq, PartialEq, Copy, Clone, Debug)]
36pub enum Weight {
37	Normal,
38	Bold,
39	Faint,
40}
41
42#[derive(Eq, PartialEq, Copy, Clone, Debug)]
43pub enum Color {
44	Default,
45	Transparent,
46	Index(u8),
47	Cmy(u8, u8, u8),
48	Cmyk(u8, u8, u8, u8),
49	Rgb(u8, u8, u8),
50}
51
52impl Into<SmallVec<[u32; CSI::SIZE]>> for SGR {
53	#[inline]
54	fn into(self) -> SmallVec<[u32; CSI::SIZE]> {
55		match self {
56			Reset =>
57				small_vec![0],
58
59			Font(Weight::Bold) =>
60				small_vec![1],
61
62			Font(Weight::Faint) =>
63				small_vec![2],
64
65			Italic(true) =>
66				small_vec![3],
67
68			Underline(true) =>
69				small_vec![4],
70
71			Blink(true) =>
72				small_vec![5],
73
74			Reverse(true) =>
75				small_vec![7],
76
77			Invisible(true) =>
78				small_vec![8],
79
80			Struck(true) =>
81				small_vec![9],
82
83			Font(Weight::Normal) =>
84				small_vec![22],
85
86			Italic(false) =>
87				small_vec![23],
88
89			Underline(false) =>
90				small_vec![24],
91
92			Blink(false) =>
93				small_vec![25],
94
95			Reverse(false) =>
96				small_vec![27],
97
98			Invisible(false) =>
99				small_vec![28],
100
101			Struck(false) =>
102				small_vec![29],
103
104			Foreground(Color::Index(index)) if index < 8 =>
105				small_vec![index as u32 + 30],
106
107			Foreground(Color::Index(index)) if index < 16 =>
108				small_vec![index as u32 - 8 + 90],
109
110			Foreground(Color::Index(index)) =>
111				small_vec![38, 5, index as u32],
112
113			Foreground(Color::Default) =>
114				small_vec![38, 0],
115
116			Foreground(Color::Transparent) =>
117				small_vec![38, 1],
118
119			Foreground(Color::Rgb(r, g, b)) =>
120				small_vec![38, 2, r as u32, g as u32, b as u32],
121
122			Foreground(Color::Cmy(c, m, y)) =>
123				small_vec![38, 3, c as u32, m as u32, y as u32],
124
125			Foreground(Color::Cmyk(c, m, y, k)) =>
126				small_vec![38, 4, c as u32, m as u32, y as u32, k as u32],
127
128			Background(Color::Index(index)) if index < 8 =>
129				small_vec![index as u32 + 40],
130
131			Background(Color::Index(index)) if index < 16 =>
132				small_vec![index as u32 - 8 + 100],
133
134			Background(Color::Index(index)) =>
135				small_vec![48, 5, index as u32],
136
137			Background(Color::Default) =>
138				small_vec![48, 0],
139
140			Background(Color::Transparent) =>
141				small_vec![48, 1],
142
143			Background(Color::Rgb(r, g, b)) =>
144				small_vec![48, 2, r as u32, g as u32, b as u32],
145
146			Background(Color::Cmy(c, m, y)) =>
147				small_vec![48, 3, c as u32, m as u32, y as u32],
148
149			Background(Color::Cmyk(c, m, y, k)) =>
150				small_vec![48, 4, c as u32, m as u32, y as u32, k as u32],
151		}
152	}
153}
154
155macro_rules! pop {
156	($args:ident, $n:expr) => ({
157		let count = $n;
158
159		if $args.len() >= count {
160			$args = &$args[count..];
161		}
162		else {
163			$args = &[];
164		}
165	});
166}
167
168macro_rules! color {
169	($args:ident) => ({
170		let id = arg!($args[0] => 0);
171		pop!($args, 1);
172
173		match id {
174			0 =>
175				Color::Default,
176
177			1 =>
178				Color::Transparent,
179
180			2 => {
181				let r = arg!($args[0] => 0) as u8;
182				let g = arg!($args[1] => 0) as u8;
183				let b = arg!($args[2] => 0) as u8;
184				pop!($args, 3);
185
186				Color::Rgb(r, g, b)
187			}
188
189			3 => {
190				let c = arg!($args[0] => 0) as u8;
191				let m = arg!($args[1] => 0) as u8;
192				let y = arg!($args[2] => 0) as u8;
193				pop!($args, 3);
194
195				Color::Cmy(c, m, y)
196			}
197
198			4 => {
199				let c = arg!($args[0] => 0) as u8;
200				let m = arg!($args[1] => 0) as u8;
201				let y = arg!($args[2] => 0) as u8;
202				let k = arg!($args[3] => 0) as u8;
203				pop!($args, 4);
204
205				Color::Cmyk(c, m, y, k)
206			}
207
208			5 => {
209				let index = arg!($args[0] => 0) as u8;
210				pop!($args, 1);
211
212				Color::Index(index)
213			}
214
215			_ =>
216				return Err(nom::ErrorKind::Custom(9006))
217		}
218	})
219}
220
221pub fn parse<'a, 'b>(args: &'b [Option<u32>]) -> Result<SmallVec<[SGR; CSI::SIZE]>, nom::ErrorKind> {
222	if args.is_empty() {
223		return Ok(small_vec![Reset]);
224	}
225
226	let mut result = SmallVec::new();
227	let mut args   = args;
228
229	while !args.is_empty() {
230		let id = arg!(args[0] => 0);
231		pop!(args, 1);
232
233		result.push(match id {
234			0 =>
235				Reset,
236
237			1 =>
238				Font(Weight::Bold),
239
240			2 =>
241				Font(Weight::Faint),
242
243			3 =>
244				Italic(true),
245
246			4 =>
247				Underline(true),
248
249			5 | 6 =>
250				Blink(true),
251
252			7 =>
253				Reverse(true),
254
255			8 =>
256				Invisible(true),
257
258			9 =>
259				Struck(true),
260
261			22 =>
262				Font(Weight::Normal),
263
264			23 =>
265				Italic(false),
266
267			24 =>
268				Underline(false),
269
270			25 =>
271				Blink(false),
272
273			27 =>
274				Reverse(false),
275
276			28 =>
277				Invisible(false),
278
279			29 =>
280				Struck(false),
281
282			c if c >= 30 && c <= 37 =>
283				Foreground(Color::Index(c as u8 - 30)),
284
285			38 =>
286				Foreground(color!(args)),
287
288			39 =>
289				Foreground(Color::Default),
290
291			c if c >= 40 && c <= 47 =>
292				Background(Color::Index(c as u8 - 40)),
293
294			48 =>
295				Background(color!(args)),
296
297			49 =>
298				Background(Color::Default),
299
300			c if c >= 90 && c <= 97 =>
301				Foreground(Color::Index(c as u8 - 90 + 8)),
302
303			c if c >= 100 && c <= 107 =>
304				Background(Color::Index(c as u8 - 100 + 8)),
305
306			_ =>
307				return Err(nom::ErrorKind::Custom(9001))
308		});
309	}
310
311	Ok(result)
312}
313
314pub mod shim {
315	pub use super::SGR as T;
316	pub use super::SGR::*;
317	pub use super::parse;
318	pub use super::{Weight, Color};
319}
320
321#[cfg(test)]
322mod test {
323	mod parse {
324		use {Control, C1, CSI, SGR, parse};
325
326		macro_rules! test {
327			($string:expr => $($attrs:expr),+) => (
328				assert_eq!(Control::C1(C1::ControlSequence(CSI::SelectGraphicalRendition(small_vec![$($attrs),*]))),
329					parse($string).unwrap().1);
330			);
331		}
332
333		#[test]
334		fn reset() {
335			test!(b"\x1B[0m" =>
336				SGR::Reset);
337
338			test!(b"\x1B[m" =>
339				SGR::Reset);
340		}
341
342		#[test]
343		fn font() {
344			test!(b"\x1B[1m" =>
345				SGR::Font(SGR::Weight::Bold));
346
347			test!(b"\x1B[2m" =>
348				SGR::Font(SGR::Weight::Faint));
349
350			test!(b"\x1B[22m" =>
351				SGR::Font(SGR::Weight::Normal));
352		}
353
354		#[test]
355		fn italic() {
356			test!(b"\x1B[3m" =>
357				SGR::Italic(true));
358
359			test!(b"\x1B[23m" =>
360				SGR::Italic(false));
361		}
362
363		#[test]
364		fn underline() {
365			test!(b"\x1B[4m" =>
366				SGR::Underline(true));
367
368			test!(b"\x1B[24m" =>
369				SGR::Underline(false));
370		}
371
372		#[test]
373		fn blink() {
374			test!(b"\x1B[5m" =>
375				SGR::Blink(true));
376
377			test!(b"\x1B[6m" =>
378				SGR::Blink(true));
379
380			test!(b"\x1B[25m" =>
381				SGR::Blink(false));
382		}
383
384		#[test]
385		fn reverse() {
386			test!(b"\x1B[7m" =>
387				SGR::Reverse(true));
388
389			test!(b"\x1B[27m" =>
390				SGR::Reverse(false));
391		}
392
393		#[test]
394		fn invisible() {
395			test!(b"\x1B[8m" =>
396				SGR::Invisible(true));
397
398			test!(b"\x1B[28m" =>
399				SGR::Invisible(false));
400		}
401
402		#[test]
403		fn struck() {
404			test!(b"\x1B[9m" =>
405				SGR::Struck(true));
406
407			test!(b"\x1B[29m" =>
408				SGR::Struck(false));
409		}
410
411		#[test]
412		fn foreground() {
413			test!(b"\x1B[38;0m" =>
414				SGR::Foreground(SGR::Color::Default));
415
416			test!(b"\x1B[39m" =>
417				SGR::Foreground(SGR::Color::Default));
418
419			test!(b"\x1B[38;1m" =>
420				SGR::Foreground(SGR::Color::Transparent));
421
422			test!(b"\x1B[30m" =>
423				SGR::Foreground(SGR::Color::Index(0)));
424
425			test!(b"\x1B[37m" =>
426				SGR::Foreground(SGR::Color::Index(7)));
427
428			test!(b"\x1B[38;2;255;;127m" =>
429				SGR::Foreground(SGR::Color::Rgb(255, 0, 127)));
430
431			test!(b"\x1B[38;5;235m" =>
432				SGR::Foreground(SGR::Color::Index(235)));
433
434			test!(b"\x1B[90m" =>
435				SGR::Foreground(SGR::Color::Index(8)));
436
437			test!(b"\x1B[97m" =>
438				SGR::Foreground(SGR::Color::Index(15)));
439		}
440
441		#[test]
442		fn background() {
443			test!(b"\x1B[48;0m" =>
444				SGR::Background(SGR::Color::Default));
445
446			test!(b"\x1B[49m" =>
447				SGR::Background(SGR::Color::Default));
448
449			test!(b"\x1B[48;1m" =>
450				SGR::Background(SGR::Color::Transparent));
451
452			test!(b"\x1B[40m" =>
453				SGR::Background(SGR::Color::Index(0)));
454
455			test!(b"\x1B[47m" =>
456				SGR::Background(SGR::Color::Index(7)));
457
458			test!(b"\x1B[48;2;255;;127m" =>
459				SGR::Background(SGR::Color::Rgb(255, 0, 127)));
460
461			test!(b"\x1B[48;5;235m" =>
462				SGR::Background(SGR::Color::Index(235)));
463
464			test!(b"\x1B[100m" =>
465				SGR::Background(SGR::Color::Index(8)));
466
467			test!(b"\x1B[107m" =>
468				SGR::Background(SGR::Color::Index(15)));
469		}
470
471		#[test]
472		fn sequence() {
473			test!(b"\x1B[38;2;0;255;127;48;2;127;255;0m" =>
474				SGR::Foreground(SGR::Color::Rgb(0, 255, 127)),
475				SGR::Background(SGR::Color::Rgb(127, 255, 0)));
476		}
477	}
478
479	mod format {
480		use {Control, C1, CSI, SGR, format, parse};
481
482		macro_rules! test {
483			($($attr:expr),+) => (
484				let item = Control::C1(C1::ControlSequence(CSI::SelectGraphicalRendition(
485					small_vec![$($attr),*])));
486
487				assert_eq!(item, parse(&format(&item)).unwrap().1);
488			);
489		}
490
491		#[test]
492		fn reset() {
493			test!(SGR::Reset);
494		}
495
496		#[test]
497		fn font() {
498			test!(SGR::Font(SGR::Weight::Bold));
499			test!(SGR::Font(SGR::Weight::Faint));
500			test!(SGR::Font(SGR::Weight::Normal));
501		}
502
503		#[test]
504		fn italic() {
505			test!(SGR::Italic(true));
506			test!(SGR::Italic(false));
507		}
508
509		#[test]
510		fn underline() {
511			test!(SGR::Underline(true));
512			test!(SGR::Underline(false));
513		}
514
515		#[test]
516		fn blink() {
517			test!(SGR::Blink(true));
518			test!(SGR::Blink(false));
519		}
520
521		#[test]
522		fn reverse() {
523			test!(SGR::Reverse(true));
524			test!(SGR::Reverse(false));
525		}
526
527		#[test]
528		fn invisible() {
529			test!(SGR::Invisible(true));
530			test!(SGR::Invisible(false));
531		}
532
533		#[test]
534		fn struck() {
535			test!(SGR::Struck(true));
536			test!(SGR::Struck(false));
537		}
538
539		#[test]
540		fn foreground() {
541			test!(SGR::Foreground(SGR::Color::Default));
542			test!(SGR::Foreground(SGR::Color::Transparent));
543			test!(SGR::Foreground(SGR::Color::Index(0)));
544			test!(SGR::Foreground(SGR::Color::Index(7)));
545			test!(SGR::Foreground(SGR::Color::Rgb(255, 0, 127)));
546			test!(SGR::Foreground(SGR::Color::Index(235)));
547			test!(SGR::Foreground(SGR::Color::Index(8)));
548			test!(SGR::Foreground(SGR::Color::Index(15)));
549		}
550
551		#[test]
552		fn background() {
553			test!(SGR::Background(SGR::Color::Default));
554			test!(SGR::Background(SGR::Color::Transparent));
555			test!(SGR::Background(SGR::Color::Index(0)));
556			test!(SGR::Background(SGR::Color::Index(7)));
557			test!(SGR::Background(SGR::Color::Rgb(255, 0, 127)));
558			test!(SGR::Background(SGR::Color::Index(235)));
559			test!(SGR::Background(SGR::Color::Index(8)));
560			test!(SGR::Background(SGR::Color::Index(15)));
561		}
562
563		#[test]
564		fn sequence() {
565			test!(SGR::Foreground(SGR::Color::Rgb(0, 255, 127)),
566			      SGR::Background(SGR::Color::Rgb(127, 255, 0)));
567		}
568	}
569}