tuifw-screen-winapi 0.21.1

Text User Interface Framework. Basic text screen implementation for Win platform.
Documentation
#![cfg(windows)]

#![feature(allocator_api)]

#![deny(warnings)]
#![doc(test(attr(deny(warnings))))]
#![doc(test(attr(allow(dead_code))))]
#![doc(test(attr(allow(unused_variables))))]
#![allow(clippy::collapsible_if)]
#![allow(clippy::many_single_char_names)]

#![no_std]

extern crate alloc;

use alloc::alloc::Global;
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::alloc::Allocator;
use core::char::{self};
use core::cmp::min;
use core::iter::{once, repeat};
use core::num::NonZeroU16;
use core::ops::Range;
use core::ptr::{null_mut};
use core::str::{self};
use either::{Either, Right, Left};
use errno_no_std::errno;
use num_traits::identities::Zero;
use panicking::panicking;
use tuifw_screen_base::*;
use tuifw_screen_base::Screen as base_Screen;
use unicode_width::UnicodeWidthChar;
use winapi::shared::minwindef::*;
use winapi::shared::ntdef::{WCHAR, HANDLE};
use winapi::um::synchapi::Sleep;
use winapi::um::wincontypes::*;
use winapi::um::wincontypes::INPUT_RECORD_Event;
use winapi::um::wincon::*;
use winapi::um::winnt::*;
use winapi::um::fileapi::*;
use winapi::um::consoleapi::*;
use winapi::um::handleapi::*;
use winapi::um::winuser::*;

const GLOBAL: composable_allocators::Global = composable_allocators::Global;

fn non_zero<Z: Zero>(r: Z, error_alloc: &'static dyn Allocator) -> Result<Z, Error> {
    if r.is_zero() {
        Err(Error::System(Box::new_in(errno(), error_alloc)))
    } else {
        Ok(r)
    }
}

fn valid_handle(h: HANDLE, error_alloc: &'static dyn Allocator) -> Result<HANDLE, Error> {
    if h == INVALID_HANDLE_VALUE {
        Err(Error::System(Box::new_in(errno(), error_alloc)))
    } else {
        Ok(h)
    }
}

pub struct Screen<A: Allocator = Global> {
    error_alloc: &'static dyn Allocator,
    max_size: Option<(u16, u16)>,
    h_input: HANDLE,
    h_output: HANDLE,
    buf: Vec<CHAR_INFO, A>,
    size: Vector,
    invalidated: Rect,
    cursor_is_visible: bool, 
    data: Vec<Range<i16>, A>,
}

impl Screen {
    pub fn new(max_size: Option<(u16, u16)>, error_alloc: Option<&'static dyn Allocator>) -> Result<Self, Error> {
        Self::new_in(max_size, error_alloc, Global)
    }
}

impl<A: Allocator> Screen<A> {
    pub fn new_in(
        max_size: Option<(u16, u16)>,
        error_alloc: Option<&'static dyn Allocator>,
        alloc: A
    ) -> Result<Self, Error> where A: Clone {
        let error_alloc = error_alloc.unwrap_or(&GLOBAL);
        unsafe { FreeConsole() };
        non_zero(unsafe { AllocConsole() }, error_alloc)?;
        let window = unsafe { GetConsoleWindow() };
        assert_ne!(window, null_mut());
        let system_menu = unsafe { GetSystemMenu(window, FALSE) };
        if !system_menu.is_null() { // Wine lacks GetSystemMenu implementation
            unsafe { DeleteMenu(system_menu, SC_CLOSE as UINT, MF_BYCOMMAND); } // non-fatal error
        }
        let mut s = Screen {
            error_alloc,
            max_size,
            h_input: INVALID_HANDLE_VALUE,
            h_output: INVALID_HANDLE_VALUE,
            data: Vec::new_in(alloc.clone()),
            buf: Vec::new_in(alloc),
            size: Vector::null(),
            invalidated: Rect { tl: Point { x: 0, y: 0 }, size: Vector::null() },
            cursor_is_visible: false,
        };
        s.h_input = valid_handle(unsafe { CreateFileA(
            "CONIN$\0".as_ptr() as _,
            GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            null_mut(),
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            null_mut())
        }, error_alloc)?;
        s.h_output = valid_handle(unsafe { CreateFileA(
            "CONOUT$\0".as_ptr() as _,
            GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            null_mut(),
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            null_mut())
        }, error_alloc)?;
        non_zero(unsafe { SetConsoleMode(s.h_input, ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT) }, error_alloc)?;
        non_zero(unsafe { SetConsoleMode(s.h_output, 0) }, error_alloc)?;
        s.resize()?;
        Ok(s)
    }

