Files
zesdex/src/app/mode/editor.rs
T
asepharyana 2efd40ca88 Enhance tool documentation and add new features
- Added module-level documentation for memory tools (`remember`, `recall`, `forget`) to clarify their purpose.
- Improved documentation in `recall.rs` and `remember.rs` to describe the functionality and flow of memory entry operations.
- Updated `mod.rs` to include descriptions for the tool trait and execution context.
- Enhanced `plan.rs` with detailed comments on plan-mode signaling tools.
- Documented text search tools in `search.rs` to explain their functionality.
- Improved sequential-thinking tool documentation in `seqthink.rs`.
- Added safety filter documentation in `shell_filter` for credential and git operations.
- Enhanced utility tools documentation, including `cd`, `dir_cache_update`, and `todowrite`.
- Improved rendering documentation in view modules (`chat`, `markdown`, `status`, `workflow`) to clarify rendering flows and purposes.
2026-07-12 11:28:39 +07:00

146 lines
4.7 KiB
Rust

//! Editor mode: a minimal in-TUI line editor for viewing/modifying a file,
//! with bounded undo history.
use crate::app::state::rest::AppStateRest;
use crate::app::state::types::Overlay;
/// State for the built-in line editor overlay: buffer contents, cursor
/// position, and a bounded undo stack.
#[derive(Debug, Clone)]
pub struct EditorState {
pub path: String,
pub content: Vec<String>,
pub undo_stack: Vec<Vec<String>>,
pub cursor_line: usize,
pub cursor_col: usize,
}
impl Default for EditorState {
fn default() -> Self {
EditorState {
path: String::new(),
content: vec![String::new()],
undo_stack: Vec::new(),
cursor_line: 0,
cursor_col: 0,
}
}
}
impl EditorState {
/// Create a fresh editor state for `path`, seeded with existing content
/// (or a single empty line for a new file).
pub fn open(path: String, existing_content: Option<Vec<String>>) -> Self {
let content = existing_content.unwrap_or_else(|| vec![String::new()]);
EditorState {
path,
content,
..Default::default()
}
}
/// Insert a new empty line immediately after the cursor line.
///
/// Why: snapshots content to the undo stack first, matching every other
/// mutating method here.
pub fn insert_line_after(&mut self) {
self.save_undo();
let pos = (self.cursor_line + 1).min(self.content.len());
self.content.insert(pos, String::new());
}
/// Push a snapshot of the current content onto the undo stack, capped at 50 entries.
///
/// Why: `remove(0)` on overflow bounds memory use at the cost of O(n)
/// shifting; the cap (50) keeps that cost negligible in practice.
fn save_undo(&mut self) {
self.undo_stack.push(self.content.clone());
if self.undo_stack.len() > 50 {
self.undo_stack.remove(0);
}
}
/// Move the cursor down one line, clamping the column to the new line's length.
pub fn cursor_down(&mut self) {
if self.cursor_line + 1 < self.content.len() {
self.cursor_line += 1;
}
self.cursor_col = self.cursor_col.min(
self.content.get(self.cursor_line).map(|l| l.len()).unwrap_or(0),
);
}
/// Insert a character at the cursor and advance the cursor past it.
pub fn insert_char(&mut self, c: char) {
self.save_undo();
if let Some(line) = self.content.get_mut(self.cursor_line) {
line.insert(self.cursor_col, c);
self.cursor_col += 1;
}
}
/// Delete the character before the cursor (backspace).
///
/// Flow: if not at column 0, remove the preceding char on this line →
/// otherwise (start of line, not the first line) merge this line into
/// the previous one, joining at the old line's end.
pub fn delete_left(&mut self) {
self.save_undo();
if let Some(line) = self.content.get_mut(self.cursor_line) {
if self.cursor_col > 0 {
self.cursor_col -= 1;
line.remove(self.cursor_col);
} else if self.cursor_line > 0 {
let prev_len = self.content[self.cursor_line - 1].len();
let rest = self.content.remove(self.cursor_line);
self.cursor_line -= 1;
self.cursor_col = prev_len;
self.content[self.cursor_line].push_str(&rest);
}
}
}
/// Join all lines with `\n` into the full file contents, for saving.
pub fn as_string(&self) -> String {
self.content.join("\n")
}
}
/// Feed a chunk of typed text into the active editor, translating newlines
/// and tabs into editor operations.
///
/// Flow: no-op if no editor is open → for each char: `\n`/`\r` inserts a
/// line and moves down, `\t` inserts two spaces, everything else inserts
/// the char directly → mark state dirty.
pub fn handle_editor_input(state: &mut AppStateRest, text: String) {
let editor = &mut state.misc.editor;
if editor.is_none() {
return;
}
let ed = editor.as_mut().unwrap();
for c in text.chars() {
match c {
'\n' | '\r' => {
ed.insert_line_after();
ed.cursor_down();
ed.cursor_col = 0;
}
'\t' => {
ed.insert_char(' ');
ed.insert_char(' ');
}
_ => {
ed.insert_char(c);
}
}
}
state.dirty = true;
}
/// Close the editor overlay without saving, clearing editor state.
pub fn handle_editor_dismiss(state: &mut AppStateRest) {
state.misc.editor = None;
state.misc.overlay = Overlay::None;
state.dirty = true;
}