dbcrab 0.4.0

Modern REPL-first PostgreSQL client.
use ratatui::{style::Style, text::Line};
use ratatui_textarea::{DataCursor, TextArea as EditTextArea, WrapMode};

#[derive(Debug, Clone)]
pub(super) struct PreviewEdit {
    pub(super) row: usize,
    pub(super) column: usize,
    pub(super) textarea: EditTextArea<'static>,
    pub(super) text: String,
}

impl PreviewEdit {
    pub(super) fn new(row: usize, column: usize, text: String) -> Self {
        let mut textarea = EditTextArea::new(text.split('\n').map(ToOwned::to_owned).collect());
        textarea.set_wrap_mode(WrapMode::Glyph);
        textarea.set_cursor_line_style(Style::default());

        Self {
            row,
            column,
            textarea,
            text,
        }
    }

    pub(super) fn sync_text(&mut self) {
        self.text = self.textarea.lines().join("\n");
    }

    pub(super) fn cursor_visual_position(&self, width: u16) -> (usize, usize) {
        textarea_cursor_visual_position(&self.textarea, usize::from(width).max(1))
    }
}

fn textarea_cursor_visual_position(textarea: &EditTextArea<'_>, width: usize) -> (usize, usize) {
    let DataCursor(cursor_row, cursor_col) = textarea.cursor();
    let lines = textarea.lines();
    let visual_y = lines
        .iter()
        .take(cursor_row)
        .map(|line| visual_line_count(line, width))
        .sum::<usize>();
    let line = lines.get(cursor_row).map_or("", String::as_str);
    let prefix_width = line_prefix_width(line, cursor_col);

    (prefix_width % width, visual_y + prefix_width / width)
}

fn visual_line_count(line: &str, width: usize) -> usize {
    Line::from(line).width().max(1).div_ceil(width)
}

fn line_prefix_width(line: &str, char_offset: usize) -> usize {
    let byte_offset = line
        .char_indices()
        .nth(char_offset)
        .map_or(line.len(), |(offset, _)| offset);

    Line::from(&line[..byte_offset]).width()
}