use hjkl_vim_types::{Mode, Operator, RangeKind};
use hjkl_engine::rope_util::rope_to_lines_vec;
use super::*;
use crate::vim_state::vim_mut;
use hjkl_engine::Editor;
use hjkl_engine::buf_helpers::{buf_cursor_pos, buf_line_chars, buf_set_cursor_rc};
pub(crate) fn text_object_inner_quote_bridge<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
quote: char,
) -> Option<((usize, usize), (usize, usize))> {
quote_text_object(ed, quote, true)
}
pub(crate) fn text_object_around_quote_bridge<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
quote: char,
) -> Option<((usize, usize), (usize, usize))> {
quote_text_object(ed, quote, false)
}
pub(crate) fn text_object_inner_bracket_bridge<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
open: char,
) -> Option<((usize, usize), (usize, usize))> {
bracket_text_object(ed, open, true, 1).map(|(s, e, _kind)| (s, e))
}
pub(crate) fn text_object_around_bracket_bridge<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
open: char,
) -> Option<((usize, usize), (usize, usize))> {
bracket_text_object(ed, open, false, 1).map(|(s, e, _kind)| (s, e))
}
pub(crate) fn text_object_inner_sentence_bridge<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
) -> Option<((usize, usize), (usize, usize))> {
sentence_text_object(ed, true, 1)
}
pub(crate) fn text_object_around_sentence_bridge<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
) -> Option<((usize, usize), (usize, usize))> {
sentence_text_object(ed, false, 1)
}
pub(crate) fn text_object_inner_paragraph_bridge<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
) -> Option<((usize, usize), (usize, usize))> {
paragraph_text_object(ed, true, 1)
}
pub(crate) fn text_object_around_paragraph_bridge<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
) -> Option<((usize, usize), (usize, usize))> {
paragraph_text_object(ed, false, 1)
}
pub(crate) fn text_object_inner_tag_bridge<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
) -> Option<((usize, usize), (usize, usize))> {
tag_text_object(ed, true)
}
pub(crate) fn text_object_around_tag_bridge<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
) -> Option<((usize, usize), (usize, usize))> {
tag_text_object(ed, false)
}
pub(crate) fn greedy_wrap(original: &[String], width: usize) -> Vec<String> {
let mut wrapped: Vec<String> = Vec::new();
let mut paragraph: Vec<String> = Vec::new();
let flush = |para: &mut Vec<String>, out: &mut Vec<String>, width: usize| {
if para.is_empty() {
return;
}
let words = para.join(" ");
let mut current = String::new();
for word in words.split_whitespace() {
let extra = if current.is_empty() {
word.chars().count()
} else {
current.chars().count() + 1 + word.chars().count()
};
if extra > width && !current.is_empty() {
out.push(std::mem::take(&mut current));
current.push_str(word);
} else if current.is_empty() {
current.push_str(word);
} else {
current.push(' ');
current.push_str(word);
}
}
if !current.is_empty() {
out.push(current);
}
para.clear();
};
for line in original {
if line.trim().is_empty() {
flush(&mut paragraph, &mut wrapped, width);
wrapped.push(String::new());
} else {
paragraph.push(line.clone());
}
}
flush(&mut paragraph, &mut wrapped, width);
wrapped
}
pub(crate) fn reflow_rows<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
top: usize,
bot: usize,
) {
let width = ed.settings().textwidth.max(1);
let mut lines: Vec<String> = rope_to_lines_vec(&hjkl_engine::types::Query::rope(ed.buffer()));
let bot = bot.min(lines.len().saturating_sub(1));
if top > bot {
return;
}
let original = lines[top..=bot].to_vec();
let wrapped = greedy_wrap(&original, width);
let last_offset = wrapped
.iter()
.rposition(|l| !l.trim().is_empty())
.unwrap_or(0);
let last_row = top + last_offset;
let after: Vec<String> = lines.split_off(bot + 1);
lines.truncate(top);
lines.extend(wrapped);
lines.extend(after);
ed.restore(lines, (last_row, 0));
move_first_non_whitespace(ed);
ed.mark_content_dirty();
}
pub(crate) fn reflow_rows_keep_cursor<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
top: usize,
bot: usize,
) -> (Vec<String>, Vec<String>) {
let width = ed.settings().textwidth.max(1);
let mut lines: Vec<String> = rope_to_lines_vec(&hjkl_engine::types::Query::rope(ed.buffer()));
let bot = bot.min(lines.len().saturating_sub(1));
if top > bot {
return (Vec::new(), Vec::new());
}
let original = lines[top..=bot].to_vec();
let wrapped = greedy_wrap(&original, width);
let after: Vec<String> = lines.split_off(bot + 1);
lines.truncate(top);
lines.extend(wrapped.clone());
lines.extend(after);
ed.restore(lines, (top, 0));
ed.mark_content_dirty();
(original, wrapped)
}
pub(crate) fn reflow_keep_cursor(
top: usize,
cursor_row: usize,
cursor_col: usize,
before_lines: &[String],
after_lines: &[String],
) -> (usize, usize) {
let relative_row = cursor_row.saturating_sub(top);
let mut char_offset: usize = 0;
for (i, line) in before_lines.iter().enumerate() {
if i == relative_row {
let line_len = line.chars().count();
char_offset += cursor_col.min(line_len);
break;
}
char_offset += line.chars().count() + 1;
}
let mut remaining = char_offset;
for (i, line) in after_lines.iter().enumerate() {
let len = line.chars().count();
if remaining <= len {
let col = remaining.min(if len == 0 { 0 } else { len.saturating_sub(1) });
return (top + i, col);
}
remaining = remaining.saturating_sub(len + 1);
}
let last = after_lines.len().saturating_sub(1);
let last_len = after_lines
.get(last)
.map(|l| l.chars().count())
.unwrap_or(0);
let col = if last_len == 0 { 0 } else { last_len - 1 };
(top + last, col)
}
pub(crate) fn apply_case_op_to_selection<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
op: Operator,
top: (usize, usize),
bot: (usize, usize),
kind: RangeKind,
) {
use hjkl_buffer::Edit;
ed.push_undo();
let saved_yank = ed.yank().to_string();
let saved_yank_linewise = ed.yank_linewise();
let selection = cut_vim_range(ed, top, bot, kind);
let transformed = match op {
Operator::Uppercase => selection.to_uppercase(),
Operator::Lowercase => selection.to_lowercase(),
Operator::ToggleCase => toggle_case_str(&selection),
Operator::Rot13 => rot13_str(&selection),
_ => unreachable!(),
};
if !transformed.is_empty() {
let cursor = buf_cursor_pos(ed.buffer());
ed.mutate_edit(Edit::InsertStr {
at: cursor,
text: transformed,
});
}
buf_set_cursor_rc(ed.buffer_mut(), top.0, top.1);
ed.set_yank(saved_yank);
ed.set_yank_linewise(saved_yank_linewise);
vim_mut(ed).mode = Mode::Normal;
}
pub(crate) fn indent_rows<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
top: usize,
bot: usize,
count: usize,
) {
ed.sync_buffer_content_from_textarea();
let width = ed.settings().shiftwidth.saturating_mul(count.max(1));
let pad: String = if ed.settings().expandtab {
" ".repeat(width)
} else {
let tabstop = ed.settings().tabstop.max(1);
let tabs = width / tabstop;
let spaces = width % tabstop;
format!("{}{}", "\t".repeat(tabs), " ".repeat(spaces))
};
let mut lines: Vec<String> = rope_to_lines_vec(&hjkl_engine::types::Query::rope(ed.buffer()));
let bot = bot.min(lines.len().saturating_sub(1));
for line in lines.iter_mut().take(bot + 1).skip(top) {
if !line.is_empty() {
line.insert_str(0, &pad);
}
}
ed.restore(lines, (top, 0));
move_first_non_whitespace(ed);
}
pub(crate) fn outdent_rows<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
top: usize,
bot: usize,
count: usize,
) {
ed.sync_buffer_content_from_textarea();
let width = ed.settings().shiftwidth.saturating_mul(count.max(1));
let mut lines: Vec<String> = rope_to_lines_vec(&hjkl_engine::types::Query::rope(ed.buffer()));
let bot = bot.min(lines.len().saturating_sub(1));
for line in lines.iter_mut().take(bot + 1).skip(top) {
let strip: usize = line
.chars()
.take(width)
.take_while(|c| *c == ' ' || *c == '\t')
.count();
if strip > 0 {
let byte_len: usize = line.chars().take(strip).map(|c| c.len_utf8()).sum();
line.drain(..byte_len);
}
}
ed.restore(lines, (top, 0));
move_first_non_whitespace(ed);
}
pub(crate) fn bracket_net(line: &str) -> i32 {
let mut net: i32 = 0;
let mut chars = line.chars().peekable();
while let Some(ch) = chars.next() {
match ch {
'/' if chars.peek() == Some(&'/') => return net,
'"' => {
while let Some(c) = chars.next() {
match c {
'\\' => {
chars.next();
} '"' => break,
_ => {}
}
}
}
'\'' => {
let saved: Vec<char> = chars.clone().take(5).collect();
let close_idx = if saved.first() == Some(&'\\') {
saved.iter().skip(2).position(|&c| c == '\'').map(|p| p + 2)
} else {
saved.iter().skip(1).position(|&c| c == '\'').map(|p| p + 1)
};
if let Some(idx) = close_idx {
for _ in 0..=idx {
chars.next();
}
}
}
'{' | '(' | '[' => net += 1,
'}' | ')' | ']' => net -= 1,
_ => {}
}
}
net
}
pub(crate) fn auto_indent_rows<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
top: usize,
bot: usize,
) {
ed.sync_buffer_content_from_textarea();
let shiftwidth = ed.settings().shiftwidth;
let expandtab = ed.settings().expandtab;
let indent_unit: String = if expandtab {
" ".repeat(shiftwidth)
} else {
"\t".to_string()
};
let mut lines: Vec<String> = rope_to_lines_vec(&hjkl_engine::types::Query::rope(ed.buffer()));
let bot = bot.min(lines.len().saturating_sub(1));
let mut depth: i32 = 0;
for line in lines.iter().take(top) {
depth += bracket_net(line);
if depth < 0 {
depth = 0;
}
}
for line in lines.iter_mut().take(bot + 1).skip(top) {
let trimmed_owned = line.trim_start().to_owned();
if trimmed_owned.is_empty() {
*line = String::new();
continue;
}
let starts_with_close = trimmed_owned
.chars()
.next()
.is_some_and(|c| matches!(c, '}' | ')' | ']'));
let starts_with_dot = trimmed_owned.starts_with('.')
&& !trimmed_owned.starts_with("..")
&& !trimmed_owned.starts_with(".;");
let effective_depth = if starts_with_close {
depth.saturating_sub(1)
} else if starts_with_dot {
depth.saturating_add(1)
} else {
depth
} as usize;
let new_line = format!("{}{}", indent_unit.repeat(effective_depth), trimmed_owned);
depth += bracket_net(&trimmed_owned);
if depth < 0 {
depth = 0;
}
*line = new_line;
}
ed.restore(lines, (top, 0));
move_first_non_whitespace(ed);
ed.set_last_indent_range(Some((top, bot)));
}
pub(crate) fn toggle_case_str(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_lowercase() {
c.to_uppercase().next().unwrap_or(c)
} else if c.is_uppercase() {
c.to_lowercase().next().unwrap_or(c)
} else {
c
}
})
.collect()
}
pub(crate) fn order(a: (usize, usize), b: (usize, usize)) -> ((usize, usize), (usize, usize)) {
if a <= b { (a, b) } else { (b, a) }
}
pub(crate) fn clamp_cursor_to_normal_mode<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) {
let (row, col) = ed.cursor();
let line_chars = buf_line_chars(ed.buffer(), row);
let max_col = line_chars.saturating_sub(1);
if col > max_col {
buf_set_cursor_rc(ed.buffer_mut(), row, max_col);
}
}