use crate::buf::text::Text;
use crate::prelude::*;
use crate::state::ops::Operation;
use crate::ui::tree::*;
use crate::ui::viewport::CursorViewport;
use crate::ui::viewport::CursorViewportArc;
use crate::ui::viewport::Viewport;
use crate::ui::viewport::ViewportArc;
use compact_str::CompactString;
use std::ops::Range;
#[allow(dead_code)]
fn _normalize_move_by(
op: Operation,
cursor_char_idx: usize,
cursor_line_idx: usize,
) -> (isize, isize) {
match op {
Operation::CursorMoveLeftBy(n) => (-(n as isize), 0),
Operation::CursorMoveRightBy(n) => (n as isize, 0),
Operation::CursorMoveUpBy(n) => (0, -(n as isize)),
Operation::CursorMoveDownBy(n) => (0, n as isize),
Operation::CursorMoveTo((x, y)) => {
let x = (x as isize) - (cursor_char_idx as isize);
let y = (y as isize) - (cursor_line_idx as isize);
(x, y)
}
Operation::CursorMoveBy((x, y)) => (x, y),
_ => unreachable!(),
}
}
fn _normalize_move_to(
op: Operation,
cursor_char_idx: usize,
cursor_line_idx: usize,
) -> (usize, usize) {
match op {
Operation::CursorMoveLeftBy(n) => {
let x = cursor_char_idx.saturating_sub(n);
let y = cursor_line_idx;
(x, y)
}
Operation::CursorMoveRightBy(n) => {
let x = cursor_char_idx.saturating_add(n);
let y = cursor_line_idx;
(x, y)
}
Operation::CursorMoveUpBy(n) => {
let x = cursor_char_idx;
let y = cursor_line_idx.saturating_sub(n);
(x, y)
}
Operation::CursorMoveDownBy(n) => {
let x = cursor_char_idx;
let y = cursor_line_idx.saturating_add(n);
(x, y)
}
Operation::CursorMoveBy((x, y)) => {
let to_x = std::cmp::max(0, (cursor_char_idx as isize) + x) as usize;
let to_y = std::cmp::max(0, (cursor_line_idx as isize) + y) as usize;
(to_x, to_y)
}
Operation::CursorMoveTo((x, y)) => (x, y),
_ => unreachable!(),
}
}
pub fn normalize_cursor_move_to_exclude_eol(
text: &Text,
op: Operation,
cursor_char_idx: usize,
cursor_line_idx: usize,
) -> (usize, usize) {
let (x, y) = _normalize_move_to(op, cursor_char_idx, cursor_line_idx);
let y = std::cmp::min(y, text.rope().len_lines().saturating_sub(1));
let x = match text.last_char_idx_on_line_exclude_eol(y) {
Some(last_char) => std::cmp::min(x, last_char),
None => {
debug_assert!(text.rope().get_line(y).is_some());
std::cmp::min(x, text.rope().line(y).len_chars().saturating_sub(1))
}
};
(x, y)
}
pub fn normalize_cursor_move_to_include_eol(
text: &Text,
op: Operation,
cursor_char_idx: usize,
cursor_line_idx: usize,
) -> (usize, usize) {
let (x, y) = _normalize_move_to(op, cursor_char_idx, cursor_line_idx);
let y = std::cmp::min(y, text.rope().len_lines().saturating_sub(1));
let x = match text.last_char_idx_on_line_exclude_eol(y) {
Some(last_char) => std::cmp::min(x, last_char + 1), None => {
debug_assert!(text.rope().get_line(y).is_some());
std::cmp::min(x, text.rope().line(y).len_chars())
}
};
(x, y)
}
pub fn normalize_window_scroll_by(
op: Operation,
viewport_start_column_idx: usize,
viewport_start_line_idx: usize,
) -> (isize, isize) {
match op {
Operation::WindowScrollLeftBy(n) => (-(n as isize), 0),
Operation::WindowScrollRightBy(n) => (n as isize, 0),
Operation::WindowScrollUpBy(n) => (0, -(n as isize)),
Operation::WindowScrollDownBy(n) => (0, n as isize),
Operation::WindowScrollTo((x, y)) => {
let x = (x as isize) - (viewport_start_column_idx as isize);
let y = (y as isize) - (viewport_start_line_idx as isize);
(x, y)
}
Operation::WindowScrollBy((x, y)) => (x, y),
_ => unreachable!(),
}
}
pub fn normalize_window_scroll_to(
op: Operation,
viewport_start_column_idx: usize,
viewport_start_line_idx: usize,
) -> (usize, usize) {
match op {
Operation::WindowScrollLeftBy(n) => {
let x = viewport_start_column_idx.saturating_add_signed(-(n as isize));
let y = viewport_start_line_idx;
(x, y)
}
Operation::WindowScrollRightBy(n) => {
let x = viewport_start_column_idx.saturating_add_signed(n as isize);
let y = viewport_start_line_idx;
(x, y)
}
Operation::WindowScrollUpBy(n) => {
let x = viewport_start_column_idx;
let y = viewport_start_line_idx.saturating_add_signed(-(n as isize));
(x, y)
}
Operation::WindowScrollDownBy(n) => {
let x = viewport_start_column_idx;
let y = viewport_start_line_idx.saturating_add_signed(n as isize);
(x, y)
}
Operation::WindowScrollTo((x, y)) => (x, y),
Operation::WindowScrollBy((by_x, by_y)) => {
let x = viewport_start_column_idx.saturating_add_signed(by_x);
let y = viewport_start_line_idx.saturating_add_signed(by_y);
(x, y)
}
_ => unreachable!(),
}
}
fn _update_viewport(
tree: &mut Tree,
id: NodeId,
text: &Text,
start_line: usize,
start_column: usize,
) -> ViewportArc {
let new_viewport = Viewport::to_arc(Viewport::view(
tree.editable_options(id),
text,
&tree.editable_actual_shape(id).size(),
start_line,
start_column,
));
tree.set_editable_viewport(id, new_viewport.clone());
new_viewport
}
fn _update_cursor_viewport(
tree: &mut Tree,
id: NodeId,
viewport: &Viewport,
text: &Text,
cursor_line: usize,
cursor_char: usize,
) -> CursorViewportArc {
let new_cursor_viewport =
CursorViewport::to_arc(CursorViewport::from_position(
viewport,
text,
&tree.editable_actual_shape(id).size(),
cursor_line,
cursor_char,
));
tree.set_editable_cursor_viewport(id, new_cursor_viewport.clone());
new_cursor_viewport
}
pub fn raw_cursor_viewport_move_to(
tree: &mut Tree,
id: NodeId,
viewport: &Viewport,
text: &Text,
cursor_move_to_op: Operation,
) -> CursorViewportArc {
debug_assert!(matches!(cursor_move_to_op, Operation::CursorMoveTo((_, _))));
let (char_idx, line_idx) = match cursor_move_to_op {
Operation::CursorMoveTo((c, l)) => (c, l),
_ => unreachable!(),
};
let line_idx =
std::cmp::min(line_idx, viewport.end_line_idx().saturating_sub(1));
debug_assert!(line_idx < viewport.end_line_idx());
debug_assert!(text.rope().get_line(line_idx).is_some());
let bufline = text.rope().line(line_idx);
let char_idx = std::cmp::min(char_idx, bufline.len_chars());
debug_assert!(bufline.len_chars() >= char_idx);
trace!("raw cursor move line/char:{}/{}", line_idx, char_idx);
if cfg!(debug_assertions) {
if bufline.len_chars() == 0 {
debug_assert_eq!(char_idx, 0_usize);
} else {
debug_assert!(bufline.len_chars() >= char_idx);
}
}
_update_cursor_viewport(tree, id, viewport, text, line_idx, char_idx)
}
pub fn raw_viewport_scroll_to(
tree: &mut Tree,
id: NodeId,
viewport: &Viewport,
text: &Text,
window_scroll_to_op: Operation,
) -> Option<ViewportArc> {
debug_assert!(matches!(
window_scroll_to_op,
Operation::WindowScrollTo((_, _))
));
let (column_idx, line_idx) = match window_scroll_to_op {
Operation::WindowScrollTo((c, l)) => (c, l),
_ => unreachable!(),
};
let buffer_len_lines = text.rope().len_lines();
let line_idx = if buffer_len_lines == 0 {
0_usize
} else {
std::cmp::min(line_idx, buffer_len_lines.saturating_sub(1))
};
if buffer_len_lines == 0 {
debug_assert_eq!(line_idx, 0_usize);
} else {
debug_assert!(line_idx < buffer_len_lines);
}
debug_assert!(text.rope().get_line(line_idx).is_some());
if line_idx == viewport.start_line_idx()
&& column_idx == viewport.start_column_idx()
{
return None;
}
let new_viewport = Viewport::to_arc(Viewport::view(
tree.editable_options(id),
text,
&tree.editable_actual_shape(id).size(),
line_idx,
column_idx,
));
tree.set_editable_viewport(id, new_viewport.clone());
Some(new_viewport)
}
fn _update_viewport_after_text_changed(
tree: &mut Tree,
id: NodeId,
text: &Text,
) {
let viewport = tree.editable_viewport(id);
let cursor_viewport = tree.editable_cursor_viewport(id);
let start_line = std::cmp::min(
viewport.start_line_idx(),
text.rope().len_lines().saturating_sub(1),
);
debug_assert!(text.rope().get_line(start_line).is_some());
let start_column = viewport.start_column_idx();
let updated_viewport =
_update_viewport(tree, id, text, start_line, start_column);
raw_cursor_viewport_move_to(
tree,
id,
&updated_viewport,
text,
Operation::CursorMoveTo((
cursor_viewport.char_idx(),
cursor_viewport.line_idx(),
)),
);
}
pub fn cursor_move(
tree: &mut Tree,
id: NodeId,
text: &Text,
op: Operation,
include_eol: bool,
) {
let viewport = tree.editable_viewport(id);
let cursor_viewport = tree.editable_cursor_viewport(id);
trace!(
"cursor_move old viewport:{}/{},cursor_viewport:{:?}",
viewport.start_line_idx(),
viewport.start_column_idx(),
cursor_viewport
);
let (target_cursor_char, target_cursor_line) = if include_eol {
normalize_cursor_move_to_include_eol(
text,
op,
cursor_viewport.char_idx(),
cursor_viewport.line_idx(),
)
} else {
normalize_cursor_move_to_exclude_eol(
text,
op,
cursor_viewport.char_idx(),
cursor_viewport.line_idx(),
)
};
let new_viewport: Option<ViewportArc> = {
let (start_line, start_column) = viewport.search(
&cursor_viewport,
tree.editable_options(id),
text,
&tree.editable_actual_shape(id).size(),
target_cursor_line,
target_cursor_char,
);
trace!("cursor_move new viewport:{}/{}", start_line, start_column);
if start_line != viewport.start_line_idx()
|| start_column != viewport.start_column_idx()
{
raw_viewport_scroll_to(
tree,
id,
&viewport,
text,
Operation::WindowScrollTo((start_column, start_line)),
)
} else {
None
}
};
let new_viewport = new_viewport.unwrap_or(viewport);
let new_cursor_viewport = raw_cursor_viewport_move_to(
tree,
id,
&new_viewport,
text,
Operation::CursorMoveTo((target_cursor_char, target_cursor_line)),
);
trace!("cursor_move new cursor_viewport:{:?}", new_cursor_viewport);
debug_assert!(tree.cursor_id().is_some());
tree
.cursor_move_position_to(
new_cursor_viewport.column_idx() as isize,
new_cursor_viewport.row_idx() as isize,
)
.unwrap();
}
pub fn cursor_insert(
tree: &mut Tree,
id: NodeId,
text: &mut Text,
payload: CompactString,
) -> (usize, usize) {
debug_assert!(tree.node(id).is_some());
let cursor_viewport = tree.editable_cursor_viewport(id);
let cursor_line_idx = cursor_viewport.line_idx();
let cursor_char_idx = cursor_viewport.char_idx();
debug_assert!(text.rope().get_line(cursor_line_idx).is_some());
debug_assert!(
cursor_char_idx <= text.rope().line(cursor_line_idx).len_chars()
);
let (cursor_line_idx_after_inserted, cursor_char_idx_after_inserted) =
text.insert(cursor_line_idx, cursor_char_idx, payload);
_update_viewport_after_text_changed(tree, id, text);
trace!(
"Move to inserted pos, line:{cursor_line_idx_after_inserted}, char:{cursor_char_idx_after_inserted}"
);
let op = Operation::CursorMoveTo((
cursor_char_idx_after_inserted,
cursor_line_idx_after_inserted,
));
cursor_move(tree, id, text, op, true);
(
cursor_line_idx_after_inserted,
cursor_char_idx_after_inserted,
)
}
pub fn cursor_delete(
tree: &mut Tree,
id: NodeId,
text: &mut Text,
n: isize,
) -> Option<(usize, usize)> {
let cursor_viewport = tree.editable_cursor_viewport(id);
let cursor_line_idx = cursor_viewport.line_idx();
let cursor_char_idx = cursor_viewport.char_idx();
debug_assert!(text.rope().get_line(cursor_line_idx).is_some());
let maybe_new_cursor_position =
text.remove(cursor_line_idx, cursor_char_idx, n);
maybe_new_cursor_position?;
_update_viewport_after_text_changed(tree, id, text);
let (cursor_line_idx_after_deleted, cursor_char_idx_after_deleted) =
maybe_new_cursor_position.unwrap();
trace!(
"Move to deleted pos, line:{cursor_line_idx_after_deleted}, char:{cursor_char_idx_after_deleted}"
);
let op = Operation::CursorMoveTo((
cursor_char_idx_after_deleted,
cursor_line_idx_after_deleted,
));
cursor_move(tree, id, text, op, true);
Some((cursor_line_idx_after_deleted, cursor_char_idx_after_deleted))
}
pub fn cursor_jump(tree: &mut Tree, parent_id: NodeId) -> Option<NodeId> {
let cursor_id = tree.cursor_id().unwrap();
let old_parent_id = tree.parent_id(cursor_id).unwrap();
let removed_id = match tree.node(old_parent_id).unwrap() {
Node::WindowContent(_content) => {
debug_assert!(tree.parent_id(old_parent_id).is_some());
debug_assert!(tree.current_window_id().is_some());
let old_window_id = tree.parent_id(old_parent_id).unwrap();
debug_assert_eq!(old_window_id, tree.current_window_id().unwrap());
if old_window_id == parent_id {
return None;
}
old_window_id
}
Node::CmdlineInput(_input) => {
debug_assert!(tree.parent_id(old_parent_id).is_some());
let old_input_panel_id = tree.parent_id(old_parent_id).unwrap();
debug_assert!(tree.parent_id(old_input_panel_id).is_some());
let old_cmdline_id = tree.parent_id(old_input_panel_id).unwrap();
if old_cmdline_id == parent_id {
return None;
}
old_cmdline_id
}
_ => unreachable!(),
};
tree
.context()
.borrow_mut()
.remove_child(old_parent_id, cursor_id)
.unwrap();
match tree.node_mut(parent_id).unwrap() {
Node::Window(window) => {
let content_id = window.content_id();
tree
.context()
.borrow_mut()
.add_child(content_id, cursor_id)
.unwrap();
}
Node::Cmdline(cmdline) => {
let input_id = cmdline.input_id();
tree
.context()
.borrow_mut()
.add_child(input_id, cursor_id)
.unwrap();
}
_ => unreachable!(),
}
Some(removed_id)
}
pub fn cursor_absolute_char_idx(tree: &Tree, id: NodeId, text: &Text) -> usize {
let cursor_viewport = tree.editable_cursor_viewport(id);
let cursor_line_idx = cursor_viewport.line_idx();
let cursor_char_idx = cursor_viewport.char_idx();
let absolute_char_idx =
text.to_absolute_char_idx(cursor_line_idx, cursor_char_idx);
debug_assert_eq!(
text.to_line_idx_and_char_idx(absolute_char_idx).0,
cursor_line_idx
);
debug_assert_eq!(
text.to_line_idx_and_char_idx(absolute_char_idx).1,
cursor_char_idx
);
absolute_char_idx
}
pub fn cursor_absolute_delete_chars_range(
tree: &Tree,
id: NodeId,
text: &Text,
n: isize,
) -> Option<Range<usize>> {
let cursor_viewport = tree.editable_cursor_viewport(id);
let cursor_line_idx = cursor_viewport.line_idx();
let cursor_char_idx = cursor_viewport.char_idx();
text.get_removable_char_idx_range(cursor_line_idx, cursor_char_idx, n)
}