tui-canvas 0.7.5

Form/textarea for TUI
Documentation
#[cfg(feature = "keybindings")]
use crate::{
    canvas::modes::AppMode,
    editor::behavior::YankRegister,
    textarea::{TextAreaDataProvider, TextAreaState},
};

#[cfg(feature = "keybindings")]
impl<P: TextAreaDataProvider> TextAreaState<P> {
    pub(crate) fn paste_after_vim(&mut self, count: usize) {
        self.paste_yank_vim(true, count);
    }

    pub(crate) fn paste_before_vim(&mut self, count: usize) {
        self.paste_yank_vim(false, count);
    }

    pub(crate) fn paste_after_helix(&mut self, count: usize) {
        self.paste_yank_helix(true, count);
    }

    pub(crate) fn paste_before_helix(&mut self, count: usize) {
        self.paste_yank_helix(false, count);
    }

    pub(crate) fn paste_after_emacs(&mut self, count: usize) {
        self.paste_yank_emacs(true, count);
    }

    pub(crate) fn paste_before_emacs(&mut self, count: usize) {
        self.paste_yank_emacs(false, count);
    }

    fn paste_yank_vim(&mut self, after: bool, count: usize) {
        let Some(register) = self.editor.behavior_state.yank().register().cloned() else {
            return;
        };

        match register {
            YankRegister::Lines(lines) => self.paste_lines_vim(after, count, lines),
            YankRegister::Text(lines) => {
                let text = repeated_text(&lines, count);
                if text.is_empty() {
                    return;
                }
                let field = self.current_field();
                let line_len = self.current_text().chars().count();
                let col = if after {
                    self.cursor_position().saturating_add(1).min(line_len)
                } else {
                    self.cursor_position()
                };
                let (target_field, target_col) = self.insert_text_at(field, col, &text);
                let _ = self.transition_to_field(target_field);
                self.set_cursor_position(target_col);
                self.set_mode(AppMode::Nor);
                #[cfg(feature = "gui")]
                {
                    self.edited_this_frame = true;
                }
            }
        }
    }

    fn paste_yank_helix(&mut self, after: bool, count: usize) {
        let Some(register) = self.editor.behavior_state.yank().register().cloned() else {
            return;
        };

        match register {
            YankRegister::Lines(lines) => self.paste_lines_helix(after, count, lines),
            YankRegister::Text(lines) => {
                let text = repeated_text(&lines, count);
                if text.is_empty() {
                    return;
                }
                let (start, end) = self.selection_endpoints();
                let (field, col) = if after {
                    (end.0, end.1.saturating_add(1))
                } else {
                    start
                };
                let (target_field, target_col) = self.insert_text_at(field, col, &text);
                let _ = self.transition_to_field(target_field);
                self.set_cursor_position(target_col);
                self.ui_state.current_mode = AppMode::Nor;
                self.ensure_helix_primary_selection();
                #[cfg(feature = "gui")]
                {
                    self.edited_this_frame = true;
                }
            }
        }
    }

    fn paste_yank_emacs(&mut self, after: bool, count: usize) {
        let Some(register) = self.editor.behavior_state.yank().register().cloned() else {
            return;
        };

        match register {
            YankRegister::Lines(lines) => self.paste_lines_emacs(after, count, lines),
            YankRegister::Text(lines) => {
                let text = repeated_text(&lines, count);
                if text.is_empty() {
                    return;
                }
                if self.mode() == AppMode::Sel {
                    self.exit_highlight_mode_emacs();
                }
                let (target_field, target_col) =
                    self.insert_text_at(self.current_field(), self.cursor_position(), &text);
                let _ = self.transition_to_field(target_field);
                self.set_cursor_position(target_col);
                #[cfg(feature = "gui")]
                {
                    self.edited_this_frame = true;
                }
            }
        }
    }