    fn init_screen_buffer(&mut self) -> Result<Vector, Error> {
        for _ in 0 .. 50 {
            pump_messages();
        }
        let mut ci = CONSOLE_SCREEN_BUFFER_INFO {
            dwSize: COORD { X: 0, Y: 0 },
            dwCursorPosition: COORD { X: 0, Y: 0 },
            wAttributes: 0,
            srWindow: SMALL_RECT { Left: 0, Top: 0, Right: 0, Bottom: 0 },
            dwMaximumWindowSize: COORD { X: 0, Y: 0 }
        };
        non_zero(unsafe { GetConsoleScreenBufferInfo(self.h_output, &mut ci as *mut _) }, self.error_alloc)?;
        let mut width = ci.srWindow.Right.saturating_sub(ci.srWindow.Left).saturating_add(1);
        let mut height = ci.srWindow.Bottom.saturating_sub(ci.srWindow.Top).saturating_add(1);
        ci.srWindow.Left = 0;
        ci.srWindow.Top = 0;
        ci.srWindow.Right = width - 1;
        ci.srWindow.Bottom = height - 1;
        let _ = non_zero(unsafe { SetConsoleWindowInfo(self.h_output, 1, &ci.srWindow as *const _) }, self.error_alloc);
        let _ = non_zero(unsafe { SetConsoleScreenBufferSize(self.h_output, COORD { X: width, Y: height }) }, self.error_alloc);
        non_zero(unsafe { FlushConsoleInputBuffer(self.h_input) }, self.error_alloc)?;
        self.set_cursor_is_visible(self.h_output, self.cursor_is_visible)?;
        if let Some(max_size) = self.max_size {
            width = min(width as u16, max_size.0) as i16;
            height = min(height as u16, max_size.1) as i16;
        }
        Ok(Vector { x: width, y: height })
    }

    fn resize(&mut self) -> Result<(), Error> {
        let size = self.init_screen_buffer()?;
        let reserve = self.max_size.unwrap_or((size.x as u16, size.y as u16));
        let reserve_data = usize::from(reserve.1).saturating_sub(self.data.len());
        self.data.try_reserve(reserve_data).map_err(|_| Error::Oom)?;
        let buf_len = usize::from(reserve.1).checked_mul(usize::from(reserve.0)).ok_or(Error::Oom)?;
        self.buf.try_reserve(buf_len.saturating_sub(self.buf.len())).map_err(|_| Error::Oom)?;
        self.data.clear();
        self.data.resize(usize::from(size.y as u16), 0 .. size.x);
        let mut space = CHAR_INFO {
            Attributes: 0,
            Char: CHAR_INFO_Char::default()
        };
        *unsafe { space.Char.UnicodeChar_mut() } = b' ' as WCHAR;
        self.size = size;
        self.buf.resize(buf_len, space);
        self.invalidated.size = Vector::null();
        Ok(())
    }

    fn encode_grapheme(g: char) -> Option<Either<u16, (u16, u16)>> {
        let width = g.width().unwrap();
        let mut buf = [0u16; 2];
        let g = g.encode_utf16(&mut buf[..]);
        if g.len() != 1 { return None; }
        if width == 1 {
            Some(Left(g[0]))
        } else if width == 2 {
            Some(Right((g[0], g[0])))
        } else {
            None
        }
    }

