Skip to main content

rusty_bubbletea/
cursed_renderer.rs

1//! Cleanroom Rust port of upstream Go source file: `cursed_renderer.go`
2//! Upstream Target Tag / Version: `v2.0.8`
3//!
4//! <public-docs>
5//! The high-performance standard terminal renderer for Bubble Tea v2.0.8.
6//!
7//! Wraps [rusty_ultraviolet::TerminalRenderer] and
8//! [rusty_ultraviolet::ScreenBuffer] exactly like the upstream `cursedRenderer`
9//! struct, so the emitted ANSI output is byte-identical to Go. Manages
10//! declarative `View` frames, alt-screen mode, cursor visibility, terminal
11//! modes (bracketed paste, focus, mouse, kitty keyboard), window title, and
12//! queued unmanaged messages.
13//! </public-docs>
14
15use 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
27/// CursedRenderer manages high-performance declarative View rendering.
28pub struct CursedRenderer {
29    w: Box<dyn Write + Send + Sync>,
30    /// Updates buffer to be flushed to [Self::w].
31    buf: Vec<u8>,
32    scr: rusty_ultraviolet::TerminalRenderer,
33    cellbuf: ScreenBuffer,
34    last_view: Option<View>,
35    env: Vec<String>,
36    // NOTE: the upstream stores `term` ($TERM); it is only used for the
37    // renderer construction which reads it from the env directly.
38    #[allow(dead_code)]
39    term: String,
40    width: usize,
41    height: usize,
42    // NOTE: upstream guards methods with a `sync.Mutex`; the ported methods
43    // take `&mut self`, so exclusivity is enforced by the type system.
44    profile: rusty_colorprofile::Profile,
45    logger: Option<Box<dyn rusty_ultraviolet::Logger + Send + Sync>>,
46    view: View,
47    /// Whether to use hard tabs to optimize cursor movements.
48    hard_tabs: bool,
49    /// Whether to use backspace to optimize cursor movements.
50    backspace: bool,
51    map_nl: bool,
52    /// Whether to use synchronized output mode for updates.
53    syncd_updates: bool,
54    /// Indicates whether the renderer is starting after being stopped.
55    starting: bool,
56}
57
58/// NewCursedRenderer creates a new [CursedRenderer].
59pub 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    /// SetLogger sets the logger for the renderer.
90    pub fn set_logger(&mut self, logger: Option<Box<dyn rusty_ultraviolet::Logger + Send + Sync>>) {
91        self.logger = logger;
92    }
93
94    /// SetOptimizations sets the cursor movement optimizations.
95    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    /// SetColorProfile sets the color profile of the renderer.
109    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    /// SetSyncdUpdates sets whether synchronized output mode is used.
115    pub fn set_syncd_updates(&mut self, syncd: bool) {
116        self.syncd_updates = syncd;
117    }
118
119    /// SetWidthMethod sets the width method of the renderer.
120    pub fn set_width_method(&mut self, method: WidthMethod) {
121        if method == WidthMethod::GraphemeWidth {
122            // Turn on Unicode mode (2027) for accurate grapheme width
123            // calculation.
124            self.scr
125                .write_string_public(ansi::mode::SET_MODE_UNICODE_CORE)
126                .ok();
127        } else if self.cellbuf.method == WidthMethod::GraphemeWidth {
128            // Turn off Unicode mode if we're switching away from grapheme
129            // width calculation.
130            self.scr
131                .write_string_public(ansi::mode::RESET_MODE_UNICODE_CORE)
132                .ok();
133        }
134        self.cellbuf.method = method;
135    }
136
137    /// SetScrollOptim sets whether to use hard scroll optimizations.
138    pub fn set_scroll_optim(&mut self, v: bool) {
139        self.scr.set_scroll_optim_public(v);
140    }
141}
142
143/// reset reinitializes the internal screen renderer.
144fn 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); // Always start in inline mode
149    s.scr.set_fullscreen_public(false); // Always start in inline mode
150    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); // disable on Windows upstream
158}
159
160/// EnableAltScreen sets the alt screen mode. Writes to the buffer directly if
161/// write is true.
162fn 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
194/// EnableTextCursor sets the text cursor mode.
195fn 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
207/// SetProgressBar writes the progress bar sequence for the given progress
208/// bar.
209fn 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
239/// ViewEquals returns whether the two views are equal.
240pub(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
280/// KeyboardEnhancementsFlags returns the kitty keyboard enhancement flags.
281fn keyboard_enhancements_flags(ke: &KeyboardEnhancements) -> i32 {
282    let mut flags = 1; // always enable basic key disambiguation
283    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
298/// EncodeCursorStyle encodes the cursor shape and blink into the ANSI
299/// sequence value.
300fn encode_cursor_style(style: CursorShape, blink: bool) -> i32 {
301    // We're using the ANSI escape sequence values for cursor styles.
302    let mut s = (style as i32) * 2 + 1;
303    if !blink {
304        s += 1;
305    }
306    s
307}
308
309/// A terminal color update: (new color, old color, reset sequence, setter).
310type 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        // Mark that we're starting. This is used to restore some state when
328        // starting the renderer again after it was stopped.
329        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        // Enable modifyOtherKeys and Kitty keyboard protocol.
401        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        // Exit the altScreen and show cursor before closing. It's important
413        // that we don't change the altScreen and cursorHidden states so that
414        // we can restore them when we start the renderer again.
415        if let Some(lv) = &self.last_view {
416            let lv = lv.clone();
417            // NOTE: The Kitty keyboard specs specify that the terminal should
418            // have two registries for the main and alt screens. Here, we
419            // reset the keyboard protocol of the last screen used.
420            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            // Go to the bottom of the screen.
426            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                // Clear the window title if it was set.
467                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                    // Reset the cursor style to default.
475                    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            // Make sure to turn off Unicode mode (2027).
508            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        // We want to clear the renderer state but not the cursor position.
525        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            // If the component is nil, we should clear the screen buffer.
540            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            // We need to resize the screen based on the frame height and
549            // terminal width.
550            let frame_height = content.height();
551            if frame_height != frame_area.dy() {
552                frame_area.max.1 = frame_height;
553            }
554        }
555
556        // Restore tab stops if we have tab optimizations enabled.
557        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            // No changes, nothing to do.
570            return Ok(());
571        }
572
573        // We're no longer starting.
574        self.starting = false;
575
576        if frame_area != self.cellbuf.bounds() {
577            self.scr.erase_public(); // Force a full redraw to avoid artifacts.
578
579            // We need to reset the touched lines buffer to match the new
580            // height.
581            self.cellbuf.render_buffer.touched.clear();
582
583            // Resize the screen buffer to match the frame area.
584            self.cellbuf
585                .render_buffer
586                .buffer
587                .resize(frame_area.dx(), frame_area.dy());
588        }
589
590        // Clear our screen buffer before copying the new frame into it to
591        // ensure we erase any old content.
592        self.cellbuf.render_buffer.clear();
593        let bounds = self.cellbuf.bounds();
594        content.draw(&mut self.cellbuf, bounds);
595
596        // If the frame height is greater than the screen height, we drop the
597        // lines from the top of the buffer.
598        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        // Alt screen mode.
605        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        // bracketed paste mode.
613        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        // report focus events mode.
633        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        // mouse events mode.
648        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        // Set window title.
694        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        // kitty keyboard protocol
704        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            // Enable modifyOtherKeys and Kitty keyboard protocol.
711            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                // Request keyboard enhancements when they change.
721                let _ = self
722                    .scr
723                    .write_string_public(ansi::kitty::REQUEST_KITTY_KEYBOARD);
724            }
725        }
726
727        // Set terminal colors.
728        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                        // Reset the color if it was set to nil.
761                        let _ = self.scr.write_string_public(reset);
762                    }
763                    Some(col) => {
764                        // Set the color.
765                        let _ = self.scr.write_string_public(&setter(&col.hex()));
766                    }
767                }
768            }
769        }
770
771        // Set cursor shape and blink if set.
772        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        // Render progress bar if it's changed.
788        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        // Render and queue changes to the screen buffer.
800        self.scr.render_public(&mut self.cellbuf.render_buffer);
801
802        if let Some(cur) = &view.cursor {
803            // MoveTo must come after Render because the cursor position might
804            // get updated during rendering.
805            self.scr
806                .move_to_public(cur.position.x as i64, cur.position.y as i64);
807        } else if !view.alt_screen {
808            // We don't want the cursor to be dangling at the end of the line
809            // in inline mode.
810            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        // Check if we have any render updates to flush.
821        let has_updates = !self.buf.is_empty();
822
823        // Cursor visibility.
824        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        // Build final output buffer with synchronized output or hide/show
835        // cursor updates. But first, enter/exit alt screen mode if needed.
836        let mut buf: Vec<u8> = Vec::new();
837        if should_update_alt_screen {
838            // We always disable keyboard enhancements when switching screens.
839            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        // Reset internal screen renderer buffer.
880        self.buf.clear();
881
882        // If our updates flush buffer has content, write it to the output
883        // writer.
884        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            // Rust's stdout is line-buffered: without an explicit flush the
901            // tail of a frame after a newline stays buffered and never
902            // reaches the terminal until the next newline-terminated write
903            // (the upstream os.Stdout is unbuffered). Flush every frame.
904            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        // We need to scroll the screen up by the number of lines in the
926        // queue.
927        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        // Scroll the screen up by the offset to make room for the new lines.
943        sb.push_str(&"\n".repeat(offset));
944
945        // XXX: Now go to the top of the screen, insert new lines, and write
946        // the queued strings.
947        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        // We need to mark the screen for clear to force a redraw.
965        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        // Move the cursor to the top left corner of the screen and trigger a
973        // full screen redraw.
974        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) {}