use super::*;
impl TextArea {
pub(super) fn insert_str(&mut self, s: &str) {
let mut st = self.edit.borrow_mut();
let (lo, hi) = (st.cursor.min(st.anchor), st.cursor.max(st.anchor));
let lo = lo.min(st.text.len());
let hi = hi.min(st.text.len());
st.text.replace_range(lo..hi, s);
st.cursor = lo + s.len();
st.anchor = st.cursor;
st.note_text_change();
drop(st);
self.mark_dirty();
self.notify_change();
}
pub(super) fn delete(&mut self, dir: i32) {
let mut st = self.edit.borrow_mut();
let (lo, hi) = (st.cursor.min(st.anchor), st.cursor.max(st.anchor));
if lo != hi {
st.text.replace_range(lo..hi, "");
st.cursor = lo;
st.anchor = lo;
} else if dir < 0 && st.cursor > 0 {
let cur = st.cursor;
let prev = prev_char_boundary(&st.text, cur);
st.text.replace_range(prev..cur, "");
st.cursor = prev;
st.anchor = prev;
} else if dir > 0 && st.cursor < st.text.len() {
let cur = st.cursor;
let next = next_char_boundary(&st.text, cur);
st.text.replace_range(cur..next, "");
}
st.note_text_change();
drop(st);
self.mark_dirty();
self.notify_change();
}
pub(super) fn move_word(&mut self, dir: i32, extend_selection: bool) {
let st = self.edit.borrow();
let p = if dir < 0 {
prev_word_boundary(&st.text, st.cursor)
} else {
next_word_boundary(&st.text, st.cursor)
};
drop(st);
self.move_cursor_to(p, extend_selection);
}
pub(super) fn delete_word(&mut self, dir: i32) {
let mut st = self.edit.borrow_mut();
let (lo, hi) = (st.cursor.min(st.anchor), st.cursor.max(st.anchor));
if lo != hi {
st.text.replace_range(lo..hi, "");
st.cursor = lo;
st.anchor = lo;
} else if dir < 0 && st.cursor > 0 {
let cur = st.cursor;
let prev = prev_word_boundary(&st.text, cur);
st.text.replace_range(prev..cur, "");
st.cursor = prev;
st.anchor = prev;
} else if dir > 0 && st.cursor < st.text.len() {
let cur = st.cursor;
let next = next_word_boundary(&st.text, cur);
st.text.replace_range(cur..next, "");
}
st.note_text_change();
drop(st);
self.mark_dirty();
self.notify_change();
}
pub(super) fn indent_selection(&mut self) {
let mut st = self.edit.borrow_mut();
let (lo, hi) = (st.cursor.min(st.anchor), st.cursor.max(st.anchor));
let starts = touched_line_starts(&st.text, lo, hi);
for &s in starts.iter().rev() {
st.text.insert_str(s, INDENT);
}
let shift = |p: usize| -> usize {
p + INDENT.len() * starts.iter().filter(|&&s| s < p).count()
};
st.cursor = shift(st.cursor);
st.anchor = shift(st.anchor);
st.note_text_change();
drop(st);
self.mark_dirty();
self.notify_change();
}
pub(super) fn outdent_selection(&mut self) {
let mut st = self.edit.borrow_mut();
let (lo, hi) = (st.cursor.min(st.anchor), st.cursor.max(st.anchor));
let starts = touched_line_starts(&st.text, lo, hi);
let removals: Vec<(usize, usize)> = starts
.iter()
.map(|&s| {
let line_end = st.text[s..].find('\n').map(|i| s + i).unwrap_or(st.text.len());
(s, leading_outdent_len(&st.text[s..line_end]))
})
.collect();
for &(s, r) in removals.iter().rev() {
if r > 0 {
st.text.replace_range(s..s + r, "");
}
}
let adjust = |p: usize| -> usize {
let mut removed_before = 0usize;
for &(s, r) in &removals {
if r == 0 {
continue;
}
if p >= s + r {
removed_before += r;
} else if p > s {
removed_before += p - s;
}
}
p - removed_before
};
st.cursor = adjust(st.cursor);
st.anchor = adjust(st.anchor);
st.note_text_change();
drop(st);
self.mark_dirty();
self.notify_change();
}
pub(super) fn begin_pointer_selection(&mut self, off: usize, clicks: u32, shift: bool) {
if shift {
self.select_granularity = SelectGranularity::Char;
self.select_pivot = (off, off);
self.move_cursor_to(off, true);
return;
}
let text = self.edit.borrow().text.clone();
match clicks {
n if n >= 3 => {
self.select_granularity = SelectGranularity::Line;
let (start, end) = paragraph_range_at(&text, off);
self.select_pivot = (start, end);
self.set_selection(start, end);
}
2 => {
self.select_granularity = SelectGranularity::Word;
let (start, end) = word_range_at(&text, off);
self.select_pivot = (start, end);
self.set_selection(start, end);
}
_ => {
self.select_granularity = SelectGranularity::Char;
self.select_pivot = (off, off);
self.move_cursor_to(off, false);
}
}
}
pub(super) fn extend_selection_drag(&mut self, off: usize) {
match self.select_granularity {
SelectGranularity::Char => self.move_cursor_to(off, true),
SelectGranularity::Word => {
let text = self.edit.borrow().text.clone();
let (pivot_start, pivot_end) = self.select_pivot;
let (cs, ce) = word_range_at(&text, off);
if off >= pivot_end {
self.set_caret_anchor(ce, pivot_start);
} else {
self.set_caret_anchor(cs, pivot_end);
}
}
SelectGranularity::Line => {
let text = self.edit.borrow().text.clone();
let (pivot_start, pivot_end) = self.select_pivot;
let (cs, ce) = paragraph_range_at(&text, off);
if off >= pivot_end {
self.set_caret_anchor(ce, pivot_start);
} else {
self.set_caret_anchor(cs, pivot_end);
}
}
}
}
pub(super) fn set_selection(&mut self, start: usize, end: usize) {
let mut st = self.edit.borrow_mut();
let len = st.text.len();
st.anchor = start.min(len);
st.cursor = end.min(len);
}
fn set_caret_anchor(&mut self, cursor: usize, anchor: usize) {
let mut st = self.edit.borrow_mut();
let len = st.text.len();
st.cursor = cursor.min(len);
st.anchor = anchor.min(len);
}
pub(super) fn move_cursor_to(&mut self, pos: usize, with_selection: bool) {
let mut st = self.edit.borrow_mut();
let p = pos.min(st.text.len());
st.cursor = p;
if !with_selection {
st.anchor = p;
}
}
pub(super) fn move_char(&mut self, dir: i32, with_selection: bool) {
let st = self.edit.borrow();
let p = if dir < 0 {
prev_char_boundary(&st.text, st.cursor)
} else {
next_char_boundary(&st.text, st.cursor)
};
drop(st);
self.move_cursor_to(p, with_selection);
}
pub(super) fn move_line(&mut self, dir: i32, with_selection: bool) {
self.move_lines(dir as isize, with_selection);
}
pub(super) fn move_lines(&mut self, delta: isize, with_selection: bool) {
if self.cached_lines.is_empty() {
return;
}
let cursor = self.edit.borrow().cursor;
let cur_line = self.line_for_cursor(cursor) as isize;
let last = self.cached_lines.len() as isize - 1;
let target_line = (cur_line + delta).clamp(0, last);
if target_line == cur_line {
return;
}
let cur_line = cur_line as usize;
let target_line = target_line as usize;
let cur_line_x = self
.cached_lines
.get(cur_line)
.map(|l| self.line_x_start(l))
.unwrap_or(self.padding);
let cur_x = self.pos_for_cursor(cursor).x - cur_line_x;
let line = &self.cached_lines[target_line];
let txt = &line.text;
let mut best_byte = 0usize;
let mut best_delta = f64::INFINITY;
let mut acc = 0.0_f64;
let mut prev_byte = 0usize;
for (i, _) in txt.char_indices().chain(std::iter::once((txt.len(), ' '))) {
let w = if i > prev_byte {
measure_advance(&self.font, &txt[prev_byte..i], self.font_size)
} else {
0.0
};
acc += w;
let d = (acc - cur_x).abs();
if d < best_delta {
best_delta = d;
best_byte = i;
}
prev_byte = i;
}
let target = line.start + best_byte;
self.move_cursor_to(target, with_selection);
}
}
const INDENT: &str = " ";
const TAB_WIDTH: usize = 4;
fn touched_line_starts(text: &str, lo: usize, hi: usize) -> Vec<usize> {
let first = text[..lo].rfind('\n').map(|i| i + 1).unwrap_or(0);
let end_pos = if hi > lo { hi - 1 } else { lo };
let mut starts = Vec::new();
let mut s = first;
loop {
starts.push(s);
match text[s..].find('\n') {
Some(off) => {
let next = s + off + 1;
if next > end_pos {
break;
}
s = next;
}
None => break,
}
}
starts
}
fn leading_outdent_len(line: &str) -> usize {
if line.starts_with('\t') {
1
} else {
line.chars().take_while(|&c| c == ' ').take(TAB_WIDTH).count()
}
}