    fn start_text(size_x: i16, line: &mut [CHAR_INFO], x: i16) -> i16 {
        if x > 0 && x < size_x {
            if line[x as u16 as usize].Attributes & COMMON_LVB_TRAILING_BYTE != 0 {
                let col = &mut line[(x as u16 as usize) - 1];
                debug_assert!(col.Attributes & COMMON_LVB_LEADING_BYTE != 0);
                col.Attributes &= !COMMON_LVB_LEADING_BYTE;
                *unsafe { col.Char.UnicodeChar_mut() } = b' ' as WCHAR;
                x - 1
            } else {
                x
            }
        } else {
            x
        }
    }

    fn end_text(size_x: i16, line: &mut [CHAR_INFO], x: i16) -> i16 {
        if x > 0 && x < size_x {
            let col = &mut line[x as u16 as usize];
            if col.Attributes & COMMON_LVB_TRAILING_BYTE != 0 {
                col.Attributes &= !COMMON_LVB_TRAILING_BYTE;
                *unsafe { col.Char.UnicodeChar_mut() } = b' ' as WCHAR;
                x + 1
            } else {
                x
            }
        } else {
            x
        }
    }

    unsafe fn drop_raw(&mut self) -> Result<(), Error> {
        if self.h_input != INVALID_HANDLE_VALUE {
            non_zero(CloseHandle(self.h_input), self.error_alloc)?;
        }
        if self.h_output != INVALID_HANDLE_VALUE {
            non_zero(CloseHandle(self.h_output), self.error_alloc)?;
        }
        non_zero(FreeConsole(), self.error_alloc)?;
        Ok(())
    }

