1use crate::{Style, StyleDelta, event::KeyboardEnhancementFlags};
43
44pub trait BufferWrite {
58 fn write_to_buffer(&self, buffer: &mut Vec<u8>);
60}
61
62impl BufferWrite for [u8] {
63 fn write_to_buffer(&self, buffer: &mut Vec<u8>) {
64 buffer.extend_from_slice(self);
65 }
66}
67
68impl BufferWrite for str {
69 fn write_to_buffer(&self, buffer: &mut Vec<u8>) {
70 buffer.extend_from_slice(self.as_bytes());
71 }
72}
73
74impl<T: itoap::Integer + Copy> BufferWrite for T {
75 fn write_to_buffer(&self, buffer: &mut Vec<u8>) {
76 itoap::write_to_vec(buffer, *self);
77 }
78}
79
80#[rustfmt::skip]
81static LOOKUP: [u32;256] = [
82 16777264,16777265,16777266,16777267,16777268,16777269,16777270,16777271,16777272,16777273,
83 33566769,33567025,33567281,33567537,33567793,33568049,33568305,33568561,33568817,33569073,
84 33566770,33567026,33567282,33567538,33567794,33568050,33568306,33568562,33568818,33569074,
85 33566771,33567027,33567283,33567539,33567795,33568051,33568307,33568563,33568819,33569075,
86 33566772,33567028,33567284,33567540,33567796,33568052,33568308,33568564,33568820,33569076,
87 33566773,33567029,33567285,33567541,33567797,33568053,33568309,33568565,33568821,33569077,
88 33566774,33567030,33567286,33567542,33567798,33568054,33568310,33568566,33568822,33569078,
89 33566775,33567031,33567287,33567543,33567799,33568055,33568311,33568567,33568823,33569079,
90 33566776,33567032,33567288,33567544,33567800,33568056,33568312,33568568,33568824,33569080,
91 33566777,33567033,33567289,33567545,33567801,33568057,33568313,33568569,33568825,33569081,
92 53489713,53555249,53620785,53686321,53751857,53817393,53882929,53948465,54014001,54079537,
93 53489969,53555505,53621041,53686577,53752113,53817649,53883185,53948721,54014257,54079793,
94 53490225,53555761,53621297,53686833,53752369,53817905,53883441,53948977,54014513,54080049,
95 53490481,53556017,53621553,53687089,53752625,53818161,53883697,53949233,54014769,54080305,
96 53490737,53556273,53621809,53687345,53752881,53818417,53883953,53949489,54015025,54080561,
97 53490993,53556529,53622065,53687601,53753137,53818673,53884209,53949745,54015281,54080817,
98 53491249,53556785,53622321,53687857,53753393,53818929,53884465,53950001,54015537,54081073,
99 53491505,53557041,53622577,53688113,53753649,53819185,53884721,53950257,54015793,54081329,
100 53491761,53557297,53622833,53688369,53753905,53819441,53884977,53950513,54016049,54081585,
101 53492017,53557553,53623089,53688625,53754161,53819697,53885233,53950769,54016305,54081841,
102 53489714,53555250,53620786,53686322,53751858,53817394,53882930,53948466,54014002,54079538,
103 53489970,53555506,53621042,53686578,53752114,53817650,53883186,53948722,54014258,54079794,
104 53490226,53555762,53621298,53686834,53752370,53817906,53883442,53948978,54014514,54080050,
105 53490482,53556018,53621554,53687090,53752626,53818162,53883698,53949234,54014770,54080306,
106 53490738,53556274,53621810,53687346,53752882,53818418,53883954,53949490,54015026,54080562,
107 53490994,53556530,53622066,53687602,53753138,53818674
108];
109
110pub fn style(out: &mut Vec<u8>, style: Style, clear: bool) {
114 if style == Style::DEFAULT {
115 if clear {
116 out.extend_from_slice(b"\x1b[0m");
117 }
118 return;
119 }
120 out.reserve(64);
121 let len = out.len();
122 let ogptr = out.as_mut_ptr();
123 let ptr = out.as_mut_ptr();
124
125 unsafe {
127 let mut ptr = ptr.add(len);
128 *(ptr as *mut [u8; 2]) = [0x1b, b'['];
129 ptr = ptr.add(2);
130 if clear {
131 *(ptr as *mut [u8; 2]) = [b'0', b';'];
132 ptr = ptr.add(2);
133 }
134 let mods = style.modifiers();
135 let mut first = true;
136 if !mods.is_empty() {
137 ptr = write_all_modifiers_inner(ptr, mods);
138 first = false;
139 }
140 if let Some(color) = style.fg() {
141 if !first {
142 *(ptr as *mut u8) = b';';
143 ptr = ptr.add(1);
144 } else {
145 first = false;
146 }
147 *(ptr as *mut [u8; 8]) = *b"38;5;000";
148 ptr = ptr.add(5);
149 let mask = LOOKUP[color.0 as usize];
150 let numlen = (mask >> 24) as usize;
151 *(ptr as *mut [u8; 4]) = mask.to_ne_bytes();
152 ptr = ptr.add(numlen);
153 }
154 if let Some(color) = style.bg() {
155 if !first {
156 *(ptr as *mut u8) = b';';
157 ptr = ptr.add(1);
158 }
159 *(ptr as *mut [u8; 8]) = *b"48;5;000";
160 ptr = ptr.add(5);
161 let mask = LOOKUP[color.0 as usize];
162 let numlen = (mask >> 24) as usize;
163 *(ptr as *mut [u8; 4]) = mask.to_ne_bytes();
164 ptr = ptr.add(numlen);
165 }
166 *ptr = b'm';
167 let new_len = ptr.offset_from(ogptr) as usize + 1;
168 out.set_len(new_len);
169 }
170}
171
172#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub struct MoveCursor(pub u16, pub u16);
178
179impl BufferWrite for MoveCursor {
180 fn write_to_buffer(&self, buffer: &mut Vec<u8>) {
181 let (x, y) = (self.0, self.1);
182 buffer.reserve(2 + 5 + 1 + 5 + 1);
183 let len = buffer.len();
184 let optr = buffer.as_mut_ptr();
185 unsafe {
187 let mut ptr = optr.add(len);
188 *(ptr as *mut [u8; 2]) = [0x1b, b'['];
189 ptr = ptr.add(2);
190 let offset = itoap::write_to_ptr(ptr, y.saturating_add(1));
191 ptr = ptr.add(offset);
192 *ptr = b';';
193 ptr = ptr.add(1);
194 let offset = itoap::write_to_ptr(ptr, x.saturating_add(1));
195 ptr = ptr.add(offset);
196 *ptr = b'H';
197 let new_len = ptr.offset_from(optr) as usize + 1;
198 buffer.set_len(new_len);
199 }
200 }
201}
202
203#[derive(Clone, Copy, Debug, PartialEq, Eq)]
208pub struct ScrollBufferDown(pub u16);
209
210impl BufferWrite for ScrollBufferDown {
211 fn write_to_buffer(&self, buffer: &mut Vec<u8>) {
212 buffer.reserve(2 + 5 + 1);
213 let len = buffer.len();
214 let optr = buffer.as_mut_ptr();
215 unsafe {
217 let mut ptr = optr.add(len);
218 *(ptr as *mut [u8; 2]) = [0x1b, b'['];
219 ptr = ptr.add(2);
220 let offset = itoap::write_to_ptr(ptr, self.0);
221 ptr = ptr.add(offset);
222 *ptr = b'T';
223 let new_len = ptr.offset_from(optr) as usize + 1;
224 buffer.set_len(new_len);
225 }
226 }
227}
228
229#[derive(Clone, Copy, Debug, PartialEq, Eq)]
234pub struct ScrollBufferUp(pub u16);
235
236impl BufferWrite for ScrollBufferUp {
237 fn write_to_buffer(&self, buffer: &mut Vec<u8>) {
238 buffer.reserve(2 + 5 + 1);
239 let len = buffer.len();
240 let optr = buffer.as_mut_ptr();
241 unsafe {
243 let mut ptr = optr.add(len);
244 *(ptr as *mut [u8; 2]) = [0x1b, b'['];
245 ptr = ptr.add(2);
246 let offset = itoap::write_to_ptr(ptr, self.0);
247 ptr = ptr.add(offset);
248 *ptr = b'S';
249 let new_len = ptr.offset_from(optr) as usize + 1;
250 buffer.set_len(new_len);
251 }
252 }
253}
254
255#[derive(Clone, Copy, Debug, PartialEq, Eq)]
259pub struct MoveCursorRight(pub u16);
260
261impl BufferWrite for MoveCursorRight {
262 fn write_to_buffer(&self, buffer: &mut Vec<u8>) {
263 buffer.reserve(2 + 5 + 1);
264 let len = buffer.len();
265 let optr = buffer.as_mut_ptr();
266 unsafe {
268 let mut ptr = optr.add(len);
269 *(ptr as *mut [u8; 2]) = [0x1b, b'['];
270 ptr = ptr.add(2);
271 let offset = itoap::write_to_ptr(ptr, self.0);
272 ptr = ptr.add(offset);
273 *ptr = b'C';
274 let new_len = ptr.offset_from(optr) as usize + 1;
275 buffer.set_len(new_len);
276 }
277 }
278}
279
280pub const MOVE_CURSOR_TO_ORIGIN: &[u8] = b"\x1b[H";
282
283pub const CLEAR_STYLE: &[u8] = b"\x1b[0m";
285pub const ERASE_LINE: &[u8] = b"\x1b[0m";
287pub const CLEAR_BELOW: &[u8] = b"\x1b[J";
289pub const CLEAR_ABOVE: &[u8] = b"\x1b[1J";
291pub const CLEAR_LINE_TO_RIGHT: &[u8] = b"\x1b[K";
293
294#[derive(Clone, Copy)]
299pub struct Modifier(pub(crate) u16);
300
301impl std::fmt::Debug for Modifier {
302 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303 let values: &[(&str, Modifier)] = &[
304 ("BOLD", Modifier::BOLD),
305 ("DIM", Modifier::DIM),
306 ("ITALIC", Modifier::ITALIC),
307 ("UNDERLINED", Modifier::UNDERLINED),
308 ("SLOW_BLINK", Modifier::SLOW_BLINK),
309 ("RAPID_BLINK", Modifier::RAPID_BLINK),
310 ("REVERSED", Modifier::REVERSED),
311 ("HIDDEN", Modifier::HIDDEN),
312 ("CROSSED_OUT", Modifier::CROSSED_OUT),
313 ];
314 let mut first = true;
315 f.debug_set()
316 .entries(values.iter().filter_map(|(name, val)| {
317 if self.has(*val) {
318 if first {
319 first = false;
320 }
321 Some(*name)
322 } else {
323 None
324 }
325 }))
326 .finish()
327 }
328}
329impl Modifier {
330 pub const BOLD: Modifier = Modifier(0b0000_0000_0001_00_000);
332 pub const DIM: Modifier = Modifier(0b0000_0000_0010_00_000);
334 pub const ITALIC: Modifier = Modifier(0b0000_0000_0100_00_000);
336 pub const UNDERLINED: Modifier = Modifier(0b0000_0000_1000_00_000);
338 pub const SLOW_BLINK: Modifier = Modifier(0b0000_0001_0000_00_000);
340 pub const RAPID_BLINK: Modifier = Modifier(0b0000_0010_0000_00_000);
342 pub const REVERSED: Modifier = Modifier(0b0000_0100_0000_00_000);
344 pub const HIDDEN: Modifier = Modifier(0b0000_1000_0000_00_000);
346 pub const CROSSED_OUT: Modifier = Modifier(0b0001_0000_0000_00_000);
348 pub const ALL: Modifier = Modifier(0b0001_1111_1111_00_000);
350
351 pub fn is_empty(self) -> bool {
353 self.0 == 0
354 }
355}
356
357impl From<Modifier> for Style {
358 fn from(value: Modifier) -> Self {
359 Style(value.0 as u32)
360 }
361}
362impl Modifier {
363 pub fn has(self, other: Modifier) -> bool {
365 (self.0 & other.0) != 0
366 }
367}
368
369unsafe fn write_all_modifiers_inner(mut ptr: *mut u8, modifiers: Modifier) -> *mut u8 {
374 unsafe {
375 if modifiers.has(Modifier::BOLD) {
376 *(ptr as *mut [u8; 2]) = [b'1', b';'];
377 ptr = ptr.add(2);
378 }
379 if modifiers.has(Modifier::DIM) {
380 *(ptr as *mut [u8; 2]) = [b'2', b';'];
381 ptr = ptr.add(2);
382 }
383 if modifiers.has(Modifier::ITALIC) {
384 *(ptr as *mut [u8; 2]) = [b'3', b';'];
385 ptr = ptr.add(2);
386 }
387 if modifiers.has(Modifier::UNDERLINED) {
388 *(ptr as *mut [u8; 2]) = [b'4', b';'];
389 ptr = ptr.add(2);
390 }
391 if modifiers.has(Modifier::SLOW_BLINK) {
392 *(ptr as *mut [u8; 2]) = [b'5', b';'];
393 ptr = ptr.add(2);
394 }
395 if modifiers.has(Modifier::RAPID_BLINK) {
396 *(ptr as *mut [u8; 2]) = [b'6', b';'];
397 ptr = ptr.add(2);
398 }
399 if modifiers.has(Modifier::REVERSED) {
400 *(ptr as *mut [u8; 2]) = [b'7', b';'];
401 ptr = ptr.add(2);
402 }
403 if modifiers.has(Modifier::HIDDEN) {
404 *(ptr as *mut [u8; 2]) = [b'8', b';'];
405 ptr = ptr.add(2);
406 }
407 if modifiers.has(Modifier::CROSSED_OUT) {
408 *(ptr as *mut [u8; 2]) = [b'9', b';'];
409 ptr = ptr.add(2);
410 }
411 ptr.sub(1)
412 }
413}
414
415pub const ENABLE_NON_MOTION_MOUSE_EVENTS: &[u8] = b"\x1b[?1000h\x1B[?1002h\x1b[?1015h\x1b[?1006h";
417pub const DISABLE_NON_MOTION_MOUSE_EVENTS: &[u8] = b"\x1b[?1006l\x1b[?1015l\x1B[?1002l\x1b[?1000l";
419
420pub const HIDE_CURSOR: &[u8] = b"\x1b[?25l";
422pub const SHOW_CURSOR: &[u8] = b"\x1b[?25h";
424
425pub const ENABLE_ALT_SCREEN: &[u8] = b"\x1b[?1049h";
427pub const DISABLE_ALT_SCREEN: &[u8] = b"\x1b[?1049l";
429pub const POP_KEYBOARD_ENABLEMENT: &[u8] = b"\x1b[<1u";
431
432impl BufferWrite for KeyboardEnhancementFlags {
433 fn write_to_buffer(&self, buffer: &mut Vec<u8>) {
434 buffer.extend_from_slice(b"\x1b[>4;");
435 itoap::write_to_vec(buffer, self.bits());
436 buffer.extend_from_slice(b"m");
437 }
438}
439
440impl BufferWrite for Style {
441 fn write_to_buffer(&self, buffer: &mut Vec<u8>) {
442 style(buffer, *self, false);
443 }
444}
445
446#[derive(Clone, Copy, PartialEq, Eq)]
450pub struct ScrollRegion(pub u16, pub u16);
451
452impl ScrollRegion {
453 pub const RESET: ScrollRegion = ScrollRegion(0, 0);
455}
456impl BufferWrite for ScrollRegion {
457 fn write_to_buffer(&self, buffer: &mut Vec<u8>) {
458 if *self == ScrollRegion::RESET {
459 buffer.extend_from_slice(b"\x1b[r");
460 return;
461 }
462 buffer.extend_from_slice(b"\x1b[");
463 itoap::write_to_vec(buffer, self.0);
464 if self.1 != 0 {
465 buffer.push(b';');
466 itoap::write_to_vec(buffer, self.1);
467 }
468 buffer.push(b'r');
469 }
470}
471
472impl BufferWrite for StyleDelta {
473 fn write_to_buffer(&self, buffer: &mut Vec<u8>) {
474 if self.current == u32::MAX {
475 style(buffer, self.target, true);
476 return;
477 }
478 if self.current == self.target.0 {
479 return;
480 }
481 let removed = (self.target.0 | self.current) ^ self.target.0;
482 let clearing = removed & (Style::HAS_BG | Style::HAS_FG | Modifier::ALL.0 as u32) != 0;
485 if clearing {
486 style(buffer, self.target, true);
487 return;
488 }
489 let mut target = self.target;
490 if Style(self.current).fg() == target.fg() {
491 target = target.without_fg();
492 }
493 if Style(self.current).bg() == target.bg() {
494 target = target.without_bg();
495 }
496 style(buffer, target, false);
497 }
498}
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503
504 fn run_buffer_write_tests(cases: &[(&dyn BufferWrite, &[u8], &str)]) {
505 for (writer, expected, desc) in cases {
506 let mut buf = Vec::new();
508 writer.write_to_buffer(&mut buf);
509 assert_eq!(&buf, expected, "{desc} (empty buffer)");
510
511 let prefix = b"PREFIX";
513 let mut buf = prefix.to_vec();
514 writer.write_to_buffer(&mut buf);
515 let mut expected_with_prefix = prefix.to_vec();
516 expected_with_prefix.extend_from_slice(expected);
517 assert_eq!(&buf, &expected_with_prefix, "{desc} (prefilled buffer)");
518 }
519 }
520
521 #[test]
522 fn buffer_write_sequences() {
523 run_buffer_write_tests(&[
524 (&ScrollBufferUp(5), b"\x1b[5S", "ScrollBufferUp(5)"),
526 (&ScrollBufferUp(100), b"\x1b[100S", "ScrollBufferUp(100)"),
527 (&ScrollBufferUp(65535), b"\x1b[65535S", "ScrollBufferUp(65535)"),
528 (&ScrollBufferDown(3), b"\x1b[3T", "ScrollBufferDown(3)"),
530 (&ScrollBufferDown(1), b"\x1b[1T", "ScrollBufferDown(1)"),
531 (&MoveCursor(0, 0), b"\x1b[1;1H", "MoveCursor(0, 0)"),
533 (&MoveCursor(10, 20), b"\x1b[21;11H", "MoveCursor(10, 20)"),
534 (&MoveCursor(99, 49), b"\x1b[50;100H", "MoveCursor(99, 49)"),
535 (&MoveCursorRight(1), b"\x1b[1C", "MoveCursorRight(1)"),
537 (&MoveCursorRight(50), b"\x1b[50C", "MoveCursorRight(50)"),
538 (&ScrollRegion(1, 24), b"\x1b[1;24r", "ScrollRegion(1, 24)"),
540 (&ScrollRegion::RESET, b"\x1b[r", "ScrollRegion::RESET"),
541 (
543 &Style::DEFAULT.with_fg(crate::Color(196)),
544 b"\x1b[38;5;196m".as_slice(),
545 "Style with fg color 196",
546 ),
547 (&Style::DEFAULT.with_modifier(Modifier::BOLD), b"\x1b[1m".as_slice(), "Style with BOLD"),
549 (
550 &Style::DEFAULT.with_modifier(Modifier::BOLD).with_modifier(Modifier::ITALIC),
551 b"\x1b[1;3m".as_slice(),
552 "Style with BOLD and ITALIC",
553 ),
554 (&Style::DEFAULT, b"".as_slice(), "Style::DEFAULT (no output)"),
556 ]);
557 }
558}