Refactor: split editor/mod.rs monolith (4011→3471 lines)
Extracted two impl Editor blocks from the monolithic editor/mod.rs: - editor/dispatch.rs (331 lines): dispatch_editor_cmd() — all F9 menubar EditorCmd variants routed to editor methods. The largest single method in the file, now self-contained with its own module docs. - editor/edit.rs (214 lines): Text editing primitives — insert_char, insert_str, delete_back, delete_forward, insert_tab_with_indent, multi-cursor operations (insert/delete at all positions). Plus private helpers adjust_bookmarks_after_edit and is_in_indent_zone. mod.rs now holds: struct definition, constructors, getters/setters, undo/redo, save, format, search, bookmark, spell, bracket, goto, smooth scroll, user menu, sort_block, and the test module. 1455 tests pass, zero warnings. Zero behavioral changes.
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
//! Editor command dispatch.
|
||||
//!
|
||||
//! `dispatch_editor_cmd` handles [`EditorCmd`] variants from the
|
||||
//! F9 menubar, routing each command to the appropriate editor method.
|
||||
//! Originally part of the monolithic `editor/mod.rs`; extracted here
|
||||
//! to keep the Editor struct definition and constructors separate
|
||||
//! from the dispatch logic.
|
||||
|
||||
use crate::editor::menubar::EditorCmd;
|
||||
use crate::editor::{Editor, EditorResult, Mode, PromptKind};
|
||||
|
||||
impl Editor {
|
||||
/// Dispatch a menu-bar [`EditorCmd`] to the corresponding editor
|
||||
/// method. Returns the resulting [`EditorResult`]. Called by the
|
||||
/// F9 menubar handler.
|
||||
pub(crate) fn dispatch_editor_cmd(&mut self, cmd: EditorCmd) -> EditorResult {
|
||||
match cmd {
|
||||
EditorCmd::Undo => {
|
||||
self.undo();
|
||||
}
|
||||
EditorCmd::Redo => {
|
||||
self.redo();
|
||||
}
|
||||
EditorCmd::WordComplete => {
|
||||
self.word_complete();
|
||||
}
|
||||
EditorCmd::SaveSettings => {
|
||||
self.message = Some("Save via F9→Options→Save setup".to_string());
|
||||
}
|
||||
EditorCmd::Refresh => {
|
||||
// TLC redraws every frame; CK_Refresh is a no-op.
|
||||
}
|
||||
EditorCmd::SpellCheck => {
|
||||
let misspelled = self.find_next_misspelled(self.cursor.position());
|
||||
if let Some((start, end)) = misspelled {
|
||||
self.cursor.set_position(end, &self.buffer);
|
||||
self.cursor.start_selection();
|
||||
self.cursor.set_position(start, &self.buffer);
|
||||
self.message =
|
||||
Some("Misspelled word — press Alt-Tab for suggestions".to_string());
|
||||
} else {
|
||||
self.message = Some("No more misspelled words".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::Cut => {
|
||||
if let Some(text) = self.cursor.selected_text(&self.buffer) {
|
||||
self.clipboard = Some(text.clone());
|
||||
let _ = crate::editor::clipboard_osc52::osc52_copy(&text);
|
||||
self.cursor.delete_selection(&mut self.buffer);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = self.buffer.is_modified();
|
||||
self.message = Some("Block cut".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::Copy => {
|
||||
if let Some(text) = self.cursor.selected_text(&self.buffer) {
|
||||
self.clipboard = Some(text.clone());
|
||||
let _ = crate::editor::clipboard_osc52::osc52_copy(&text);
|
||||
self.message = Some("Block copied".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::ToggleColumnMode => {
|
||||
if self.cursor.selection_mode()
|
||||
== crate::editor::cursor::SelectionMode::Column
|
||||
{
|
||||
self.cursor.clear_selection();
|
||||
self.message = Some("Stream selection".to_string());
|
||||
} else {
|
||||
self.cursor.start_column_selection();
|
||||
self.message = Some("Column selection".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::Delete => {
|
||||
if self.cursor.has_selection() {
|
||||
self.cursor.delete_selection(&mut self.buffer);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = self.buffer.is_modified();
|
||||
self.message = Some("Deleted".to_string());
|
||||
} else {
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
let _ = self.cursor.select_right(&mut self.buffer);
|
||||
if self.cursor.has_selection() {
|
||||
self.cursor.delete_selection(&mut self.buffer);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = self.buffer.is_modified();
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorCmd::Paste => {
|
||||
if let Some(text) = self.clipboard.clone() {
|
||||
if self.cursor.has_selection() {
|
||||
self.cursor.delete_selection(&mut self.buffer);
|
||||
}
|
||||
self.insert_str(&text);
|
||||
self.message = Some("Pasted".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::GotoTop => {
|
||||
self.cursor.set_position(0, &self.buffer);
|
||||
}
|
||||
EditorCmd::GotoBottom => {
|
||||
self.cursor.set_position(self.buffer.len(), &self.buffer);
|
||||
}
|
||||
EditorCmd::BookmarkToggle => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::BookmarkSet);
|
||||
}
|
||||
EditorCmd::BookmarkClearAll => {
|
||||
let names = self.bookmarks.names();
|
||||
let count = names.len();
|
||||
for n in names {
|
||||
self.bookmarks.clear(n);
|
||||
}
|
||||
self.message = Some(format!("{count} bookmarks cleared"));
|
||||
}
|
||||
EditorCmd::GotoLine => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::GotoLine);
|
||||
}
|
||||
EditorCmd::ToggleAutoIndent => {
|
||||
let on = self.toggle_auto_indent();
|
||||
self.message = Some(if on {
|
||||
"Auto-indent: ON".to_string()
|
||||
} else {
|
||||
"Auto-indent: OFF".to_string()
|
||||
});
|
||||
}
|
||||
EditorCmd::FormatParagraph => {
|
||||
self.format_paragraph();
|
||||
}
|
||||
EditorCmd::ToggleShowWhitespace => {
|
||||
let on = self.toggle_show_whitespace();
|
||||
self.message = Some(if on {
|
||||
"Show whitespace: ON".to_string()
|
||||
} else {
|
||||
"Show whitespace: OFF".to_string()
|
||||
});
|
||||
}
|
||||
EditorCmd::ToggleWordWrap => {
|
||||
let on = self.toggle_word_wrap();
|
||||
self.message = Some(if on {
|
||||
"Word wrap: ON".to_string()
|
||||
} else {
|
||||
"Word wrap: OFF".to_string()
|
||||
});
|
||||
}
|
||||
EditorCmd::ToggleSyntax => {
|
||||
self.syntax_enabled = !self.syntax_enabled;
|
||||
self.message = Some(if self.syntax_enabled {
|
||||
"Syntax: ON".to_string()
|
||||
} else {
|
||||
"Syntax: OFF".to_string()
|
||||
});
|
||||
}
|
||||
EditorCmd::Settings => {
|
||||
let ai = if self.auto_indent { "ON" } else { "OFF" };
|
||||
let ww = if self.word_wrap { "ON" } else { "OFF" };
|
||||
let ws = if self.show_whitespace { "ON" } else { "OFF" };
|
||||
let sy = if self.syntax_enabled { "ON" } else { "OFF" };
|
||||
self.message = Some(format!(
|
||||
"Settings: auto-indent={ai} wrap={ww} whitespace={ws} syntax={sy} (Alt-A/Alt-W/Alt-E/Ctrl-S toggle)"
|
||||
));
|
||||
}
|
||||
EditorCmd::New => {
|
||||
let mut new_editor = Self::new_empty();
|
||||
std::mem::swap(&mut self.buffer, &mut new_editor.buffer);
|
||||
std::mem::swap(&mut self.cursor, &mut new_editor.cursor);
|
||||
self.path = None;
|
||||
self.modified = false;
|
||||
self.title = "*new*".to_string();
|
||||
self.message = Some("New buffer".to_string());
|
||||
}
|
||||
EditorCmd::Open => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::InsertFile);
|
||||
}
|
||||
EditorCmd::Save => match self.save() {
|
||||
Ok(()) => self.message = Some("Saved".to_string()),
|
||||
Err(e) => self.message = Some(format!("Save error: {e}")),
|
||||
},
|
||||
EditorCmd::SaveAs => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::SaveAs);
|
||||
}
|
||||
EditorCmd::Quit => {
|
||||
if self.modified {
|
||||
self.open_save_before_close_prompt();
|
||||
} else {
|
||||
return EditorResult::Close;
|
||||
}
|
||||
}
|
||||
EditorCmd::Find => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::Find);
|
||||
}
|
||||
EditorCmd::FindNext => {
|
||||
let from = self.cursor.position();
|
||||
if let Some(m) = self.search.find_next(&self.buffer, from) {
|
||||
self.buffer.set_cursor(m.range.start);
|
||||
self.cursor.set_position(m.range.start, &self.buffer);
|
||||
self.message = Some(if self.search.last_wrapped() {
|
||||
"Search wrapped".to_string()
|
||||
} else {
|
||||
"Match found".to_string()
|
||||
});
|
||||
} else {
|
||||
self.message = Some("No more matches".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::FindPrev => {
|
||||
let from = self.cursor.position();
|
||||
if let Some(m) = self.search.find_prev(&self.buffer, from) {
|
||||
self.buffer.set_cursor(m.range.start);
|
||||
self.cursor.set_position(m.range.start, &self.buffer);
|
||||
self.message = Some(if self.search.last_wrapped() {
|
||||
"Search wrapped".to_string()
|
||||
} else {
|
||||
"Match found".to_string()
|
||||
});
|
||||
} else {
|
||||
self.message = Some("No more matches".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::Replace => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::Replace);
|
||||
}
|
||||
EditorCmd::BookmarkNext => {
|
||||
let cur_line = self.buffer.line_of_cursor() as u32;
|
||||
let all: Vec<(char, crate::editor::bookmark::Mark)> = self
|
||||
.bookmarks
|
||||
.names()
|
||||
.into_iter()
|
||||
.filter_map(|n| self.bookmarks.get(n).map(|m| (n, m)))
|
||||
.collect();
|
||||
let target = all
|
||||
.iter()
|
||||
.find(|(_, m)| m.line > cur_line)
|
||||
.or_else(|| all.first());
|
||||
if let Some((name, mark)) = target {
|
||||
if let Ok(off) = crate::editor::goto::col_to_offset(
|
||||
&self.buffer,
|
||||
mark.line + 1,
|
||||
mark.col + 1,
|
||||
) {
|
||||
self.buffer.set_cursor(off);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.message = Some(format!("Bookmark '{}'", name));
|
||||
}
|
||||
} else {
|
||||
self.message = Some("No bookmarks set".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::BookmarkPrev => {
|
||||
let cur_line = self.buffer.line_of_cursor() as u32;
|
||||
let mut all: Vec<(char, crate::editor::bookmark::Mark)> = self
|
||||
.bookmarks
|
||||
.names()
|
||||
.into_iter()
|
||||
.filter_map(|n| self.bookmarks.get(n).map(|m| (n, m)))
|
||||
.collect();
|
||||
all.sort_by_key(|(_, m)| m.line);
|
||||
let target = all
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(_, m)| m.line < cur_line)
|
||||
.or_else(|| all.last());
|
||||
if let Some((name, mark)) = target {
|
||||
if let Ok(off) = crate::editor::goto::col_to_offset(
|
||||
&self.buffer,
|
||||
mark.line + 1,
|
||||
mark.col + 1,
|
||||
) {
|
||||
self.buffer.set_cursor(off);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.message = Some(format!("Bookmark '{}'", name));
|
||||
}
|
||||
} else {
|
||||
self.message = Some("No bookmarks set".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::EditUserMenu => {
|
||||
let menu = crate::filemanager::usermenu::UserMenu::new();
|
||||
let path = menu.storage_path.clone();
|
||||
let new_editor = Self::open(&path);
|
||||
*self = new_editor;
|
||||
self.message = Some(format!("Editing user menu: {}", path.display()));
|
||||
}
|
||||
EditorCmd::MarkAll => {
|
||||
let len = self.buffer.len();
|
||||
self.cursor.set_position(0, &self.buffer);
|
||||
self.cursor.start_selection();
|
||||
self.cursor.set_position(len, &self.buffer);
|
||||
}
|
||||
EditorCmd::InsertFile => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::InsertFile);
|
||||
}
|
||||
EditorCmd::BlockSave => {
|
||||
if let Some(text) = self.cursor.selected_text(&self.buffer) {
|
||||
self.prompt_input.clear();
|
||||
self.prompt_input.text = text;
|
||||
self.prompt_input.cursor =
|
||||
self.prompt_input.text.chars().count();
|
||||
self.mode = Mode::Prompt(PromptKind::SaveAs);
|
||||
} else {
|
||||
self.message = Some("No selection".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::ToggleOverwrite => {
|
||||
self.overwrite = !self.overwrite;
|
||||
self.message = Some(if self.overwrite {
|
||||
"[OVR]".to_string()
|
||||
} else {
|
||||
"[INS]".to_string()
|
||||
});
|
||||
}
|
||||
EditorCmd::Close => {
|
||||
if self.modified {
|
||||
self.mode = Mode::Prompt(PromptKind::SaveBeforeClose);
|
||||
} else {
|
||||
*self = Self::new_empty();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.message = Some(format!("F9: {:?} (not yet wired)", cmd));
|
||||
}
|
||||
}
|
||||
EditorResult::Running
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
//! Text editing operations.
|
||||
//!
|
||||
//! Low-level character insertion, deletion, and multi-cursor editing
|
||||
//! methods. These are the primitive operations that every key handler
|
||||
//! and menu command delegates to. Extracted from the monolithic
|
||||
//! `editor/mod.rs` to keep the Editor struct and its editing surface
|
||||
//! separate.
|
||||
|
||||
use crate::editor::Editor;
|
||||
|
||||
impl Editor {
|
||||
/// Insert a character at the cursor. Marks the buffer as modified.
|
||||
pub fn insert_char(&mut self, c: char) {
|
||||
self.completer.cancel();
|
||||
let line_before = self.buffer_line_of(self.buffer.cursor());
|
||||
let lines_before = self.buffer.line_count();
|
||||
self.buffer.insert_char(c);
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Insert a string at the cursor.
|
||||
pub fn insert_str(&mut self, s: &str) {
|
||||
let line_before = self.buffer_line_of(self.buffer.cursor());
|
||||
let lines_before = self.buffer.line_count();
|
||||
self.buffer.insert_str(s);
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Recompute bookmark line numbers after a buffer edit.
|
||||
fn adjust_bookmarks_after_edit(&mut self, edit_line: usize, lines_before: usize) {
|
||||
let lines_after = self.buffer.line_count();
|
||||
let delta = lines_after as i64 - lines_before as i64;
|
||||
if delta != 0 {
|
||||
self.bookmarks.adjust_lines(edit_line as u32, delta as i32);
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a Tab with indent-aware behavior (MC parity).
|
||||
///
|
||||
/// When the cursor is positioned at or before the first
|
||||
/// non-whitespace character on its line, insert enough spaces to
|
||||
/// advance to the next `tab_width` boundary. Otherwise insert a
|
||||
/// single literal `\t`.
|
||||
pub fn insert_tab_with_indent(&mut self) {
|
||||
self.completer.cancel();
|
||||
let col = self.cursor.visual_column();
|
||||
let next_boundary = ((col / self.tab_width) + 1) * self.tab_width;
|
||||
if self.is_in_indent_zone() {
|
||||
let spaces = next_boundary.saturating_sub(col).max(1);
|
||||
self.buffer.insert_str(&" ".repeat(spaces));
|
||||
} else {
|
||||
self.buffer.insert_char('\t');
|
||||
}
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// True if the cursor is on a line where all characters before the
|
||||
/// cursor are whitespace.
|
||||
fn is_in_indent_zone(&self) -> bool {
|
||||
let cursor_byte = self.cursor.position();
|
||||
let line_idx = self.buffer_line_of(cursor_byte);
|
||||
let line_start = self.buffer.line_offset(line_idx);
|
||||
if cursor_byte <= line_start {
|
||||
return true;
|
||||
}
|
||||
let bytes = self.buffer.to_bytes();
|
||||
let prefix = bytes.get(line_start..cursor_byte).unwrap_or(&[]);
|
||||
prefix.iter().all(|&b| b == b' ' || b == b'\t')
|
||||
}
|
||||
|
||||
/// Backspace at the cursor.
|
||||
pub fn delete_back(&mut self) {
|
||||
let line_before = self.buffer_line_of(self.buffer.cursor());
|
||||
let lines_before = self.buffer.line_count();
|
||||
self.buffer.delete_back();
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Delete at the cursor.
|
||||
pub fn delete_forward(&mut self) {
|
||||
let line_before = self.buffer_line_of(self.buffer.cursor());
|
||||
let lines_before = self.buffer.line_count();
|
||||
self.buffer.delete_forward();
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Whether any secondary cursors are active.
|
||||
pub fn has_multi_cursor(&self) -> bool {
|
||||
!self.secondary_cursors.is_empty()
|
||||
}
|
||||
|
||||
/// Number of active secondary cursors.
|
||||
pub fn secondary_cursor_count(&self) -> usize {
|
||||
self.secondary_cursors.len()
|
||||
}
|
||||
|
||||
/// Add a secondary cursor at `byte_pos` (deduped, sorted).
|
||||
pub fn add_secondary_cursor(&mut self, byte_pos: usize) {
|
||||
let pos = byte_pos.min(self.buffer.len());
|
||||
if pos == self.cursor.position() {
|
||||
return;
|
||||
}
|
||||
if !self.secondary_cursors.contains(&pos) {
|
||||
self.secondary_cursors.push(pos);
|
||||
self.secondary_cursors.sort_unstable();
|
||||
self.secondary_cursors.dedup();
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove all secondary cursors.
|
||||
pub fn clear_secondary_cursors(&mut self) {
|
||||
self.secondary_cursors.clear();
|
||||
}
|
||||
|
||||
/// Return all cursor positions (primary + secondary), sorted and
|
||||
/// deduped.
|
||||
pub fn all_cursor_positions(&self) -> Vec<usize> {
|
||||
let mut all = self.secondary_cursors.clone();
|
||||
all.push(self.cursor.position());
|
||||
all.sort_unstable();
|
||||
all.dedup();
|
||||
all
|
||||
}
|
||||
|
||||
/// Insert `c` at every cursor position (primary + all secondary).
|
||||
/// Positions are processed right-to-left; after insertion all
|
||||
/// secondary cursor byte offsets are advanced by the char's UTF-8
|
||||
/// length.
|
||||
pub fn insert_char_multi(&mut self, c: char) {
|
||||
if self.secondary_cursors.is_empty() {
|
||||
self.insert_char(c);
|
||||
return;
|
||||
}
|
||||
self.completer.cancel();
|
||||
let char_len = c.len_utf8();
|
||||
let line_before = self.buffer_line_of(self.cursor.position());
|
||||
let lines_before = self.buffer.line_count();
|
||||
let positions = self.all_cursor_positions();
|
||||
for &pos in positions.iter().rev() {
|
||||
self.buffer.set_cursor(pos);
|
||||
self.buffer.insert_char(c);
|
||||
}
|
||||
for sec in &mut self.secondary_cursors {
|
||||
*sec += char_len;
|
||||
}
|
||||
self.cursor
|
||||
.set_position(self.cursor.position() + char_len, &self.buffer);
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Backspace at every cursor position. Positions are processed
|
||||
/// right-to-left. Secondary cursors at byte offset 0 are skipped.
|
||||
pub fn delete_back_multi(&mut self) {
|
||||
if self.secondary_cursors.is_empty() {
|
||||
self.delete_back();
|
||||
return;
|
||||
}
|
||||
self.completer.cancel();
|
||||
let line_before = self.buffer_line_of(self.cursor.position());
|
||||
let lines_before = self.buffer.line_count();
|
||||
let positions = self.all_cursor_positions();
|
||||
for &pos in positions.iter().rev() {
|
||||
if pos == 0 {
|
||||
continue;
|
||||
}
|
||||
self.buffer.set_cursor(pos);
|
||||
self.buffer.delete_back();
|
||||
}
|
||||
let new_len = self.buffer.len();
|
||||
self.secondary_cursors.retain_mut(|p| {
|
||||
if *p > 0 {
|
||||
*p -= 1;
|
||||
}
|
||||
*p <= new_len
|
||||
});
|
||||
let primary = self.cursor.position().saturating_sub(1);
|
||||
self.cursor.set_position(primary, &self.buffer);
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Delete-forward at every cursor position. Positions are processed
|
||||
/// right-to-left.
|
||||
pub fn delete_forward_multi(&mut self) {
|
||||
if self.secondary_cursors.is_empty() {
|
||||
self.delete_forward();
|
||||
return;
|
||||
}
|
||||
self.completer.cancel();
|
||||
let line_before = self.buffer_line_of(self.cursor.position());
|
||||
let lines_before = self.buffer.line_count();
|
||||
let positions = self.all_cursor_positions();
|
||||
for &pos in positions.iter().rev() {
|
||||
self.buffer.set_cursor(pos);
|
||||
self.buffer.delete_forward();
|
||||
}
|
||||
let new_len = self.buffer.len();
|
||||
self.secondary_cursors.retain(|p| *p <= new_len);
|
||||
self.cursor
|
||||
.set_position(self.cursor.position().min(new_len), &self.buffer);
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.modified = true;
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,9 @@ pub mod completion;
|
||||
pub mod cursor;
|
||||
pub mod cursor_shape;
|
||||
/// Per-file cursor position save/restore (MC `~/.mc/filepos`).
|
||||
pub mod dispatch;
|
||||
pub mod edit;
|
||||
/// Per-file cursor position save/restore (MC `~/.mc/filepos`).
|
||||
pub mod filepos;
|
||||
pub mod folding;
|
||||
pub mod format;
|
||||
@@ -1046,217 +1049,6 @@ impl Editor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Insert a character at the cursor. Marks the buffer as
|
||||
/// modified.
|
||||
pub fn insert_char(&mut self, c: char) {
|
||||
self.completer.cancel();
|
||||
let line_before = self.buffer_line_of(self.buffer.cursor());
|
||||
let lines_before = self.buffer.line_count();
|
||||
self.buffer.insert_char(c);
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Insert a string at the cursor.
|
||||
pub fn insert_str(&mut self, s: &str) {
|
||||
let line_before = self.buffer_line_of(self.buffer.cursor());
|
||||
let lines_before = self.buffer.line_count();
|
||||
self.buffer.insert_str(s);
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Recompute bookmark line numbers after a buffer edit. Called by
|
||||
/// every insert/delete entry point on `Editor`. `edit_line` is the
|
||||
/// line where the edit happened; `lines_before` is the line count
|
||||
/// before the edit.
|
||||
fn adjust_bookmarks_after_edit(&mut self, edit_line: usize, lines_before: usize) {
|
||||
let lines_after = self.buffer.line_count();
|
||||
let delta = lines_after as i64 - lines_before as i64;
|
||||
if delta != 0 {
|
||||
self.bookmarks.adjust_lines(edit_line as u32, delta as i32);
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a Tab with indent-aware behavior (MC parity).
|
||||
///
|
||||
/// When the cursor is positioned at or before the first
|
||||
/// non-whitespace character on its line, insert enough spaces to
|
||||
/// advance to the next `tab_width` boundary (the indent context).
|
||||
/// Otherwise (cursor is mid-line or past all whitespace) insert
|
||||
/// a single literal `\t` character (the column-alignment
|
||||
/// context).
|
||||
///
|
||||
/// The cursor's `visual_column` is tab-expanded; we use it to
|
||||
/// decide which context applies.
|
||||
pub fn insert_tab_with_indent(&mut self) {
|
||||
self.completer.cancel();
|
||||
let col = self.cursor.visual_column();
|
||||
let next_boundary = ((col / self.tab_width) + 1) * self.tab_width;
|
||||
if self.is_in_indent_zone() {
|
||||
let spaces = next_boundary.saturating_sub(col).max(1);
|
||||
self.buffer.insert_str(&" ".repeat(spaces));
|
||||
} else {
|
||||
self.buffer.insert_char('\t');
|
||||
}
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// True if the cursor is on a line where all characters before
|
||||
/// the cursor are whitespace. Used to decide between Tab-as-indent
|
||||
/// and Tab-as-column-align.
|
||||
fn is_in_indent_zone(&self) -> bool {
|
||||
let cursor_byte = self.cursor.position();
|
||||
let line_idx = self.buffer_line_of(cursor_byte);
|
||||
let line_start = self.buffer.line_offset(line_idx);
|
||||
if cursor_byte <= line_start {
|
||||
return true;
|
||||
}
|
||||
let bytes = self.buffer.to_bytes();
|
||||
let prefix = bytes.get(line_start..cursor_byte).unwrap_or(&[]);
|
||||
prefix.iter().all(|&b| b == b' ' || b == b'\t')
|
||||
}
|
||||
|
||||
/// Backspace at the cursor.
|
||||
pub fn delete_back(&mut self) {
|
||||
let line_before = self.buffer_line_of(self.buffer.cursor());
|
||||
let lines_before = self.buffer.line_count();
|
||||
self.buffer.delete_back();
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Delete at the cursor.
|
||||
pub fn delete_forward(&mut self) {
|
||||
let line_before = self.buffer_line_of(self.buffer.cursor());
|
||||
let lines_before = self.buffer.line_count();
|
||||
self.buffer.delete_forward();
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Whether any secondary cursors are active.
|
||||
pub fn has_multi_cursor(&self) -> bool {
|
||||
!self.secondary_cursors.is_empty()
|
||||
}
|
||||
|
||||
/// Number of active secondary cursors.
|
||||
pub fn secondary_cursor_count(&self) -> usize {
|
||||
self.secondary_cursors.len()
|
||||
}
|
||||
|
||||
/// Add a secondary cursor at `byte_pos` (deduped, sorted).
|
||||
pub fn add_secondary_cursor(&mut self, byte_pos: usize) {
|
||||
let pos = byte_pos.min(self.buffer.len());
|
||||
if pos == self.cursor.position() {
|
||||
return;
|
||||
}
|
||||
if !self.secondary_cursors.contains(&pos) {
|
||||
self.secondary_cursors.push(pos);
|
||||
self.secondary_cursors.sort_unstable();
|
||||
self.secondary_cursors.dedup();
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove all secondary cursors.
|
||||
pub fn clear_secondary_cursors(&mut self) {
|
||||
self.secondary_cursors.clear();
|
||||
}
|
||||
|
||||
/// Return all cursor positions (primary + secondary), sorted and deduped.
|
||||
pub fn all_cursor_positions(&self) -> Vec<usize> {
|
||||
let mut all = self.secondary_cursors.clone();
|
||||
all.push(self.cursor.position());
|
||||
all.sort_unstable();
|
||||
all.dedup();
|
||||
all
|
||||
}
|
||||
|
||||
/// Insert `c` at every cursor position (primary + all secondary).
|
||||
/// Positions are processed right-to-left so earlier insertions
|
||||
/// don't shift later positions. After insertion, all secondary
|
||||
/// cursor byte offsets are advanced by the char's UTF-8 length.
|
||||
pub fn insert_char_multi(&mut self, c: char) {
|
||||
if self.secondary_cursors.is_empty() {
|
||||
self.insert_char(c);
|
||||
return;
|
||||
}
|
||||
self.completer.cancel();
|
||||
let char_len = c.len_utf8();
|
||||
let line_before = self.buffer_line_of(self.cursor.position());
|
||||
let lines_before = self.buffer.line_count();
|
||||
let positions = self.all_cursor_positions();
|
||||
for &pos in positions.iter().rev() {
|
||||
self.buffer.set_cursor(pos);
|
||||
self.buffer.insert_char(c);
|
||||
}
|
||||
for sec in &mut self.secondary_cursors {
|
||||
*sec += char_len;
|
||||
}
|
||||
self.cursor.set_position(
|
||||
self.cursor.position() + char_len,
|
||||
&self.buffer,
|
||||
);
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Backspace at every cursor position. Positions are processed
|
||||
/// right-to-left. Secondary cursors at byte offset 0 are skipped.
|
||||
pub fn delete_back_multi(&mut self) {
|
||||
if self.secondary_cursors.is_empty() {
|
||||
self.delete_back();
|
||||
return;
|
||||
}
|
||||
self.completer.cancel();
|
||||
let line_before = self.buffer_line_of(self.cursor.position());
|
||||
let lines_before = self.buffer.line_count();
|
||||
let positions = self.all_cursor_positions();
|
||||
for &pos in positions.iter().rev() {
|
||||
if pos == 0 {
|
||||
continue;
|
||||
}
|
||||
self.buffer.set_cursor(pos);
|
||||
self.buffer.delete_back();
|
||||
}
|
||||
let new_len = self.buffer.len();
|
||||
self.secondary_cursors.retain_mut(|p| {
|
||||
if *p > 0 { *p -= 1; }
|
||||
*p <= new_len
|
||||
});
|
||||
let primary = self.cursor.position().saturating_sub(1);
|
||||
self.cursor.set_position(primary, &self.buffer);
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Delete-forward at every cursor position. Positions are processed
|
||||
/// right-to-left so earlier deletions don't shift later positions.
|
||||
pub fn delete_forward_multi(&mut self) {
|
||||
if self.secondary_cursors.is_empty() {
|
||||
self.delete_forward();
|
||||
return;
|
||||
}
|
||||
self.completer.cancel();
|
||||
let line_before = self.buffer_line_of(self.cursor.position());
|
||||
let lines_before = self.buffer.line_count();
|
||||
let positions = self.all_cursor_positions();
|
||||
for &pos in positions.iter().rev() {
|
||||
self.buffer.set_cursor(pos);
|
||||
self.buffer.delete_forward();
|
||||
}
|
||||
let new_len = self.buffer.len();
|
||||
self.secondary_cursors.retain(|p| *p <= new_len);
|
||||
self.cursor.set_position(self.cursor.position().min(new_len), &self.buffer);
|
||||
self.adjust_bookmarks_after_edit(line_before, lines_before);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Undo the last edit. Returns true if anything was undone.
|
||||
pub fn undo(&mut self) -> bool {
|
||||
let r = self.buffer.undo();
|
||||
@@ -1277,338 +1069,6 @@ impl Editor {
|
||||
r
|
||||
}
|
||||
|
||||
/// Dispatch a menu-bar [`crate::editor::menubar::EditorCmd`] to
|
||||
/// the corresponding editor method. Returns the resulting
|
||||
/// [`EditorResult`]. Used by F9 menubar dispatch.
|
||||
pub(crate) fn dispatch_editor_cmd(
|
||||
&mut self,
|
||||
cmd: crate::editor::menubar::EditorCmd,
|
||||
) -> EditorResult {
|
||||
use crate::editor::menubar::EditorCmd;
|
||||
match cmd {
|
||||
EditorCmd::Undo => {
|
||||
self.undo();
|
||||
}
|
||||
EditorCmd::Redo => {
|
||||
self.redo();
|
||||
}
|
||||
EditorCmd::WordComplete => {
|
||||
self.word_complete();
|
||||
}
|
||||
EditorCmd::SaveSettings => {
|
||||
self.message = Some("Save via F9→Options→Save setup".to_string());
|
||||
}
|
||||
EditorCmd::Refresh => {
|
||||
// TLC redraws every frame; CK_Refresh is a no-op.
|
||||
}
|
||||
EditorCmd::SpellCheck => {
|
||||
let misspelled = self.find_next_misspelled(
|
||||
self.cursor.position(),
|
||||
);
|
||||
if let Some((start, end)) = misspelled {
|
||||
self.cursor.set_position(end, &self.buffer);
|
||||
self.cursor.start_selection();
|
||||
self.cursor.set_position(start, &self.buffer);
|
||||
self.message = Some(
|
||||
"Misspelled word — press Alt-Tab for suggestions"
|
||||
.to_string(),
|
||||
);
|
||||
} else {
|
||||
self.message =
|
||||
Some("No more misspelled words".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::Cut => {
|
||||
if let Some(text) = self.cursor.selected_text(&self.buffer) {
|
||||
self.clipboard = Some(text.clone());
|
||||
let _ = crate::editor::clipboard_osc52::osc52_copy(&text);
|
||||
self.cursor.delete_selection(&mut self.buffer);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = self.buffer.is_modified();
|
||||
self.message = Some("Block cut".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::Copy => {
|
||||
if let Some(text) = self.cursor.selected_text(&self.buffer) {
|
||||
self.clipboard = Some(text.clone());
|
||||
let _ = crate::editor::clipboard_osc52::osc52_copy(&text);
|
||||
self.message = Some("Block copied".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::ToggleColumnMode => {
|
||||
if self.cursor.selection_mode() == crate::editor::cursor::SelectionMode::Column {
|
||||
self.cursor.clear_selection();
|
||||
self.message = Some("Stream selection".to_string());
|
||||
} else {
|
||||
self.cursor.start_column_selection();
|
||||
self.message = Some("Column selection".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::Delete => {
|
||||
if self.cursor.has_selection() {
|
||||
self.cursor.delete_selection(&mut self.buffer);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = self.buffer.is_modified();
|
||||
self.message = Some("Deleted".to_string());
|
||||
} else {
|
||||
// Delete forward one character.
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
let _ = self.cursor.select_right(&mut self.buffer);
|
||||
if self.cursor.has_selection() {
|
||||
self.cursor.delete_selection(&mut self.buffer);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.modified = self.buffer.is_modified();
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorCmd::Paste => {
|
||||
if let Some(text) = self.clipboard.clone() {
|
||||
if self.cursor.has_selection() {
|
||||
self.cursor.delete_selection(&mut self.buffer);
|
||||
}
|
||||
self.insert_str(&text);
|
||||
self.message = Some("Pasted".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::GotoTop => {
|
||||
self.cursor.set_position(0, &self.buffer);
|
||||
}
|
||||
EditorCmd::GotoBottom => {
|
||||
self.cursor.set_position(self.buffer.len(), &self.buffer);
|
||||
}
|
||||
EditorCmd::BookmarkToggle => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::BookmarkSet);
|
||||
}
|
||||
EditorCmd::BookmarkClearAll => {
|
||||
let names = self.bookmarks.names();
|
||||
let count = names.len();
|
||||
for n in names {
|
||||
self.bookmarks.clear(n);
|
||||
}
|
||||
self.message = Some(format!("{count} bookmarks cleared"));
|
||||
}
|
||||
EditorCmd::GotoLine => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::GotoLine);
|
||||
}
|
||||
EditorCmd::ToggleAutoIndent => {
|
||||
let on = self.toggle_auto_indent();
|
||||
self.message = Some(if on {
|
||||
"Auto-indent: ON".to_string()
|
||||
} else {
|
||||
"Auto-indent: OFF".to_string()
|
||||
});
|
||||
}
|
||||
EditorCmd::FormatParagraph => {
|
||||
self.format_paragraph();
|
||||
}
|
||||
EditorCmd::ToggleShowWhitespace => {
|
||||
let on = self.toggle_show_whitespace();
|
||||
self.message = Some(if on {
|
||||
"Show whitespace: ON".to_string()
|
||||
} else {
|
||||
"Show whitespace: OFF".to_string()
|
||||
});
|
||||
}
|
||||
EditorCmd::ToggleWordWrap => {
|
||||
let on = self.toggle_word_wrap();
|
||||
self.message = Some(if on {
|
||||
"Word wrap: ON".to_string()
|
||||
} else {
|
||||
"Word wrap: OFF".to_string()
|
||||
});
|
||||
}
|
||||
EditorCmd::ToggleSyntax => {
|
||||
self.syntax_enabled = !self.syntax_enabled;
|
||||
self.message = Some(if self.syntax_enabled {
|
||||
"Syntax: ON".to_string()
|
||||
} else {
|
||||
"Syntax: OFF".to_string()
|
||||
});
|
||||
}
|
||||
EditorCmd::Settings => {
|
||||
let ai = if self.auto_indent { "ON" } else { "OFF" };
|
||||
let ww = if self.word_wrap { "ON" } else { "OFF" };
|
||||
let ws = if self.show_whitespace { "ON" } else { "OFF" };
|
||||
let sy = if self.syntax_enabled { "ON" } else { "OFF" };
|
||||
self.message = Some(format!(
|
||||
"Settings: auto-indent={ai} wrap={ww} whitespace={ws} syntax={sy} (Alt-A/Alt-W/Alt-E/Ctrl-S toggle)"
|
||||
));
|
||||
}
|
||||
EditorCmd::New => {
|
||||
let mut new_editor = Self::new_empty();
|
||||
std::mem::swap(&mut self.buffer, &mut new_editor.buffer);
|
||||
std::mem::swap(&mut self.cursor, &mut new_editor.cursor);
|
||||
self.path = None;
|
||||
self.modified = false;
|
||||
self.title = "*new*".to_string();
|
||||
self.message = Some("New buffer".to_string());
|
||||
}
|
||||
EditorCmd::Open => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::InsertFile);
|
||||
}
|
||||
EditorCmd::Save => {
|
||||
match self.save() {
|
||||
Ok(()) => self.message = Some("Saved".to_string()),
|
||||
Err(e) => self.message = Some(format!("Save error: {e}")),
|
||||
}
|
||||
}
|
||||
EditorCmd::SaveAs => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::SaveAs);
|
||||
}
|
||||
EditorCmd::Quit => {
|
||||
if self.modified {
|
||||
self.open_save_before_close_prompt();
|
||||
} else {
|
||||
return EditorResult::Close;
|
||||
}
|
||||
}
|
||||
EditorCmd::Find => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::Find);
|
||||
}
|
||||
EditorCmd::FindNext => {
|
||||
let from = self.cursor.position();
|
||||
if let Some(m) = self.search.find_next(&self.buffer, from) {
|
||||
self.buffer.set_cursor(m.range.start);
|
||||
self.cursor.set_position(m.range.start, &self.buffer);
|
||||
self.message = Some(
|
||||
if self.search.last_wrapped() {
|
||||
"Search wrapped".to_string()
|
||||
} else {
|
||||
"Match found".to_string()
|
||||
},
|
||||
);
|
||||
} else {
|
||||
self.message = Some("No more matches".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::FindPrev => {
|
||||
let from = self.cursor.position();
|
||||
if let Some(m) = self.search.find_prev(&self.buffer, from) {
|
||||
self.buffer.set_cursor(m.range.start);
|
||||
self.cursor.set_position(m.range.start, &self.buffer);
|
||||
self.message = Some(
|
||||
if self.search.last_wrapped() {
|
||||
"Search wrapped".to_string()
|
||||
} else {
|
||||
"Match found".to_string()
|
||||
},
|
||||
);
|
||||
} else {
|
||||
self.message = Some("No more matches".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::Replace => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::Replace);
|
||||
}
|
||||
EditorCmd::BookmarkNext => {
|
||||
let cur_line = self.buffer.line_of_cursor() as u32;
|
||||
let all: Vec<(char, crate::editor::bookmark::Mark)> = self
|
||||
.bookmarks
|
||||
.names()
|
||||
.into_iter()
|
||||
.filter_map(|n| self.bookmarks.get(n).map(|m| (n, m)))
|
||||
.collect();
|
||||
let target = all
|
||||
.iter()
|
||||
.find(|(_, m)| m.line > cur_line)
|
||||
.or_else(|| all.first());
|
||||
if let Some((name, mark)) = target {
|
||||
if let Ok(off) = crate::editor::goto::col_to_offset(
|
||||
&self.buffer,
|
||||
mark.line + 1,
|
||||
mark.col + 1,
|
||||
) {
|
||||
self.buffer.set_cursor(off);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.message = Some(format!("Bookmark '{}'", name));
|
||||
}
|
||||
} else {
|
||||
self.message = Some("No bookmarks set".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::BookmarkPrev => {
|
||||
let cur_line = self.buffer.line_of_cursor() as u32;
|
||||
let mut all: Vec<(char, crate::editor::bookmark::Mark)> = self
|
||||
.bookmarks
|
||||
.names()
|
||||
.into_iter()
|
||||
.filter_map(|n| self.bookmarks.get(n).map(|m| (n, m)))
|
||||
.collect();
|
||||
all.sort_by_key(|(_, m)| m.line);
|
||||
let target = all
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(_, m)| m.line < cur_line)
|
||||
.or_else(|| all.last());
|
||||
if let Some((name, mark)) = target {
|
||||
if let Ok(off) = crate::editor::goto::col_to_offset(
|
||||
&self.buffer,
|
||||
mark.line + 1,
|
||||
mark.col + 1,
|
||||
) {
|
||||
self.buffer.set_cursor(off);
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.message = Some(format!("Bookmark '{}'", name));
|
||||
}
|
||||
} else {
|
||||
self.message = Some("No bookmarks set".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::EditUserMenu => {
|
||||
let menu = crate::filemanager::usermenu::UserMenu::new();
|
||||
let path = menu.storage_path.clone();
|
||||
let new_editor = Self::open(&path);
|
||||
*self = new_editor;
|
||||
self.message = Some(format!("Editing user menu: {}", path.display()));
|
||||
}
|
||||
EditorCmd::MarkAll => {
|
||||
let len = self.buffer.len();
|
||||
self.cursor.set_position(0, &self.buffer);
|
||||
self.cursor.start_selection();
|
||||
self.cursor.set_position(len, &self.buffer);
|
||||
}
|
||||
EditorCmd::InsertFile => {
|
||||
self.prompt_input.clear();
|
||||
self.mode = Mode::Prompt(PromptKind::InsertFile);
|
||||
}
|
||||
EditorCmd::BlockSave => {
|
||||
if let Some(text) = self.cursor.selected_text(&self.buffer) {
|
||||
self.prompt_input.clear();
|
||||
self.prompt_input.text = text;
|
||||
self.prompt_input.cursor = self.prompt_input.text.chars().count();
|
||||
self.mode = Mode::Prompt(PromptKind::SaveAs);
|
||||
} else {
|
||||
self.message = Some("No selection".to_string());
|
||||
}
|
||||
}
|
||||
EditorCmd::ToggleOverwrite => {
|
||||
self.overwrite = !self.overwrite;
|
||||
self.message = Some(if self.overwrite {
|
||||
"[OVR]".to_string()
|
||||
} else {
|
||||
"[INS]".to_string()
|
||||
});
|
||||
}
|
||||
EditorCmd::Close => {
|
||||
if self.modified {
|
||||
self.mode = Mode::Prompt(PromptKind::SaveBeforeClose);
|
||||
} else {
|
||||
*self = Self::new_empty();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.message = Some(format!("F9: {:?} (not yet wired)", cmd));
|
||||
}
|
||||
}
|
||||
EditorResult::Running
|
||||
}
|
||||
|
||||
/// Alt-P — reformat the current paragraph.
|
||||
///
|
||||
/// Walks the contiguous block of non-blank lines that contains
|
||||
|
||||
Reference in New Issue
Block a user