    fn update_raw(&mut self, cursor: Option<Point>, wait: bool) -> Result<Option<Event>, Error> {
        if !self.invalidated.is_empty() {
            let mut region = SMALL_RECT {
                Top: self.invalidated.t(),
                Left: self.invalidated.l(),
                Right: self.invalidated.r() - 1,
                Bottom: self.invalidated.b() - 1
            };
            non_zero(unsafe { WriteConsoleOutputW(
                self.h_output,
                self.buf.as_ptr(),
                COORD { X: self.size.x, Y: self.size.y },
                COORD { X: region.Left, Y: region.Top },
                &mut region as *mut _
            ) }, self.error_alloc)?;
            self.invalidated.size = Vector::null();
        }
        let cursor = cursor.and_then(|cursor| {
            if (Rect { tl: Point { x: 0, y: 0 }, size: self.size() }).contains(cursor) {
                Some(cursor)
            } else {
                None
            }
        });
        self.cursor_is_visible = cursor.is_some();
        self.set_cursor_is_visible(self.h_output, self.cursor_is_visible)?;
        let (count, key, c, ctrl, alt) = loop {
            pump_messages();
            if !wait {
                let mut n: DWORD = 0;
                non_zero(unsafe { GetNumberOfConsoleInputEvents(self.h_input, &mut n as *mut _) }, self.error_alloc)?;
                if n == 0 { return Ok(None); }
            }
            let mut input = INPUT_RECORD {
                EventType: 0,
                Event: INPUT_RECORD_Event::default()
            };
            let mut readed: DWORD = 0;
            non_zero(unsafe { ReadConsoleInputW(self.h_input, &mut input as *mut _, 1, &mut readed as *mut _) }, self.error_alloc)?;
            assert_eq!(readed, 1);
            match input.EventType {
                WINDOW_BUFFER_SIZE_EVENT => {
                    self.resize()?;
                    return Ok(Some(Event::Resize));
                },
                KEY_EVENT => {
                    let e = unsafe { input.Event.KeyEvent() };
                    if e.bKeyDown != 0 {
                        break (
                            NonZeroU16::new(e.wRepeatCount).unwrap(),
                            e.wVirtualKeyCode,
                            *unsafe { e.uChar.UnicodeChar() },
                            e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0,
                            e.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0
                        );
                    }
                },
                _ => { }
            }
        };
        Ok(match key as i32 {
            VK_RETURN => Some(Event::Key(count, Key::Enter)),
            VK_TAB => Some(Event::Key(count, Key::Tab)),
            VK_PRIOR => Some(Event::Key(count, Key::PageUp)),
            VK_NEXT => Some(Event::Key(count, Key::PageDown)),
            VK_HOME => Some(Event::Key(count, Key::Home)),
            VK_END => Some(Event::Key(count, Key::End)),
            VK_DOWN => Some(Event::Key(count, Key::Down)),
            VK_UP => Some(Event::Key(count, Key::Up)),
            VK_LEFT => Some(Event::Key(count, Key::Left)),
            VK_RIGHT => Some(Event::Key(count, Key::Right)),
            VK_DELETE => Some(Event::Key(count, Key::Delete)),
            VK_INSERT => Some(Event::Key(count, Key::Insert)),
            VK_F1 => Some(Event::Key(count, Key::F1)),
            VK_F2 => Some(Event::Key(count, Key::F2)),
            VK_F3 => Some(Event::Key(count, Key::F3)),
            VK_F4 => Some(Event::Key(count, Key::F4)),
            VK_F5 => Some(Event::Key(count, Key::F5)),
            VK_F6 => Some(Event::Key(count, Key::F6)),
            VK_F7 => Some(Event::Key(count, Key::F7)),
            VK_F8 => Some(Event::Key(count, Key::F8)),
            VK_F9 => Some(Event::Key(count, Key::F9)),
            VK_F10 => Some(Event::Key(count, Key::F10)),
            VK_F11 => Some(Event::Key(count, Key::F11)),
            VK_F12 => Some(Event::Key(count, Key::F12)),
            VK_ESCAPE => Some(Event::Key(count, Key::Escape)),
            VK_BACK => Some(Event::Key(count, Key::Backspace)),
            0x32 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::At))),
            0x41 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::A))),
            0x42 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::B))),
            0x43 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::C))),
            0x44 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::D))),
            0x45 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::E))),
            0x46 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::F))),
            0x47 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::G))),
            0x48 if ctrl => Some(Event::Key(count, Key::Backspace)),
            0x49 if ctrl => Some(Event::Key(count, Key::Tab)),
            0x4A if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::J))),
            0x4B if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::K))),
            0x4C if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::L))),
            0x4D if ctrl => Some(Event::Key(count, Key::Enter)),
            0x4E if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::N))),
            0x4F if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::O))),
            0x50 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::P))),
            0x51 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Q))),
            0x52 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::R))),
            0x53 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::S))),
            0x54 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::T))),
            0x55 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::U))),
            0x56 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::V))),
            0x57 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::W))),
            0x58 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::X))),
            0x59 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Y))),
            0x5A if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Z))),
            VK_OEM_4 if ctrl => Some(Event::Key(count, Key::Escape)),
            VK_OEM_5 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Backslash))),
            VK_OEM_6 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Bracket))),
            0x36 if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Caret))),
            VK_OEM_MINUS if ctrl => Some(Event::Key(count, Key::Ctrl(Ctrl::Underscore))),
            VK_OEM_2 if ctrl => Some(Event::Key(count, Key::Backspace)),
            _ => {
                assert!(!(0xD800 .. 0xDC00).contains(&c));
                let c = if (0xDC00 .. 0xE000).contains(&c) {
                    assert_eq!(count.get(), 1);
                    let mut input = INPUT_RECORD {
                        EventType: 0,
                        Event: INPUT_RECORD_Event::default()
                    };
                    let mut n: DWORD = 0;
                    non_zero(unsafe { GetNumberOfConsoleInputEvents(self.h_input, &mut n as *mut _) }, self.error_alloc)?;
                    assert_ne!(n, 0);
                    let mut readed: DWORD = 0;
                    non_zero(
                        unsafe { ReadConsoleInputW(self.h_input, &mut input as *mut _, 1, &mut readed as *mut _) },
                        self.error_alloc
                    )?;
                    assert_eq!(readed, 1);
                    assert_eq!(input.EventType, KEY_EVENT);
                    let e = unsafe { input.Event.KeyEvent() };
                    assert!(e.bKeyDown != 0);
                    assert_eq!(e.wRepeatCount, 1);
                    let h = *unsafe { e.uChar.UnicodeChar() };
                    assert!((0xD800 .. 0xDC00).contains(&h));
                    ((h as u32 - 0xD800) << 10) | (c as u32 - 0xDC00)
                } else {
                    c as u32
                };
                let c = char::from_u32(c).unwrap();
                if c >= ' ' && c != '\x7F' {
                    if alt {
                        Some(Event::Key(count, Key::Alt(c)))
                    } else {
                        Some(Event::Key(count, Key::Char(c)))
                    }
                } else {
                    None
                }
            }
        })
    }

    fn set_cursor_is_visible(&self, h_output: HANDLE, cursor_is_visible: bool) -> Result<(), Error> {
        let mut cursor = CONSOLE_CURSOR_INFO { dwSize: 100, bVisible: TRUE };
        cursor.bVisible = if !cursor_is_visible { TRUE } else { FALSE };
        non_zero(unsafe { SetConsoleCursorInfo(h_output, &cursor as *const _) }, self.error_alloc)?;
        pump_messages();
        cursor.dwSize = 25;
        non_zero(unsafe { SetConsoleCursorInfo(h_output, &cursor as *const _) }, self.error_alloc)?;
        pump_messages();
        cursor.bVisible = if cursor_is_visible { TRUE } else { FALSE };
        non_zero(unsafe { SetConsoleCursorInfo(h_output, &cursor as *const _) }, self.error_alloc)?;
        pump_messages();
        Ok(())
    }
}

