use crate::model::cursor::Cursor;
use crate::state::EditorState;
pub enum AddCursorResult {
Success {
cursor: Cursor,
total_cursors: usize,
},
Failed { message: String },
}
struct CursorLineInfo {
line_start: usize,
col_offset: usize,
}
fn get_cursor_line_info(state: &mut EditorState, position: usize) -> Option<CursorLineInfo> {
let mut iter = state.buffer.line_iterator(position, 80);
let (line_start, _) = iter.next()?;
Some(CursorLineInfo {
line_start,
col_offset: position.saturating_sub(line_start),
})
}
fn cursor_position_on_line(line_start: usize, line_content: &str, target_col: usize) -> usize {
let line_len = line_content.trim_end_matches('\n').len();
line_start + target_col.min(line_len)
}
fn success_result(cursor: Cursor, state: &EditorState) -> AddCursorResult {
AddCursorResult::Success {
cursor,
total_cursors: state.cursors.iter().count() + 1,
}
}
fn adjust_position_for_newline(state: &mut EditorState, position: usize) -> usize {
if position < state.buffer.len() {
if let Ok(byte_at_cursor) = state.buffer.get_text_range_mut(position, 1) {
if byte_at_cursor.first() == Some(&b'\n') {
return position + 1;
}
}
}
position
}
pub fn add_cursor_at_next_match(state: &mut EditorState) -> AddCursorResult {
let primary = state.cursors.primary();
let selection_range = match primary.selection_range() {
Some(range) => range,
None => {
return AddCursorResult::Failed {
message: "No selection to match".to_string(),
}
}
};
let cursor_at_start = primary.position == selection_range.start;
let pattern = state.get_text_range(selection_range.start, selection_range.end);
let search_start = selection_range.end;
let match_pos = match state.buffer.find_next(&pattern, search_start) {
Some(pos) => pos,
None => {
return AddCursorResult::Failed {
message: "No more matches".to_string(),
}
}
};
let match_start = match_pos;
let match_end = match_pos + pattern.len();
let new_cursor = if cursor_at_start {
let mut cursor = Cursor::new(match_start);
cursor.set_anchor(match_end);
cursor
} else {
Cursor::with_selection(match_start, match_end)
};
success_result(new_cursor, state)
}
pub fn add_cursor_above(state: &mut EditorState) -> AddCursorResult {
let position = state.cursors.primary().position;
let adjusted_position = adjust_position_for_newline(state, position);
let Some(info) = get_cursor_line_info(state, adjusted_position) else {
return AddCursorResult::Failed {
message: "Unable to find current line".to_string(),
};
};
if info.line_start == 0 {
return AddCursorResult::Failed {
message: "Already at first line".to_string(),
};
}
let mut iter = state.buffer.line_iterator(adjusted_position, 80);
iter.next(); iter.prev();
if let Some((prev_line_start, prev_line_content)) = iter.prev() {
let new_pos = cursor_position_on_line(prev_line_start, &prev_line_content, info.col_offset);
success_result(Cursor::new(new_pos), state)
} else {
AddCursorResult::Failed {
message: "Already at first line".to_string(),
}
}
}
pub fn add_cursor_below(state: &mut EditorState) -> AddCursorResult {
let position = state.cursors.primary().position;
let Some(info) = get_cursor_line_info(state, position) else {
return AddCursorResult::Failed {
message: "Unable to find current line".to_string(),
};
};
let mut iter = state.buffer.line_iterator(position, 80);
iter.next();
if let Some((next_line_start, next_line_content)) = iter.next() {
let new_pos = cursor_position_on_line(next_line_start, &next_line_content, info.col_offset);
success_result(Cursor::new(new_pos), state)
} else {
AddCursorResult::Failed {
message: "Already at last line".to_string(),
}
}
}