    fn paste_lines_vim(&mut self, after: bool, count: usize, lines: Vec<String>) {
        if lines.is_empty() {
            return;
        }

        self.editor
            .record_checkpoint(crate::editor::features::history::EditKind::Other);

        let mut content = self.editor.data_provider().capture_content();
        let current = self.current_field().min(content.len().saturating_sub(1));
        let insert_at = if after {
            current.saturating_add(1).min(content.len())
        } else {
            current
        };

        let repeat = count.max(1);
        let mut insert = Vec::with_capacity(lines.len() * repeat);
        for _ in 0..repeat {
            insert.extend(lines.iter().cloned());
        }

        content.splice(insert_at..insert_at, insert);
        self.editor.data_provider_mut().restore_content(&content);
        let _ = self.transition_to_field(insert_at.min(content.len().saturating_sub(1)));
        self.move_line_start();
        self.set_mode(AppMode::Nor);
        #[cfg(feature = "gui")]
        {
            self.edited_this_frame = true;
        }
    }

    fn paste_lines_helix(&mut self, after: bool, count: usize, lines: Vec<String>) {
        if lines.is_empty() {
            return;
        }

        self.editor
            .record_checkpoint(crate::editor::features::history::EditKind::Other);

        let mut content = self.editor.data_provider().capture_content();
        let current = self.current_field().min(content.len().saturating_sub(1));
        let insert_at = if after {
            current.saturating_add(1).min(content.len())
        } else {
            current
        };

        let repeat = count.max(1);
        let mut insert = Vec::with_capacity(lines.len() * repeat);
        for _ in 0..repeat {
            insert.extend(lines.iter().cloned());
        }

        content.splice(insert_at..insert_at, insert);
        self.editor.data_provider_mut().restore_content(&content);
        let _ = self.transition_to_field(insert_at.min(content.len().saturating_sub(1)));
        self.move_line_start();
        self.ui_state.current_mode = AppMode::Nor;
        self.ensure_helix_primary_selection();
        #[cfg(feature = "gui")]
        {
            self.edited_this_frame = true;
        }
    }

    fn paste_lines_emacs(&mut self, after: bool, count: usize, lines: Vec<String>) {
        if lines.is_empty() {
            return;
        }

        self.editor
            .record_checkpoint(crate::editor::features::history::EditKind::Other);

        if self.mode() == AppMode::Sel {
            self.exit_highlight_mode_emacs();
        }

        let mut content = self.editor.data_provider().capture_content();
        let current = self.current_field().min(content.len().saturating_sub(1));
        let insert_at = if after {
            current.saturating_add(1).min(content.len())
        } else {
            current
        };

        let repeat = count.max(1);
        let mut insert = Vec::with_capacity(lines.len() * repeat);
        for _ in 0..repeat {
            insert.extend(lines.iter().cloned());
        }

        content.splice(insert_at..insert_at, insert);
        self.editor.data_provider_mut().restore_content(&content);
        let _ = self.transition_to_field(insert_at.min(content.len().saturating_sub(1)));
        self.move_line_start();
        self.set_mode(AppMode::Nor);
        #[cfg(feature = "gui")]
        {
            self.edited_this_frame = true;
        }
    }

    fn insert_text_at(&mut self, field: usize, col: usize, text: &str) -> (usize, usize) {
        self.editor
            .record_checkpoint(crate::editor::features::history::EditKind::Other);

        let mut content = self.editor.data_provider().capture_content();
        if content.is_empty() {
            content.push(String::new());
        }
        let field = field.min(content.len().saturating_sub(1));
        let line = &content[field];
        let col = col.min(line.chars().count());
        let prefix: String = line.chars().take(col).collect();
        let suffix: String = line.chars().skip(col).collect();
        let parts: Vec<&str> = text.split('\n').collect();

        let target = if parts.len() == 1 {
            content[field] = format!("{prefix}{}{suffix}", parts[0]);
            (field, col.saturating_add(parts[0].chars().count()))
        } else {
            let mut replacement = Vec::with_capacity(parts.len());
            replacement.push(format!("{prefix}{}", parts[0]));
            for part in &parts[1..parts.len() - 1] {
                replacement.push((*part).to_string());
            }
            let last = parts[parts.len() - 1];
            replacement.push(format!("{last}{suffix}"));
            content.splice(field..=field, replacement);
            (field.saturating_add(parts.len() - 1), last.chars().count())
        };

        self.editor.data_provider_mut().restore_content(&content);
        target
    }
}

fn repeated_text(lines: &[String], count: usize) -> String {
    let repeat = count.max(1);
    let text = lines.join("\n");
    let mut pasted = String::new();
    for i in 0..repeat {
        if i > 0 {
            pasted.push('\n');
        }
        pasted.push_str(&text);
    }
    pasted
}