fn pump_messages() {
    unsafe {
        Sleep(1);
        let mut msg = MSG::default();
        while PeekMessageA(&mut msg as *mut _, null_mut(), 0, 0, PM_REMOVE) != 0 {
            TranslateMessage(&mut msg as *mut _); 
            DispatchMessageA(&msg as *const _); 
        }
    }
}

impl<A: Allocator> Drop for Screen<A> {
    #[allow(clippy::panicking_unwrap)]
    fn drop(&mut self) {
        let e = unsafe { self.drop_raw() };
        if e.is_err() && !panicking() { e.unwrap(); }
    }
}

fn attr_w(fg: Fg, bg: Bg) -> WORD {
    let fg = match fg {
        Fg::Black => 0,
        Fg::DarkGray => FOREGROUND_INTENSITY,
        Fg::Red => FOREGROUND_RED,
        Fg::LightRed => FOREGROUND_INTENSITY | FOREGROUND_RED,
        Fg::Green => FOREGROUND_GREEN,
        Fg::LightGreen => FOREGROUND_INTENSITY | FOREGROUND_GREEN,
        Fg::Brown => FOREGROUND_RED | FOREGROUND_GREEN,
        Fg::Yellow => FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN,
        Fg::Blue => FOREGROUND_BLUE,
        Fg::LightBlue => FOREGROUND_INTENSITY | FOREGROUND_BLUE,
        Fg::Magenta => FOREGROUND_RED | FOREGROUND_BLUE,
        Fg::LightMagenta => FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE,
        Fg::Cyan => FOREGROUND_BLUE | FOREGROUND_GREEN,
        Fg::LightCyan => FOREGROUND_INTENSITY | FOREGROUND_BLUE | FOREGROUND_GREEN,
        Fg::LightGray => FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED,
        Fg::White => FOREGROUND_INTENSITY | FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED,
    };
    let bg = match bg {
        Bg::Black | Bg::None => 0,
        Bg::Red => BACKGROUND_RED,
        Bg::Green => BACKGROUND_GREEN,
        Bg::Brown => BACKGROUND_RED | BACKGROUND_GREEN,
        Bg::Blue => BACKGROUND_BLUE,
        Bg::Magenta => BACKGROUND_RED | BACKGROUND_BLUE,
        Bg::Cyan => BACKGROUND_BLUE | BACKGROUND_GREEN,
        Bg::LightGray => BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED
    };
    fg | bg
}

impl<A: Allocator> base_Screen for Screen<A> {
    fn size(&self) -> Vector { self.size }

