use crate::ime::ImeState;
use super::grapheme::insert_text_at;
use super::ops::{delete_selection, record_edit, reset_blink, selection_range};
use super::undo::EditOp;
pub(crate) fn selected_text(state: &ImeState) -> Option<String> {
selection_range(state).map(|(lo, hi)| state.text[lo..hi].to_string())
}
pub(crate) fn clean_paste(raw: &str, multiline: bool) -> String {
if multiline {
raw.replace("\r\n", "\n").replace('\r', "\n")
} else {
raw.chars().filter(|c| *c != '\n' && *c != '\r').collect()
}
}
pub(crate) fn apply_cut(state: &mut ImeState) -> String {
delete_selection(state);
record_edit(state, EditOp::Discrete);
reset_blink(state);
state.text.clone()
}
pub(crate) fn apply_paste(state: &mut ImeState, cleaned: &str) -> String {
state.preedit = None;
delete_selection(state);
let old_caret = state.caret;
let new_caret = insert_text_at(&mut state.text, old_caret, cleaned);
state.caret = new_caret;
record_edit(state, EditOp::Discrete);
reset_blink(state);
state.text.clone()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn paste_single_line_strips_newlines_keeps_other_chars() {
let cleaned = clean_paste("line1\nline2\r\nline3", false);
assert_eq!(cleaned, "line1line2line3");
}
#[test]
fn paste_multiline_keeps_lf_and_normalizes_crlf() {
assert_eq!(clean_paste("a\r\nb", true), "a\nb");
assert_eq!(clean_paste("a\rb", true), "a\nb");
assert_eq!(clean_paste("a\nb", true), "a\nb");
}
#[test]
fn apply_paste_multiline_inserts_newlines_at_caret() {
let mut s = ImeState {
text: "x".to_string(),
caret: 1,
..Default::default()
};
let cleaned = clean_paste("a\r\nb", true);
let out = apply_paste(&mut s, &cleaned);
assert_eq!(out, "xa\nb");
assert_eq!(s.caret, 4, "caret advances past the inserted run");
assert_eq!(s.selection_anchor, None);
}
}