use super::text_object::{Pos, VisualTextObject, is_wordchar};
use super::vim_buf::{VimBuf, with_vim_buf};
use hjkl_engine::Editor;
pub fn word_text_object<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
inner: bool,
big: bool,
count: usize,
) -> Option<(Pos, Pos)> {
with_vim_buf(ed, |buf, cursor| {
let words = Words { buf, big };
let (found, ok) = words.current_word(cursor, None, !inner, count.max(1));
ok.then(|| (found.start, words.exclusive_end(&found)))
})
.flatten()
}
pub fn word_object_stop<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
inner: bool,
big: bool,
count: usize,
) -> Option<Pos> {
with_vim_buf(ed, |buf, cursor| {
let words = Words { buf, big };
let (found, ok) = words.current_word(cursor, None, !inner, count.max(1));
(!ok).then_some(found.cursor)
})
.flatten()
}
pub fn word_visual<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
anchor: Pos,
inner: bool,
big: bool,
count: usize,
) -> Option<VisualTextObject> {
with_vim_buf(ed, |buf, cursor| {
let words = Words { buf, big };
let (found, _) = words.current_word(cursor, Some(anchor), !inner, count.max(1));
if anchor == cursor {
VisualTextObject::Select(found.start, found.cursor)
} else {
VisualTextObject::Extend(found.cursor)
}
})
}
struct Found {
start: Pos,
cursor: Pos,
inclusive: bool,
}
struct Words<'b, 'a> {
buf: &'b VimBuf<'a>,
big: bool,
}
impl Words<'_, '_> {
fn cls(&self, p: Pos) -> u8 {
match self.buf.gchar(p) {
None | Some(' ' | '\t') => 0,
Some(c) if !c.is_ascii() && c.is_whitespace() => 0,
Some(_) if self.big => 1,
Some(c) if is_wordchar(c) => 2,
Some(_) => 1,
}
}
fn line_empty(&self, row: usize) -> bool {
self.buf.len(row) == 0
}
fn oneleft(p: &mut Pos) -> bool {
if p.1 == 0 {
return false;
}
p.1 -= 1;
true
}
fn back_in_line(&self, p: &mut Pos) {
let sclass = self.cls(*p);
while p.1 > 0 {
self.buf.dec(p);
if self.cls(*p) != sclass {
self.buf.inc(p);
break;
}
}
}
fn skip_chars(&self, p: &mut Pos, cclass: u8, forward: bool) -> bool {
while self.cls(*p) == cclass {
let r = if forward {
self.buf.inc(p)
} else {
self.buf.dec(p)
};
if r == -1 {
return true;
}
}
false
}
fn fwd_word(&self, p: &mut Pos, mut count: usize, eol: bool) -> bool {
while count > 0 {
count -= 1;
let sclass = self.cls(*p);
let last_line = p.0 + 1 == self.buf.n_lines;
let i = self.buf.inc(p);
if i == -1 || (i >= 1 && last_line) {
return false;
}
if i >= 1 && eol && count == 0 {
return true;
}
if sclass != 0 {
while self.cls(*p) == sclass {
let i = self.buf.inc(p);
if i == -1 || (i >= 1 && eol && count == 0) {
return true;
}
}
}
while self.cls(*p) == 0 {
if p.1 == 0 && self.line_empty(p.0) {
break;
}
let i = self.buf.inc(p);
if i == -1 || (i >= 1 && eol && count == 0) {
return true;
}
}
}
true
}
fn bck_word(&self, p: &mut Pos, mut count: usize, mut stop: bool) -> bool {
while count > 0 {
count -= 1;
let sclass = self.cls(*p);
if self.buf.dec(p) == -1 {
return false;
}
'finished: {
if !stop || sclass == self.cls(*p) || sclass == 0 {
while self.cls(*p) == 0 {
if p.1 == 0 && self.line_empty(p.0) {
break 'finished;
}
if self.buf.dec(p) == -1 {
return true;
}
}
if self.skip_chars(p, self.cls(*p), false) {
return true;
}
}
self.buf.inc(p);
}
stop = false;
}
true
}
fn end_word(&self, p: &mut Pos, mut count: usize, mut stop: bool, empty: bool) -> bool {
while count > 0 {
count -= 1;
let sclass = self.cls(*p);
if self.buf.inc(p) == -1 {
return false;
}
'finished: {
if self.cls(*p) == sclass && sclass != 0 {
if self.skip_chars(p, sclass, true) {
return false;
}
} else if !stop || sclass == 0 {
while self.cls(*p) == 0 {
if p.1 == 0 && self.line_empty(p.0) && empty {
break 'finished;
}
if self.buf.inc(p) == -1 {
return false;
}
}
if self.skip_chars(p, self.cls(*p), true) {
return false;
}
}
self.buf.dec(p);
}
stop = false;
}
true
}
fn bckend_word(&self, p: &mut Pos, mut count: usize, eol: bool) -> bool {
while count > 0 {
count -= 1;
let sclass = self.cls(*p);
let i = self.buf.dec(p);
if i == -1 {
return false;
}
if eol && i == 1 {
return true;
}
if sclass != 0 {
while self.cls(*p) == sclass {
let i = self.buf.dec(p);
if i == -1 || (eol && i == 1) {
return true;
}
}
}
while self.cls(*p) == 0 {
if p.1 == 0 && self.line_empty(p.0) {
break;
}
let i = self.buf.dec(p);
if i == -1 || (eol && i == 1) {
return true;
}
}
}
true
}
fn current_word(
&self,
cursor: Pos,
visual: Option<Pos>,
include: bool,
mut count: usize,
) -> (Found, bool) {
let mut found = Found {
start: visual.unwrap_or(cursor),
cursor,
inclusive: true,
};
let ok = self.walk_words(&mut found, visual.is_some(), include, &mut count);
(found, ok)
}
fn walk_words(
&self,
found: &mut Found,
visual: bool,
include: bool,
count: &mut usize,
) -> bool {
let Found {
start,
cursor: cur,
inclusive,
} = found;
let mut start_pos = *cur;
let mut include_white = false;
if !visual || *start == *cur {
self.back_in_line(cur);
start_pos = *cur;
if (self.cls(*cur) == 0) == include {
if !self.end_word(cur, 1, true, true) {
return false;
}
} else {
self.fwd_word(cur, 1, true);
if cur.1 == 0 {
self.buf.decl(cur);
} else {
Self::oneleft(cur);
}
include_white = include;
}
*start = start_pos;
*count -= 1;
}
while *count > 0 {
*inclusive = true;
if visual && *cur < *start {
if self.buf.decl(cur) == -1 {
return false;
}
if include != (self.cls(*cur) != 0) {
if !self.bck_word(cur, 1, true) {
return false;
}
} else {
if !self.bckend_word(cur, 1, true) {
return false;
}
self.buf.incl(cur);
}
} else {
if self.buf.incl(cur) == -1 {
return false;
}
if include != (self.cls(*cur) == 0) {
if !self.fwd_word(cur, 1, true) && *count > 1 {
return false;
}
if !Self::oneleft(cur) {
*inclusive = false;
}
} else if !self.end_word(cur, 1, true, true) {
return false;
}
}
*count -= 1;
}
if include_white && (self.cls(*cur) != 0 || (cur.1 == 0 && !*inclusive)) {
let mut p = start_pos;
if Self::oneleft(&mut p) {
self.back_in_line(&mut p);
if self.cls(p) == 0 && p.1 > 0 {
*start = p;
}
}
}
true
}
fn exclusive_end(&self, found: &Found) -> Pos {
let (row, col) = found.cursor;
if found.inclusive && col < self.buf.len(row) {
(row, col + 1)
} else {
(row, col)
}
}
}