    fn out(
        &mut self,
        p: Point,
        fg: Fg,
        bg: Bg,
        text: &str,
        hard: Range<i16>,
        soft: Range<i16>
    ) -> Range<i16> {
        debug_assert!(p.y >= 0 && p.y < self.size().y);
        debug_assert!(hard.start >= 0 && hard.end > hard.start && hard.end <= self.size().x);
        debug_assert!(soft.start >= 0 && soft.end > soft.start && soft.end <= self.size().x);
        let text_end = if soft.end <= p.x { return 0 .. 0 } else { soft.end.saturating_sub(p.x) };
        let text_start = if soft.start <= p.x { 0 } else { soft.start.saturating_sub(p.x) };
        let size = self.size;
        let line = (p.y as u16 as usize) * (size.x as u16 as usize);
        let line = &mut self.buf[line .. line + size.x as u16 as usize];
        let attr = attr_w(fg, bg);
        let mut x0 = None;
        let mut x = p.x;
        let mut n = 0i16;
        let text = text.chars()
            .filter(|&x| x != '\0' && x.width().is_some())
            .flat_map(|c| Self::encode_grapheme(c).map_or_else(
                || Left(repeat(Left(0x2666u16)).take(c.width().unwrap())),
                |g| Right(once(g))
            ))
        ;
        for g in text {
            if x >= hard.end { break; }
            if n >= text_end { break; }
            let w = if g.is_left() { 1 } else { 2 };
            n = n.saturating_add(w);
            let before_text_start = n <= text_start;
            if before_text_start {
                x = min(hard.end, x.saturating_add(w));
                continue;
            }
            if x < hard.start {
                x = min(hard.end, x.saturating_add(w));
                if x > hard.start {
                    debug_assert!(x0.is_none());
                    let invalidated_l = Self::start_text(size.x, line, hard.start);
                    x0 = Some((invalidated_l, hard.start));
                    let col = &mut line[hard.start as u16 as usize];
                    col.Attributes = 0;
                    *unsafe { col.Char.UnicodeChar_mut() } = b' ' as WCHAR;
                }
                continue;
            }
            if x0.is_none() {
                let invalidated_l = Self::start_text(size.x, line, x);
                x0 = Some((invalidated_l, x));
            }
            let next_x = min(hard.end, x.saturating_add(w));
            if next_x - x < w {
                let col = &mut line[x as u16 as usize];
                col.Attributes = 0;
                *unsafe { col.Char.UnicodeChar_mut() } = b' ' as WCHAR;
                x = next_x;
                break;
            }
            match g {
                Left(c) => {
                    let col = &mut line[x as u16 as usize];
                    col.Attributes = attr;
                    *unsafe { col.Char.UnicodeChar_mut() } = c;
                    x += 1;
                },
                Right((l, t)) => {
                    let col = &mut line[x as u16 as usize];
                    col.Attributes = attr | COMMON_LVB_LEADING_BYTE;
                    *unsafe { col.Char.UnicodeChar_mut() } = l;
                    x += 1;
                    let col = &mut line[x as u16 as usize];
                    col.Attributes = attr | COMMON_LVB_TRAILING_BYTE;
                    *unsafe { col.Char.UnicodeChar_mut() } = t;
                    x += 1;
                }
            }
        }
        if let Some((invalidated_l, x0)) = x0 {
            let invalidated_r = Self::end_text(size.x, line, x);
            self.invalidated = self.invalidated
                .union(Rect::from_tl_br(Point { x: invalidated_l, y: p.y }, Point { x: invalidated_r, y: p.y + 1 }))
                .unwrap().right().unwrap()
            ;
            x0 .. x
        } else {
            0 .. 0
        }
    }

    fn update(&mut self, cursor: Option<Point>, wait: bool) -> Result<Option<Event>, Error> {
        self.update_raw(cursor, wait)
    }

    fn line_invalidated_range(&self, line: i16) -> &Range<i16> { &self.data[usize::from(line as u16)] }

    fn line_invalidated_range_mut(&mut self, line: i16) -> &mut Range<i16> { &mut self.data[usize::from(line as u16)] }
}