This commit is contained in:
alice pellerin
2026-03-18 17:30:43 -05:00
parent 7695d23984
commit 0a98df9000
6 changed files with 74 additions and 19 deletions
+25 -6
View File
@@ -1,4 +1,4 @@
use std::{cmp::min, mem::{replace, swap}};
use std::{cmp::min, fs::File, io::Write, mem::{replace, swap}};
use crate::{BYTES_PER_LINE, app::{App, Mode, PartialAction}, edit_action::EditAction};
@@ -10,8 +10,9 @@ pub enum Action {
SelectMode,
Goto,
Zview,
View,
Replace,
Space,
MoveByteUp,
MoveByteDown,
@@ -54,6 +55,8 @@ pub enum Action {
Undo,
Redo,
Save,
}
impl App {
@@ -65,8 +68,9 @@ impl App {
Action::SelectMode => self.select_mode(),
Action::Goto => self.goto(),
Action::Zview => self.zview(),
Action::View => self.view(),
Action::Replace => self.replace(),
Action::Space => self.space(),
Action::MoveByteUp => self.move_byte_up(),
Action::MoveByteDown => self.move_byte_down(),
@@ -109,6 +113,8 @@ impl App {
Action::Undo => self.undo(),
Action::Redo => self.redo(),
Action::Save => self.save(),
}
}
@@ -128,14 +134,18 @@ impl App {
self.partial_action = Some(PartialAction::Goto);
}
const fn zview(&mut self) {
self.partial_action = Some(PartialAction::Zview);
const fn view(&mut self) {
self.partial_action = Some(PartialAction::View);
}
const fn replace(&mut self) {
self.partial_action = Some(PartialAction::Replace);
}
const fn space(&mut self) {
self.partial_action = Some(PartialAction::Space);
}
const fn move_byte_up(&mut self) {
if self.cursor.head >= BYTES_PER_LINE {
self.cursor.head -= BYTES_PER_LINE;
@@ -359,7 +369,7 @@ impl App {
}
fn undo(&mut self) {
if self.time_traveling == Some(0) { return }
if self.time_traveling == Some(0) || self.edit_history.is_empty() { return }
let current_date = self.time_traveling
.map_or(self.edit_history.len() - 1, |date| date - 1);
@@ -396,6 +406,15 @@ impl App {
self.edit_history[previous_date] = edit_action;
}
fn save(&mut self) {
let mut file = File::create(&self.file_path).unwrap();
file.write_all(&self.contents).unwrap();
self.last_saved_at = Some(
self.time_traveling.unwrap_or(self.edit_history.len())
);
}
}
// helpers
+24 -2
View File
@@ -8,6 +8,7 @@ mod widget;
pub struct App {
pub config: Config,
pub file_name: String,
pub file_path: PathBuf,
pub contents: Vec<u8>,
@@ -25,6 +26,8 @@ pub struct App {
pub edit_history: Vec<EditAction>,
// the index *after* the latest edit action
pub time_traveling: Option<usize>,
// the index *after* the last saved edit action
pub last_saved_at: Option<usize>,
pub logs: Vec<String>,
}
@@ -36,7 +39,7 @@ pub enum Mode {
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub enum PartialAction {
Goto, Zview, Replace
Goto, View, Replace, Space
}
impl Mode {
@@ -61,8 +64,9 @@ impl PartialAction {
pub const fn label(self) -> &'static str {
match self {
Self::Goto => "g",
Self::Zview => "z",
Self::View => "z",
Self::Replace => "r",
Self::Space => "",
}
}
}
@@ -87,6 +91,7 @@ impl App {
Self {
config: Config::default(),
file_name: file_path.file_name().unwrap().to_str().unwrap().to_owned(),
file_path,
contents,
@@ -104,6 +109,7 @@ impl App {
edit_history: Vec::new(),
time_traveling: None,
last_saved_at: Some(0),
logs: Vec::new(),
}
@@ -161,6 +167,22 @@ impl App {
}
}
}
const fn has_unsaved_changes(&self) -> bool {
!self.all_changes_saved()
}
const fn all_changes_saved(&self) -> bool {
if let Some(last_saved_at) = self.last_saved_at {
if let Some(time_traveling) = self.time_traveling {
last_saved_at == time_traveling
} else {
last_saved_at == self.edit_history.len()
}
} else {
false
}
}
}
fn nybble_from_hex(hex: char) -> Option<u8> {
+3 -3
View File
@@ -401,10 +401,10 @@ mod status_line {
}
fn modified_indicator(&self) -> Span<'static> {
if self.edit_history.is_empty() {
"".into()
} else {
if self.has_unsaved_changes() {
" [+]".into()
} else {
"".into()
}
}
}
+12 -4
View File
@@ -104,8 +104,9 @@ impl Default for Config {
("v".try_into().unwrap(), Action::SelectMode),
("g".try_into().unwrap(), Action::Goto),
("z".try_into().unwrap(), Action::Zview),
("z".try_into().unwrap(), Action::View),
("r".try_into().unwrap(), Action::Replace),
(" ".try_into().unwrap(), Action::Space),
("i".try_into().unwrap(), Action::MoveByteUp),
("k".try_into().unwrap(), Action::MoveByteDown),
@@ -142,7 +143,10 @@ impl Default for Config {
("l".try_into().unwrap(), Action::GotoLineEnd),
("g".try_into().unwrap(), Action::GotoFileStart),
].into())
].into()),
(Some(PartialAction::Space), [
("w".try_into().unwrap(), Action::Save),
].into()),
].into()),
(Mode::Select, [
(None, [
@@ -151,8 +155,9 @@ impl Default for Config {
("v".try_into().unwrap(), Action::NormalMode),
("g".try_into().unwrap(), Action::Goto),
("z".try_into().unwrap(), Action::Zview),
("z".try_into().unwrap(), Action::View),
("r".try_into().unwrap(), Action::Replace),
(" ".try_into().unwrap(), Action::Space),
("i".try_into().unwrap(), Action::ExtendByteUp),
("k".try_into().unwrap(), Action::ExtendByteDown),
@@ -181,7 +186,10 @@ impl Default for Config {
("u".try_into().unwrap(), Action::Undo),
("U".try_into().unwrap(), Action::Redo),
].into())
].into()),
(Some(PartialAction::Space), [
("w".try_into().unwrap(), Action::Save),
].into()),
].into())
].into()
}
+5 -1
View File
@@ -34,6 +34,10 @@ impl App {
if let Some(date) = self.time_traveling {
self.edit_history.truncate(date);
self.time_traveling = None;
if self.last_saved_at.is_some_and(|it| it > date) {
self.last_saved_at = None;
}
}
self.edit_history.push(edit_action);
@@ -62,7 +66,7 @@ impl App {
fn delete_at(&mut self, cursor: Cursor) {
self.contents.drain(cursor.range());
self.cursor.head = min(cursor.head, cursor.tail);
self.cursor.head = min(min(cursor.head, cursor.tail), self.contents.len() - 1);
self.cursor.collapse();
}
+5 -3
View File
@@ -17,7 +17,8 @@ const BYTES_PER_CHUNK: usize = 4;
const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
// TODO:
// - undo/redo
// - multiple buffers (tabs)
// - search
// - modifications
// - insert/append
// - mode
@@ -26,8 +27,6 @@ const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
// - replace-and-keep-going
// - mode
// - change
// - saving
// - search
// - edit character panel
// - modifier on existing keys like teehee? or jump to panel?
// - if jump to panel, space?
@@ -51,6 +50,9 @@ const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
// - utf8?
// - diffing
// TODO: opening empty file crashes (or deleting entire file)
// - cursor is NOT guaranteed to be in-bounds..?
// when AsciiChar is stabilized, use it instead of char everywhere
fn main() {