saving
This commit is contained in:
+25
-6
@@ -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};
|
use crate::{BYTES_PER_LINE, app::{App, Mode, PartialAction}, edit_action::EditAction};
|
||||||
|
|
||||||
@@ -10,8 +10,9 @@ pub enum Action {
|
|||||||
SelectMode,
|
SelectMode,
|
||||||
|
|
||||||
Goto,
|
Goto,
|
||||||
Zview,
|
View,
|
||||||
Replace,
|
Replace,
|
||||||
|
Space,
|
||||||
|
|
||||||
MoveByteUp,
|
MoveByteUp,
|
||||||
MoveByteDown,
|
MoveByteDown,
|
||||||
@@ -54,6 +55,8 @@ pub enum Action {
|
|||||||
|
|
||||||
Undo,
|
Undo,
|
||||||
Redo,
|
Redo,
|
||||||
|
|
||||||
|
Save,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
@@ -65,8 +68,9 @@ impl App {
|
|||||||
Action::SelectMode => self.select_mode(),
|
Action::SelectMode => self.select_mode(),
|
||||||
|
|
||||||
Action::Goto => self.goto(),
|
Action::Goto => self.goto(),
|
||||||
Action::Zview => self.zview(),
|
Action::View => self.view(),
|
||||||
Action::Replace => self.replace(),
|
Action::Replace => self.replace(),
|
||||||
|
Action::Space => self.space(),
|
||||||
|
|
||||||
Action::MoveByteUp => self.move_byte_up(),
|
Action::MoveByteUp => self.move_byte_up(),
|
||||||
Action::MoveByteDown => self.move_byte_down(),
|
Action::MoveByteDown => self.move_byte_down(),
|
||||||
@@ -109,6 +113,8 @@ impl App {
|
|||||||
|
|
||||||
Action::Undo => self.undo(),
|
Action::Undo => self.undo(),
|
||||||
Action::Redo => self.redo(),
|
Action::Redo => self.redo(),
|
||||||
|
|
||||||
|
Action::Save => self.save(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,14 +134,18 @@ impl App {
|
|||||||
self.partial_action = Some(PartialAction::Goto);
|
self.partial_action = Some(PartialAction::Goto);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn zview(&mut self) {
|
const fn view(&mut self) {
|
||||||
self.partial_action = Some(PartialAction::Zview);
|
self.partial_action = Some(PartialAction::View);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn replace(&mut self) {
|
const fn replace(&mut self) {
|
||||||
self.partial_action = Some(PartialAction::Replace);
|
self.partial_action = Some(PartialAction::Replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn space(&mut self) {
|
||||||
|
self.partial_action = Some(PartialAction::Space);
|
||||||
|
}
|
||||||
|
|
||||||
const fn move_byte_up(&mut self) {
|
const fn move_byte_up(&mut self) {
|
||||||
if self.cursor.head >= BYTES_PER_LINE {
|
if self.cursor.head >= BYTES_PER_LINE {
|
||||||
self.cursor.head -= BYTES_PER_LINE;
|
self.cursor.head -= BYTES_PER_LINE;
|
||||||
@@ -359,7 +369,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn undo(&mut self) {
|
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
|
let current_date = self.time_traveling
|
||||||
.map_or(self.edit_history.len() - 1, |date| date - 1);
|
.map_or(self.edit_history.len() - 1, |date| date - 1);
|
||||||
@@ -396,6 +406,15 @@ impl App {
|
|||||||
|
|
||||||
self.edit_history[previous_date] = edit_action;
|
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
|
// helpers
|
||||||
|
|||||||
+24
-2
@@ -8,6 +8,7 @@ mod widget;
|
|||||||
pub struct App {
|
pub struct App {
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
|
pub file_path: PathBuf,
|
||||||
|
|
||||||
pub contents: Vec<u8>,
|
pub contents: Vec<u8>,
|
||||||
|
|
||||||
@@ -25,6 +26,8 @@ pub struct App {
|
|||||||
pub edit_history: Vec<EditAction>,
|
pub edit_history: Vec<EditAction>,
|
||||||
// the index *after* the latest edit action
|
// the index *after* the latest edit action
|
||||||
pub time_traveling: Option<usize>,
|
pub time_traveling: Option<usize>,
|
||||||
|
// the index *after* the last saved edit action
|
||||||
|
pub last_saved_at: Option<usize>,
|
||||||
|
|
||||||
pub logs: Vec<String>,
|
pub logs: Vec<String>,
|
||||||
}
|
}
|
||||||
@@ -36,7 +39,7 @@ pub enum Mode {
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
pub enum PartialAction {
|
pub enum PartialAction {
|
||||||
Goto, Zview, Replace
|
Goto, View, Replace, Space
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode {
|
impl Mode {
|
||||||
@@ -61,8 +64,9 @@ impl PartialAction {
|
|||||||
pub const fn label(self) -> &'static str {
|
pub const fn label(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Goto => "g",
|
Self::Goto => "g",
|
||||||
Self::Zview => "z",
|
Self::View => "z",
|
||||||
Self::Replace => "r",
|
Self::Replace => "r",
|
||||||
|
Self::Space => "␠",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,6 +91,7 @@ impl App {
|
|||||||
Self {
|
Self {
|
||||||
config: Config::default(),
|
config: Config::default(),
|
||||||
file_name: file_path.file_name().unwrap().to_str().unwrap().to_owned(),
|
file_name: file_path.file_name().unwrap().to_str().unwrap().to_owned(),
|
||||||
|
file_path,
|
||||||
|
|
||||||
contents,
|
contents,
|
||||||
|
|
||||||
@@ -104,6 +109,7 @@ impl App {
|
|||||||
|
|
||||||
edit_history: Vec::new(),
|
edit_history: Vec::new(),
|
||||||
time_traveling: None,
|
time_traveling: None,
|
||||||
|
last_saved_at: Some(0),
|
||||||
|
|
||||||
logs: Vec::new(),
|
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> {
|
fn nybble_from_hex(hex: char) -> Option<u8> {
|
||||||
|
|||||||
+3
-3
@@ -401,10 +401,10 @@ mod status_line {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn modified_indicator(&self) -> Span<'static> {
|
fn modified_indicator(&self) -> Span<'static> {
|
||||||
if self.edit_history.is_empty() {
|
if self.has_unsaved_changes() {
|
||||||
"".into()
|
|
||||||
} else {
|
|
||||||
" [+]".into()
|
" [+]".into()
|
||||||
|
} else {
|
||||||
|
"".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-4
@@ -104,8 +104,9 @@ impl Default for Config {
|
|||||||
("v".try_into().unwrap(), Action::SelectMode),
|
("v".try_into().unwrap(), Action::SelectMode),
|
||||||
|
|
||||||
("g".try_into().unwrap(), Action::Goto),
|
("g".try_into().unwrap(), Action::Goto),
|
||||||
("z".try_into().unwrap(), Action::Zview),
|
("z".try_into().unwrap(), Action::View),
|
||||||
("r".try_into().unwrap(), Action::Replace),
|
("r".try_into().unwrap(), Action::Replace),
|
||||||
|
(" ".try_into().unwrap(), Action::Space),
|
||||||
|
|
||||||
("i".try_into().unwrap(), Action::MoveByteUp),
|
("i".try_into().unwrap(), Action::MoveByteUp),
|
||||||
("k".try_into().unwrap(), Action::MoveByteDown),
|
("k".try_into().unwrap(), Action::MoveByteDown),
|
||||||
@@ -142,7 +143,10 @@ impl Default for Config {
|
|||||||
("l".try_into().unwrap(), Action::GotoLineEnd),
|
("l".try_into().unwrap(), Action::GotoLineEnd),
|
||||||
|
|
||||||
("g".try_into().unwrap(), Action::GotoFileStart),
|
("g".try_into().unwrap(), Action::GotoFileStart),
|
||||||
].into())
|
].into()),
|
||||||
|
(Some(PartialAction::Space), [
|
||||||
|
("w".try_into().unwrap(), Action::Save),
|
||||||
|
].into()),
|
||||||
].into()),
|
].into()),
|
||||||
(Mode::Select, [
|
(Mode::Select, [
|
||||||
(None, [
|
(None, [
|
||||||
@@ -151,8 +155,9 @@ impl Default for Config {
|
|||||||
("v".try_into().unwrap(), Action::NormalMode),
|
("v".try_into().unwrap(), Action::NormalMode),
|
||||||
|
|
||||||
("g".try_into().unwrap(), Action::Goto),
|
("g".try_into().unwrap(), Action::Goto),
|
||||||
("z".try_into().unwrap(), Action::Zview),
|
("z".try_into().unwrap(), Action::View),
|
||||||
("r".try_into().unwrap(), Action::Replace),
|
("r".try_into().unwrap(), Action::Replace),
|
||||||
|
(" ".try_into().unwrap(), Action::Space),
|
||||||
|
|
||||||
("i".try_into().unwrap(), Action::ExtendByteUp),
|
("i".try_into().unwrap(), Action::ExtendByteUp),
|
||||||
("k".try_into().unwrap(), Action::ExtendByteDown),
|
("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::Undo),
|
||||||
("U".try_into().unwrap(), Action::Redo),
|
("U".try_into().unwrap(), Action::Redo),
|
||||||
].into())
|
].into()),
|
||||||
|
(Some(PartialAction::Space), [
|
||||||
|
("w".try_into().unwrap(), Action::Save),
|
||||||
|
].into()),
|
||||||
].into())
|
].into())
|
||||||
].into()
|
].into()
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -34,6 +34,10 @@ impl App {
|
|||||||
if let Some(date) = self.time_traveling {
|
if let Some(date) = self.time_traveling {
|
||||||
self.edit_history.truncate(date);
|
self.edit_history.truncate(date);
|
||||||
self.time_traveling = None;
|
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);
|
self.edit_history.push(edit_action);
|
||||||
@@ -62,7 +66,7 @@ impl App {
|
|||||||
fn delete_at(&mut self, cursor: Cursor) {
|
fn delete_at(&mut self, cursor: Cursor) {
|
||||||
self.contents.drain(cursor.range());
|
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();
|
self.cursor.collapse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -17,7 +17,8 @@ const BYTES_PER_CHUNK: usize = 4;
|
|||||||
const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
|
const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - undo/redo
|
// - multiple buffers (tabs)
|
||||||
|
// - search
|
||||||
// - modifications
|
// - modifications
|
||||||
// - insert/append
|
// - insert/append
|
||||||
// - mode
|
// - mode
|
||||||
@@ -26,8 +27,6 @@ const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
|
|||||||
// - replace-and-keep-going
|
// - replace-and-keep-going
|
||||||
// - mode
|
// - mode
|
||||||
// - change
|
// - change
|
||||||
// - saving
|
|
||||||
// - search
|
|
||||||
// - edit character panel
|
// - edit character panel
|
||||||
// - modifier on existing keys like teehee? or jump to panel?
|
// - modifier on existing keys like teehee? or jump to panel?
|
||||||
// - if jump to panel, space?
|
// - if jump to panel, space?
|
||||||
@@ -51,6 +50,9 @@ const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
|
|||||||
// - utf8?
|
// - utf8?
|
||||||
// - diffing
|
// - 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
|
// when AsciiChar is stabilized, use it instead of char everywhere
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
Reference in New Issue
Block a user