1use crate::color::Color;
16use crate::cursor::CursorShape;
17use crate::keyboard::KeyboardEnhancements;
18use crate::model::Cmd;
19use crate::mouse::MouseMsg;
20use crate::renderer::Renderer;
21use crate::view::{MouseMode, ProgressBar, ProgressBarState, View};
22use rusty_ultraviolet::{Environ, ScreenBuffer, StyledString};
23use rusty_x_ansi as ansi;
24use rusty_x_ansi::method::WidthMethod;
25use std::io::Write;
26
27pub struct CursedRenderer {
29 w: Box<dyn Write + Send + Sync>,
30 buf: Vec<u8>,
32 scr: rusty_ultraviolet::TerminalRenderer,
33 cellbuf: ScreenBuffer,
34 last_view: Option<View>,
35 env: Vec<String>,
36 #[allow(dead_code)]
39 term: String,
40 width: usize,
41 height: usize,
42 profile: rusty_colorprofile::Profile,
45 logger: Option<Box<dyn rusty_ultraviolet::Logger + Send + Sync>>,
46 view: View,
47 hard_tabs: bool,
49 backspace: bool,
51 map_nl: bool,
52 syncd_updates: bool,
54 starting: bool,
56}
57
58pub fn new_cursed_renderer(
60 w: Box<dyn Write + Send + Sync>,
61 env: &[String],
62 width: usize,
63 height: usize,
64) -> CursedRenderer {
65 let mut s = CursedRenderer {
66 w,
67 buf: Vec::new(),
68 scr: rusty_ultraviolet::TerminalRenderer::new_without_writer(&Environ(env.to_vec())),
69 cellbuf: rusty_ultraviolet::new_screen_buffer(width, height),
70 last_view: None,
71 env: env.to_vec(),
72 term: Environ(env.to_vec()).getenv("TERM"),
73 width,
74 height,
75 profile: rusty_colorprofile::Profile::NoTty,
76 logger: None,
77 view: View::default(),
78 hard_tabs: false,
79 backspace: false,
80 map_nl: false,
81 syncd_updates: false,
82 starting: false,
83 };
84 reset(&mut s);
85 s
86}
87
88impl CursedRenderer {
89 pub fn set_logger(&mut self, logger: Option<Box<dyn rusty_ultraviolet::Logger + Send + Sync>>) {
91 self.logger = logger;
92 }
93
94 pub fn set_optimizations(&mut self, hard_tabs: bool, backspace: bool, map_nl: bool) {
96 self.hard_tabs = hard_tabs;
97 self.backspace = backspace;
98 self.map_nl = map_nl;
99 if self.hard_tabs {
100 self.scr.set_tab_stops_public(self.width as i32);
101 } else {
102 self.scr.set_tab_stops_public(-1);
103 }
104 self.scr.set_backspace_public(self.backspace);
105 self.scr.set_map_newline_public(self.map_nl);
106 }
107
108 pub fn set_color_profile(&mut self, p: rusty_colorprofile::Profile) {
110 self.profile = p;
111 self.scr.set_color_profile_public(p);
112 }
113
114 pub fn set_syncd_updates(&mut self, syncd: bool) {
116 self.syncd_updates = syncd;
117 }
118
119 pub fn set_width_method(&mut self, method: WidthMethod) {
121 if method == WidthMethod::GraphemeWidth {
122 self.scr
125 .write_string_public(ansi::mode::SET_MODE_UNICODE_CORE)
126 .ok();
127 } else if self.cellbuf.method == WidthMethod::GraphemeWidth {
128 self.scr
131 .write_string_public(ansi::mode::RESET_MODE_UNICODE_CORE)
132 .ok();
133 }
134 self.cellbuf.method = method;
135 }
136
137 pub fn set_scroll_optim(&mut self, v: bool) {
139 self.scr.set_scroll_optim_public(v);
140 }
141}
142
143fn reset(s: &mut CursedRenderer) {
145 s.buf.clear();
146 s.scr = rusty_ultraviolet::TerminalRenderer::new_without_writer(&Environ(s.env.clone()));
147 s.scr.set_color_profile_public(s.profile);
148 s.scr.set_relative_cursor_public(true); s.scr.set_fullscreen_public(false); if s.hard_tabs {
151 s.scr.set_tab_stops_public(s.width as i32);
152 } else {
153 s.scr.set_tab_stops_public(-1);
154 }
155 s.scr.set_backspace_public(s.backspace);
156 s.scr.set_map_newline_public(s.map_nl);
157 s.scr.set_scroll_optim_public(true); }
159
160fn enable_alt_screen(s: &mut CursedRenderer, enable: bool, write: bool) {
163 if enable {
164 enter_alt_screen(s, write);
165 } else {
166 exit_alt_screen(s, write);
167 }
168}
169
170fn enter_alt_screen(s: &mut CursedRenderer, write: bool) {
171 s.scr.save_cursor_public();
172 if write {
173 let _ = s
174 .scr
175 .write_string_public(ansi::mode::SET_MODE_ALT_SCREEN_SAVE_CURSOR);
176 }
177 s.scr.set_fullscreen_public(true);
178 s.scr.set_relative_cursor_public(false);
179 s.scr.erase_public();
180}
181
182fn exit_alt_screen(s: &mut CursedRenderer, write: bool) {
183 s.scr.erase_public();
184 s.scr.set_relative_cursor_public(true);
185 s.scr.set_fullscreen_public(false);
186 if write {
187 let _ = s
188 .scr
189 .write_string_public(ansi::mode::RESET_MODE_ALT_SCREEN_SAVE_CURSOR);
190 }
191 s.scr.restore_cursor_public();
192}
193
194fn enable_text_cursor(s: &mut CursedRenderer, enable: bool) {
196 if enable {
197 let _ = s
198 .scr
199 .write_string_public(ansi::mode::SET_MODE_TEXT_CURSOR_ENABLE);
200 } else {
201 let _ = s
202 .scr
203 .write_string_public(ansi::mode::RESET_MODE_TEXT_CURSOR_ENABLE);
204 }
205}
206
207fn set_progress_bar(s: &mut CursedRenderer, pb: Option<&ProgressBar>) {
210 match pb {
211 None => {
212 let _ = s
213 .scr
214 .write_string_public(ansi::progress::RESET_PROGRESS_BAR);
215 }
216 Some(pb) => {
217 let seq = match pb.state {
218 ProgressBarState::ProgressBarNone => ansi::progress::RESET_PROGRESS_BAR.to_string(),
219 ProgressBarState::ProgressBarDefault => {
220 ansi::progress::set_progress_bar(pb.value as i32)
221 }
222 ProgressBarState::ProgressBarError => {
223 ansi::progress::set_error_progress_bar(pb.value as i32)
224 }
225 ProgressBarState::ProgressBarIndeterminate => {
226 ansi::progress::SET_INDETERMINATE_PROGRESS_BAR.to_string()
227 }
228 ProgressBarState::ProgressBarWarning => {
229 ansi::progress::set_warning_progress_bar(pb.value as i32)
230 }
231 };
232 if !seq.is_empty() {
233 let _ = s.scr.write_string_public(&seq);
234 }
235 }
236 }
237}
238
239pub(crate) fn view_equals(a: &View, b: &View) -> bool {
241 if a.content != b.content
242 || a.alt_screen != b.alt_screen
243 || a.disable_bracketed_paste_mode != b.disable_bracketed_paste_mode
244 || a.report_focus != b.report_focus
245 || a.mouse_mode != b.mouse_mode
246 || a.window_title != b.window_title
247 || a.foreground_color != b.foreground_color
248 || a.background_color != b.background_color
249 || a.keyboard_enhancements != b.keyboard_enhancements
250 {
251 return false;
252 }
253
254 if (a.cursor.is_none()) != (b.cursor.is_none()) {
255 return false;
256 }
257 if let (Some(ac), Some(bc)) = (&a.cursor, &b.cursor) {
258 if ac.position.x != bc.position.x
259 || ac.position.y != bc.position.y
260 || ac.shape != bc.shape
261 || ac.blink != bc.blink
262 || ac.color != bc.color
263 {
264 return false;
265 }
266 }
267
268 if (a.progress_bar.is_none()) != (b.progress_bar.is_none()) {
269 return false;
270 }
271 if let (Some(ap), Some(bp)) = (&a.progress_bar, &b.progress_bar) {
272 if ap.state != bp.state || ap.value != bp.value {
273 return false;
274 }
275 }
276
277 true
278}
279
280fn keyboard_enhancements_flags(ke: &KeyboardEnhancements) -> i32 {
282 let mut flags = 1; if ke.report_event_types {
284 flags |= ansi::kitty::KITTY_REPORT_EVENT_TYPES as i32;
285 }
286 if ke.report_alternate_keys {
287 flags |= ansi::kitty::KITTY_REPORT_ALTERNATE_KEYS as i32;
288 }
289 if ke.report_all_keys_as_escape_codes {
290 flags |= ansi::kitty::KITTY_REPORT_ALL_KEYS_AS_ESCAPE_CODES as i32;
291 }
292 if ke.report_associated_text {
293 flags |= ansi::kitty::KITTY_REPORT_ASSOCIATED_KEYS as i32;
294 }
295 flags
296}
297
298fn encode_cursor_style(style: CursorShape, blink: bool) -> i32 {
301 let mut s = (style as i32) * 2 + 1;
303 if !blink {
304 s += 1;
305 }
306 s
307}
308
309type ColorUpdate = (
311 Option<Color>,
312 Option<Color>,
313 &'static str,
314 fn(&str) -> String,
315);
316
317impl Renderer for CursedRenderer {
318 fn set_optimizations(&mut self, hard_tabs: bool, backspace: bool, map_nl: bool) {
319 CursedRenderer::set_optimizations(self, hard_tabs, backspace, map_nl);
320 }
321
322 fn set_color_profile(&mut self, p: rusty_colorprofile::Profile) {
323 CursedRenderer::set_color_profile(self, p);
324 }
325
326 fn start(&mut self) {
327 self.starting = true;
330
331 let Some(lv) = self.last_view.clone() else {
332 return;
333 };
334
335 if lv.alt_screen {
336 enable_alt_screen(self, true, true);
337 }
338 enable_text_cursor(self, lv.cursor.is_some());
339 if let Some(cur) = &lv.cursor {
340 if let Some(col) = cur.color {
341 let col: Color = col;
342 let _ = self
343 .scr
344 .write_string_public(&ansi::background::set_cursor_color(&col.hex()));
345 }
346 let cur_style = encode_cursor_style(cur.shape, cur.blink);
347 if cur_style != 0 && cur_style != 1 {
348 let _ = self
349 .scr
350 .write_string_public(&ansi::cursor::set_cursor_style(cur_style));
351 }
352 }
353 if let Some(col) = lv.foreground_color {
354 let _ = self
355 .scr
356 .write_string_public(&ansi::background::set_foreground_color(&col.hex()));
357 }
358 if let Some(col) = lv.background_color {
359 let _ = self
360 .scr
361 .write_string_public(&ansi::background::set_background_color(&col.hex()));
362 }
363 if !lv.disable_bracketed_paste_mode {
364 let _ = self
365 .scr
366 .write_string_public(ansi::mode::SET_MODE_BRACKETED_PASTE);
367 }
368 if lv.report_focus {
369 let _ = self
370 .scr
371 .write_string_public(ansi::mode::SET_MODE_FOCUS_EVENT);
372 }
373 match lv.mouse_mode {
374 MouseMode::MouseModeNone => {}
375 MouseMode::MouseModeCellMotion => {
376 let _ = self.scr.write_string_public(
377 ansi::mode::SET_MODE_MOUSE_BUTTON_EVENT.to_owned().as_str(),
378 );
379 let _ = self
380 .scr
381 .write_string_public(ansi::mode::SET_MODE_MOUSE_EXT_SGR);
382 }
383 MouseMode::MouseModeAllMotion => {
384 let _ = self
385 .scr
386 .write_string_public(ansi::mode::SET_MODE_MOUSE_ANY_EVENT);
387 let _ = self
388 .scr
389 .write_string_public(ansi::mode::SET_MODE_MOUSE_EXT_SGR);
390 }
391 }
392 if !lv.window_title.is_empty() {
393 let _ = self
394 .scr
395 .write_string_public(&ansi::screen::set_window_title(&lv.window_title));
396 }
397 if lv.progress_bar.is_some() {
398 set_progress_bar(self, lv.progress_bar.as_ref());
399 }
400 let _ = self
402 .scr
403 .write_string_public(ansi::mode::ENABLE_MODIFY_OTHER_KEYS2);
404
405 let kitty_flags = keyboard_enhancements_flags(&lv.keyboard_enhancements);
406 let _ = self
407 .scr
408 .write_string_public(&ansi::kitty::kitty_keyboard(kitty_flags as u8, 1));
409 }
410
411 fn close(&mut self) -> Result<(), Box<dyn std::error::Error>> {
412 if let Some(lv) = &self.last_view {
416 let lv = lv.clone();
417 self.scr
421 .write_string_public(ansi::mode::RESET_MODIFY_OTHER_KEYS)?;
422 self.scr
423 .write_string_public(&ansi::kitty::kitty_keyboard(0, 1))?;
424
425 self.scr.move_to_public(0, self.cellbuf.height() as i64 - 1);
427 let mut out = Vec::new();
428 self.scr.flush_into(&mut out);
429 self.buf.extend_from_slice(&out);
430 if lv.alt_screen {
431 enable_alt_screen(self, false, true);
432 } else {
433 let _ = self
434 .scr
435 .write_string_public(ansi::screen::ERASE_SCREEN_BELOW);
436 }
437 if lv.cursor.is_none() {
438 enable_text_cursor(self, true);
439 }
440 if !lv.disable_bracketed_paste_mode {
441 let _ = self
442 .scr
443 .write_string_public(ansi::mode::RESET_MODE_BRACKETED_PASTE);
444 }
445 if lv.report_focus {
446 let _ = self
447 .scr
448 .write_string_public(ansi::mode::RESET_MODE_FOCUS_EVENT);
449 }
450 match lv.mouse_mode {
451 MouseMode::MouseModeNone => {}
452 MouseMode::MouseModeCellMotion | MouseMode::MouseModeAllMotion => {
453 let _ = self
454 .scr
455 .write_string_public(ansi::mode::RESET_MODE_MOUSE_BUTTON_EVENT);
456 let _ = self
457 .scr
458 .write_string_public(ansi::mode::RESET_MODE_MOUSE_ANY_EVENT);
459 let _ = self
460 .scr
461 .write_string_public(ansi::mode::RESET_MODE_MOUSE_EXT_SGR);
462 }
463 }
464
465 if !lv.window_title.is_empty() {
466 let _ = self
468 .scr
469 .write_string_public(&ansi::screen::set_window_title(""));
470 }
471 if let Some(lc) = &lv.cursor {
472 let cur_shape = encode_cursor_style(lc.shape, lc.blink);
473 if cur_shape != 0 && cur_shape != 1 {
474 let _ = self
476 .scr
477 .write_string_public(&ansi::cursor::set_cursor_style(0));
478 }
479
480 if lc.color.is_some() {
481 let _ = self
482 .scr
483 .write_string_public(ansi::background::RESET_CURSOR_COLOR);
484 }
485 }
486
487 if lv.background_color.is_some() {
488 let _ = self
489 .scr
490 .write_string_public(ansi::background::RESET_BACKGROUND_COLOR);
491 }
492 if lv.foreground_color.is_some() {
493 let _ = self
494 .scr
495 .write_string_public(ansi::background::RESET_FOREGROUND_COLOR);
496 }
497 if let Some(pb) = &lv.progress_bar {
498 if pb.state != ProgressBarState::ProgressBarNone {
499 let _ = self
500 .scr
501 .write_string_public(ansi::progress::RESET_PROGRESS_BAR);
502 }
503 }
504 }
505
506 if self.cellbuf.method == WidthMethod::GraphemeWidth {
507 let _ = self
509 .scr
510 .write_string_public(ansi::mode::RESET_MODE_UNICODE_CORE);
511 }
512
513 let mut out = Vec::new();
514 self.scr.flush_into(&mut out);
515 self.buf.extend_from_slice(&out);
516
517 if !self.buf.is_empty() {
518 self.w.write_all(&self.buf)?;
519 self.buf.clear();
520 }
521
522 let (x, y) = self.scr.position_public();
523
524 reset(self);
526 self.scr.set_position_public(x, y);
527
528 Ok(())
529 }
530
531 fn render(&mut self, view: View) {
532 self.view = view;
533 }
534
535 fn flush(&mut self, closing: bool) -> Result<(), Box<dyn std::error::Error>> {
536 let view = self.view.clone();
537 let mut frame_area = rusty_ultraviolet::rect(0, 0, self.width, self.height);
538 if view.content.is_empty() {
539 frame_area.max.1 = 0;
541 }
542
543 let content = StyledString {
544 text: view.content.clone(),
545 ..StyledString::default()
546 };
547 if !view.alt_screen {
548 let frame_height = content.height();
551 if frame_height != frame_area.dy() {
552 frame_area.max.1 = frame_height;
553 }
554 }
555
556 if self.starting && self.hard_tabs {
558 let _ = self
559 .scr
560 .write_string_public(ansi::screen::SET_TAB_EVERY_8_COLUMNS);
561 }
562
563 if !self.starting
564 && !closing
565 && self.last_view.is_some()
566 && view_equals(self.last_view.as_ref().unwrap(), &view)
567 && frame_area == self.cellbuf.bounds()
568 {
569 return Ok(());
571 }
572
573 self.starting = false;
575
576 if frame_area != self.cellbuf.bounds() {
577 self.scr.erase_public(); self.cellbuf.render_buffer.touched.clear();
582
583 self.cellbuf
585 .render_buffer
586 .buffer
587 .resize(frame_area.dx(), frame_area.dy());
588 }
589
590 self.cellbuf.render_buffer.clear();
593 let bounds = self.cellbuf.bounds();
594 content.draw(&mut self.cellbuf, bounds);
595
596 let frame_height = frame_area.dy();
599 if frame_height > self.height {
600 let drop = frame_height - self.height;
601 self.cellbuf.render_buffer.buffer.lines.drain(..drop);
602 }
603
604 let should_update_alt_screen = (self.last_view.is_none() && view.alt_screen)
606 || (self.last_view.is_some()
607 && self.last_view.as_ref().unwrap().alt_screen != view.alt_screen);
608 if should_update_alt_screen {
609 enable_alt_screen(self, view.alt_screen, false);
610 }
611
612 if self.last_view.is_none()
614 || view.disable_bracketed_paste_mode
615 != self
616 .last_view
617 .as_ref()
618 .unwrap()
619 .disable_bracketed_paste_mode
620 {
621 if !view.disable_bracketed_paste_mode {
622 let _ = self
623 .scr
624 .write_string_public(ansi::mode::SET_MODE_BRACKETED_PASTE);
625 } else if self.last_view.is_some() {
626 let _ = self
627 .scr
628 .write_string_public(ansi::mode::RESET_MODE_BRACKETED_PASTE);
629 }
630 }
631
632 if self.last_view.is_none()
634 || self.last_view.as_ref().unwrap().report_focus != view.report_focus
635 {
636 if view.report_focus {
637 let _ = self
638 .scr
639 .write_string_public(ansi::mode::SET_MODE_FOCUS_EVENT);
640 } else if self.last_view.is_some() {
641 let _ = self
642 .scr
643 .write_string_public(ansi::mode::RESET_MODE_FOCUS_EVENT);
644 }
645 }
646
647 let last_mouse = self.last_view.as_ref().map(|v| v.mouse_mode);
649 if self.last_view.is_none() || last_mouse != Some(view.mouse_mode) {
650 match view.mouse_mode {
651 MouseMode::MouseModeNone => {
652 if last_mouse.is_some() && last_mouse != Some(MouseMode::MouseModeNone) {
653 let _ = self
654 .scr
655 .write_string_public(ansi::mode::RESET_MODE_MOUSE_BUTTON_EVENT);
656 let _ = self
657 .scr
658 .write_string_public(ansi::mode::RESET_MODE_MOUSE_ANY_EVENT);
659 let _ = self
660 .scr
661 .write_string_public(ansi::mode::RESET_MODE_MOUSE_EXT_SGR);
662 }
663 }
664 MouseMode::MouseModeCellMotion => {
665 if last_mouse == Some(MouseMode::MouseModeAllMotion) {
666 let _ = self
667 .scr
668 .write_string_public(ansi::mode::RESET_MODE_MOUSE_ANY_EVENT);
669 }
670 let _ = self
671 .scr
672 .write_string_public(ansi::mode::SET_MODE_MOUSE_BUTTON_EVENT);
673 let _ = self
674 .scr
675 .write_string_public(ansi::mode::SET_MODE_MOUSE_EXT_SGR);
676 }
677 MouseMode::MouseModeAllMotion => {
678 if last_mouse == Some(MouseMode::MouseModeCellMotion) {
679 let _ = self
680 .scr
681 .write_string_public(ansi::mode::RESET_MODE_MOUSE_BUTTON_EVENT);
682 }
683 let _ = self
684 .scr
685 .write_string_public(ansi::mode::SET_MODE_MOUSE_ANY_EVENT);
686 let _ = self
687 .scr
688 .write_string_public(ansi::mode::SET_MODE_MOUSE_EXT_SGR);
689 }
690 }
691 }
692
693 let last_title = self.last_view.as_ref().map(|v| v.window_title.clone());
695 if (self.last_view.is_none() || last_title.as_deref() != Some(view.window_title.as_str()))
696 && (self.last_view.is_some() || !view.window_title.is_empty())
697 {
698 let _ = self
699 .scr
700 .write_string_public(&ansi::screen::set_window_title(&view.window_title));
701 }
702
703 let last_ke = self.last_view.as_ref().map(|v| v.keyboard_enhancements);
705 let last_alt = self.last_view.as_ref().map(|v| v.alt_screen);
706 if self.last_view.is_none()
707 || last_ke.as_ref() != Some(&view.keyboard_enhancements)
708 || last_alt != Some(view.alt_screen)
709 {
710 let _ = self
712 .scr
713 .write_string_public(ansi::mode::ENABLE_MODIFY_OTHER_KEYS2);
714
715 let kitty_flags = keyboard_enhancements_flags(&view.keyboard_enhancements);
716 let _ = self
717 .scr
718 .write_string_public(&ansi::kitty::kitty_keyboard(kitty_flags as u8, 1));
719 if !closing {
720 let _ = self
722 .scr
723 .write_string_public(ansi::kitty::REQUEST_KITTY_KEYBOARD);
724 }
725 }
726
727 let cc = view.cursor.as_ref().and_then(|c| c.color);
729 let lcc = self
730 .last_view
731 .as_ref()
732 .and_then(|v| v.cursor.as_ref())
733 .and_then(|c| c.color);
734 let lfg = self.last_view.as_ref().and_then(|v| v.foreground_color);
735 let lbg = self.last_view.as_ref().and_then(|v| v.background_color);
736 let colors: [ColorUpdate; 3] = [
737 (
738 cc,
739 lcc,
740 ansi::background::RESET_CURSOR_COLOR,
741 ansi::background::set_cursor_color,
742 ),
743 (
744 view.foreground_color,
745 lfg,
746 ansi::background::RESET_FOREGROUND_COLOR,
747 ansi::background::set_foreground_color,
748 ),
749 (
750 view.background_color,
751 lbg,
752 ansi::background::RESET_BACKGROUND_COLOR,
753 ansi::background::set_background_color,
754 ),
755 ];
756 for (new_color, old_color, reset, setter) in colors {
757 if new_color != old_color {
758 match new_color {
759 None => {
760 let _ = self.scr.write_string_public(reset);
762 }
763 Some(col) => {
764 let _ = self.scr.write_string_public(&setter(&col.hex()));
766 }
767 }
768 }
769 }
770
771 let cc_style = view
773 .cursor
774 .as_ref()
775 .map(|c| encode_cursor_style(c.shape, c.blink));
776 let lc_style = self
777 .last_view
778 .as_ref()
779 .and_then(|v| v.cursor.as_ref())
780 .map(|c| encode_cursor_style(c.shape, c.blink));
781 if cc_style != lc_style {
782 let _ = self
783 .scr
784 .write_string_public(&ansi::cursor::set_cursor_style(cc_style.unwrap_or(0)));
785 }
786
787 let last_pb = self.last_view.as_ref().and_then(|v| v.progress_bar);
789 let view_pb = view.progress_bar;
790 let pb_changed = (self.last_view.is_none()
791 && view_pb.is_some()
792 && view_pb.map(|p| p.state) != Some(ProgressBarState::ProgressBarNone))
793 || (self.last_view.is_some() && (last_pb.is_none()) != (view_pb.is_none()))
794 || (last_pb.is_some() && view_pb.is_some() && last_pb != view_pb);
795 if pb_changed {
796 set_progress_bar(self, view_pb.as_ref());
797 }
798
799 self.scr.render_public(&mut self.cellbuf.render_buffer);
801
802 if let Some(cur) = &view.cursor {
803 self.scr
806 .move_to_public(cur.position.x as i64, cur.position.y as i64);
807 } else if !view.alt_screen {
808 let (x, y) = self.scr.position_public();
811 if x >= self.width.saturating_sub(1) {
812 self.scr.move_to_public(0, y as i64);
813 }
814 }
815
816 let mut out = Vec::new();
817 self.scr.flush_into(&mut out);
818 self.buf.extend_from_slice(&out);
819
820 let has_updates = !self.buf.is_empty();
822
823 let did_show_cursor = self
825 .last_view
826 .as_ref()
827 .map(|v| v.cursor.is_some())
828 .unwrap_or(false);
829 let show_cursor = view.cursor.is_some();
830 let hide_cursor = !show_cursor;
831 let should_update_cursor_vis = (self.last_view.is_none() || did_show_cursor != show_cursor)
832 || should_update_alt_screen;
833
834 let mut buf: Vec<u8> = Vec::new();
837 if should_update_alt_screen {
838 let _ = ansi::mode::RESET_MODIFY_OTHER_KEYS;
840 buf.extend_from_slice(ansi::mode::RESET_MODIFY_OTHER_KEYS.as_bytes());
841 buf.extend_from_slice(ansi::kitty::kitty_keyboard(0, 1).as_bytes());
842 if view.alt_screen {
843 buf.extend_from_slice(ansi::mode::SET_MODE_ALT_SCREEN_SAVE_CURSOR.as_bytes());
844 } else {
845 buf.extend_from_slice(ansi::mode::RESET_MODE_ALT_SCREEN_SAVE_CURSOR.as_bytes());
846 }
847 }
848
849 if self.syncd_updates {
850 if has_updates {
851 buf.extend_from_slice(ansi::mode::SET_MODE_SYNCHRONIZED_OUTPUT.as_bytes());
852 }
853 if should_update_cursor_vis && hide_cursor {
854 buf.extend_from_slice(ansi::mode::RESET_MODE_TEXT_CURSOR_ENABLE.as_bytes());
855 }
856 } else if (should_update_cursor_vis && hide_cursor)
857 || (has_updates && show_cursor && did_show_cursor)
858 {
859 buf.extend_from_slice(ansi::mode::RESET_MODE_TEXT_CURSOR_ENABLE.as_bytes());
860 }
861
862 if has_updates {
863 buf.extend_from_slice(&self.buf);
864 }
865
866 if self.syncd_updates {
867 if should_update_cursor_vis && show_cursor {
868 buf.extend_from_slice(ansi::mode::SET_MODE_TEXT_CURSOR_ENABLE.as_bytes());
869 }
870 if has_updates {
871 buf.extend_from_slice(ansi::mode::RESET_MODE_SYNCHRONIZED_OUTPUT.as_bytes());
872 }
873 } else if (should_update_cursor_vis && show_cursor)
874 || (has_updates && show_cursor && did_show_cursor)
875 {
876 buf.extend_from_slice(ansi::mode::SET_MODE_TEXT_CURSOR_ENABLE.as_bytes());
877 }
878
879 self.buf.clear();
881
882 if std::env::var("UV_RENDER_DEBUG").is_ok() && !buf.is_empty() {
885 use std::io::Write as _;
886 let mut f = std::fs::OpenOptions::new()
887 .create(true)
888 .append(true)
889 .open("/tmp/flush.log")
890 .unwrap();
891 let _ = writeln!(
892 f,
893 "FLUSH ({}) {:?}",
894 buf.len(),
895 String::from_utf8_lossy(&buf)
896 );
897 }
898 if !buf.is_empty() {
899 self.w.write_all(&buf)?;
900 self.w.flush()?;
905 }
906
907 self.last_view = Some(view);
908
909 Ok(())
910 }
911
912 fn reset(&mut self) {
913 reset(self);
914 }
915
916 fn insert_above(&mut self, str_: String) -> Result<(), Box<dyn std::error::Error>> {
917 if str_.is_empty() {
918 return Ok(());
919 }
920
921 let mut sb = String::new();
922 let (w, h) = (self.cellbuf.width(), self.cellbuf.height());
923 let (_, y) = self.scr.position_public();
924
925 sb.push('\r');
928 let down = h as i64 - y as i64 - 1;
929 if down > 0 {
930 sb.push_str(&ansi::cursor::cursor_down(down as i32));
931 }
932
933 let lines: Vec<&str> = str_.split('\n').collect();
934 let mut offset = lines.len();
935 for line in &lines {
936 let line_width = ansi::width::string_width(line);
937 if w > 0 && line_width > w {
938 offset += line_width / w;
939 }
940 }
941
942 sb.push_str(&"\n".repeat(offset));
944
945 let up = offset + h - 1;
948 sb.push_str(&ansi::cursor::cursor_up(up as i32));
949 sb.push_str(&ansi::screen::insert_line(offset as i32));
950 for line in &lines {
951 sb.push_str(line);
952 sb.push_str(ansi::screen::ERASE_LINE_RIGHT);
953 sb.push_str("\r\n");
954 }
955
956 self.scr.set_position_public(0, 0);
957
958 self.w.write_all(sb.as_bytes())?;
959
960 Ok(())
961 }
962
963 fn resize(&mut self, width: usize, height: usize) {
964 self.scr.erase_public();
966 self.width = width;
967 self.height = height;
968 self.scr.resize_public(width, height);
969 }
970
971 fn clear_screen(&mut self) {
972 self.scr.move_to_public(0, 0);
975 self.scr.erase_public();
976 }
977
978 fn write_string(&mut self, s: &str) -> Result<usize, Box<dyn std::error::Error>> {
979 let n = s.len();
980 self.scr.write_string_public(s)?;
981 Ok(n)
982 }
983
984 fn on_mouse(&mut self, m: MouseMsg) -> Cmd {
985 if let Some(lv) = &self.last_view {
986 if let Some(on_mouse) = &lv.on_mouse {
987 return on_mouse(m);
988 }
989 }
990 None
991 }
992}
993
994#[allow(dead_code)]
995fn _assert_send<T: Send>(_: &T) {}