use crate::core::{TextCore, TextStore};
use crate::{Cursor, TextError, TextPosition, TextRange};
#[allow(clippy::needless_bool)]
pub fn insert_quotes<Store: TextStore + Default>(
core: &mut TextCore<Store>,
mut sel: TextRange,
c: char,
) -> Result<bool, TextError> {
core.begin_undo_seq();
if sel.end.x > 0 {
let first = TextRange::new(sel.start, (sel.start.x + 1, sel.start.y));
let last = TextRange::new((sel.end.x - 1, sel.end.y), sel.end);
let c0 = core.str_slice(first).expect("valid_slice");
let c1 = core.str_slice(last).expect("valid_slice");
let remove_quote = if c == '\'' || c == '`' || c == '"' {
if c0 == "'" && c1 == "'" {
true
} else if c0 == "\"" && c1 == "\"" {
true
} else if c0 == "`" && c1 == "`" {
true
} else {
false
}
} else {
if c0 == "<" && c1 == ">" {
true
} else if c0 == "(" && c1 == ")" {
true
} else if c0 == "[" && c1 == "]" {
true
} else if c0 == "{" && c1 == "}" {
true
} else {
false
}
};
if remove_quote {
core.remove_char_range(last)?;
core.remove_char_range(first)?;
if sel.start.y == sel.end.y {
sel = TextRange::new(sel.start, TextPosition::new(sel.end.x - 2, sel.end.y));
} else {
sel = TextRange::new(sel.start, TextPosition::new(sel.end.x - 1, sel.end.y));
}
}
}
let cc = match c {
'\'' => '\'',
'`' => '`',
'"' => '"',
'<' => '>',
'(' => ')',
'[' => ']',
'{' => '}',
_ => unreachable!("invalid quotes"),
};
core.insert_char(sel.end, cc)?;
core.insert_char(sel.start, c)?;
if sel.start.y == sel.end.y {
sel = TextRange::new(sel.start, TextPosition::new(sel.end.x + 2, sel.end.y));
} else {
sel = TextRange::new(sel.start, TextPosition::new(sel.end.x + 1, sel.end.y));
}
core.set_selection(sel.start, sel.end);
core.end_undo_seq();
Ok(true)
}
pub fn insert_tab<Store: TextStore + Default>(
core: &mut TextCore<Store>,
mut pos: TextPosition,
expand_tabs: bool,
tab_width: u32,
) -> Result<bool, TextError> {
if expand_tabs {
let n = tab_width - (pos.x % tab_width);
for _ in 0..n {
core.insert_char(pos, ' ')?;
pos.x += 1;
}
} else {
core.insert_char(pos, '\t')?;
}
Ok(true)
}
pub fn remove_prev_char<Store: TextStore + Default>(
core: &mut TextCore<Store>,
pos: TextPosition,
) -> Result<bool, TextError> {
let (sx, sy) = if pos.y == 0 && pos.x == 0 {
(0, 0)
} else if pos.y > 0 && pos.x == 0 {
let prev_line_width = core.line_width(pos.y - 1).expect("line_width");
(prev_line_width, pos.y - 1)
} else {
(pos.x - 1, pos.y)
};
let range = TextRange::new((sx, sy), (pos.x, pos.y));
core.remove_char_range(range)
}
pub fn remove_next_char<Store: TextStore + Default>(
core: &mut TextCore<Store>,
pos: TextPosition,
) -> Result<bool, TextError> {
let c_line_width = core.line_width(pos.y)?;
let c_last_line = core.len_lines() - 1;
let (ex, ey) = if pos.y == c_last_line && pos.x == c_line_width {
(pos.x, pos.y)
} else if pos.y != c_last_line && pos.x == c_line_width {
(0, pos.y + 1)
} else {
(pos.x + 1, pos.y)
};
let range = TextRange::new((pos.x, pos.y), (ex, ey));
core.remove_char_range(range)
}
pub fn next_word_start<Store: TextStore + Default>(
core: &TextCore<Store>,
pos: TextPosition,
) -> Result<TextPosition, TextError> {
let mut it = core.text_graphemes(pos)?;
let mut last_pos = it.text_offset();
loop {
let Some(c) = it.next() else {
break;
};
last_pos = c.text_bytes().start;
if !c.is_whitespace() {
break;
}
}
Ok(core.byte_pos(last_pos).expect("valid_pos"))
}
pub fn next_word_end<Store: TextStore + Default>(
core: &TextCore<Store>,
pos: TextPosition,
) -> Result<TextPosition, TextError> {
let mut it = core.text_graphemes(pos)?;
let mut last_pos = it.text_offset();
let mut init = true;
loop {
let Some(c) = it.next() else {
break;
};
last_pos = c.text_bytes().start;
if init {
if !c.is_whitespace() {
init = false;
}
} else {
if c.is_whitespace() {
break;
}
}
last_pos = c.text_bytes().end;
}
Ok(core.byte_pos(last_pos).expect("valid_pos"))
}
pub fn prev_word_start<Store: TextStore + Default>(
core: &TextCore<Store>,
pos: TextPosition,
) -> Result<TextPosition, TextError> {
let mut it = core.text_graphemes(pos)?;
let mut last_pos = it.text_offset();
let mut init = true;
loop {
let Some(c) = it.prev() else {
break;
};
if init {
if !c.is_whitespace() {
init = false;
}
} else {
if c.is_whitespace() {
break;
}
}
last_pos = c.text_bytes().start;
}
Ok(core.byte_pos(last_pos).expect("valid_pos"))
}
pub fn prev_word_end<Store: TextStore + Default>(
core: &TextCore<Store>,
pos: TextPosition,
) -> Result<TextPosition, TextError> {
let mut it = core.text_graphemes(pos)?;
let mut last_pos = it.text_offset();
loop {
let Some(c) = it.prev() else {
break;
};
if !c.is_whitespace() {
break;
}
last_pos = c.text_bytes().start;
}
Ok(core.byte_pos(last_pos).expect("valid_pos"))
}
pub fn is_word_boundary<Store: TextStore + Default>(
core: &TextCore<Store>,
pos: TextPosition,
) -> Result<bool, TextError> {
let mut it = core.text_graphemes(pos)?;
if let Some(c0) = it.prev() {
it.next();
if let Some(c1) = it.next() {
Ok(c0.is_whitespace() && !c1.is_whitespace()
|| !c0.is_whitespace() && c1.is_whitespace())
} else {
Ok(false)
}
} else {
Ok(false)
}
}
pub fn word_start<Store: TextStore + Default>(
core: &TextCore<Store>,
pos: TextPosition,
) -> Result<TextPosition, TextError> {
let mut it = core.text_graphemes(pos)?;
let mut last_pos = it.text_offset();
loop {
let Some(c) = it.prev() else {
break;
};
if c.is_whitespace() {
break;
}
last_pos = c.text_bytes().start;
}
Ok(core.byte_pos(last_pos).expect("valid_pos"))
}
pub fn word_end<Store: TextStore + Default>(
core: &TextCore<Store>,
pos: TextPosition,
) -> Result<TextPosition, TextError> {
let mut it = core.text_graphemes(pos)?;
let mut last_pos = it.text_offset();
loop {
let Some(c) = it.next() else {
break;
};
last_pos = c.text_bytes().start;
if c.is_whitespace() {
break;
}
last_pos = c.text_bytes().end;
}
Ok(core.byte_pos(last_pos).expect("valid_pos"))
}