fix handling empty files

This commit is contained in:
alice pellerin
2026-03-18 19:18:08 -05:00
parent 0a98df9000
commit a699c0a371
6 changed files with 59 additions and 37 deletions
+26 -16
View File
@@ -139,8 +139,10 @@ impl App {
}
const fn replace(&mut self) {
if !self.contents.is_empty() {
self.partial_action = Some(PartialAction::Replace);
}
}
const fn space(&mut self) {
self.partial_action = Some(PartialAction::Space);
@@ -156,7 +158,7 @@ impl App {
}
const fn move_byte_down(&mut self) {
if self.contents.len() - 1 - self.cursor.head >= BYTES_PER_LINE {
if self.max_contents_index() - self.cursor.head >= BYTES_PER_LINE {
self.cursor.head += BYTES_PER_LINE;
self.cursor.collapse();
@@ -174,7 +176,7 @@ impl App {
}
const fn move_byte_right(&mut self) {
if self.contents.len() - 1 - self.cursor.head >= 1 {
if self.max_contents_index() - self.cursor.head >= 1 {
self.cursor.head += 1;
self.cursor.collapse();
@@ -190,7 +192,7 @@ impl App {
}
const fn extend_byte_down(&mut self) {
if self.contents.len() - 1 - self.cursor.head >= BYTES_PER_LINE {
if self.max_contents_index() - self.cursor.head >= BYTES_PER_LINE {
self.cursor.head += BYTES_PER_LINE;
self.clamp_screen_to_cursor();
}
@@ -204,7 +206,7 @@ impl App {
}
const fn extend_byte_right(&mut self) {
if self.contents.len() - 1 - self.cursor.head >= 1 {
if self.max_contents_index() - self.cursor.head >= 1 {
self.cursor.head += 1;
self.clamp_screen_to_cursor();
}
@@ -235,6 +237,8 @@ impl App {
}
fn scroll_down(&mut self) {
if self.contents.len() <= 5 * BYTES_PER_LINE { return; }
self.scroll_position = min(
self.scroll_position + BYTES_PER_LINE,
self.contents.len() - (5 * BYTES_PER_LINE)
@@ -248,6 +252,8 @@ impl App {
}
fn page_cursor_half_down(&mut self) {
if self.contents.len() <= 5 * BYTES_PER_LINE { return; }
let head_offset = self.cursor.head - self.scroll_position;
let tail_offset = self.cursor.tail - self.scroll_position;
@@ -256,8 +262,8 @@ impl App {
self.contents.len() - (5 * BYTES_PER_LINE)
);
self.cursor.head = (self.scroll_position + head_offset).min(self.contents.len() - 1);
self.cursor.tail = (self.scroll_position + tail_offset).min(self.contents.len() - 1);
self.cursor.head = (self.scroll_position + head_offset).min(self.max_contents_index());
self.cursor.tail = (self.scroll_position + tail_offset).min(self.max_contents_index());
}
fn page_cursor_half_up(&mut self) {
@@ -268,11 +274,13 @@ impl App {
(self.screen_size() / 2).next_multiple_of(BYTES_PER_LINE)
);
self.cursor.head = (self.scroll_position + head_offset).min(self.contents.len() - 1);
self.cursor.tail = (self.scroll_position + tail_offset).min(self.contents.len() - 1);
self.cursor.head = (self.scroll_position + head_offset).min(self.max_contents_index());
self.cursor.tail = (self.scroll_position + tail_offset).min(self.max_contents_index());
}
fn page_down(&mut self) {
if self.contents.len() <= 5 * BYTES_PER_LINE { return; }
self.scroll_position = min(
self.scroll_position + self.screen_size(),
self.contents.len() - (5 * BYTES_PER_LINE)
@@ -288,12 +296,12 @@ impl App {
}
fn move_next_word_start(&mut self) {
self.cursor.move_to_next_word(self.contents.len() - 1);
self.cursor.move_to_next_word(self.max_contents_index());
self.clamp_screen_to_cursor();
}
fn move_next_word_end(&mut self) {
self.cursor.move_to_next_end(self.contents.len() - 1);
self.cursor.move_to_next_end(self.max_contents_index());
self.clamp_screen_to_cursor();
}
@@ -303,12 +311,12 @@ impl App {
}
fn extend_next_word_start(&mut self) {
self.cursor.extend_to_next_word(self.contents.len() - 1);
self.cursor.extend_to_next_word(self.max_contents_index());
self.clamp_screen_to_cursor();
}
fn extend_next_word_end(&mut self) {
self.cursor.extend_to_next_end(self.contents.len() - 1);
self.cursor.extend_to_next_end(self.max_contents_index());
self.clamp_screen_to_cursor();
}
@@ -331,7 +339,7 @@ impl App {
{
self.cursor.head = min(
self.cursor.head + BYTES_PER_LINE,
self.contents.len() - 1
self.max_contents_index()
);
} else {
self.cursor.tail -= self.cursor.tail % BYTES_PER_LINE;
@@ -346,7 +354,7 @@ impl App {
if self.cursor.head.is_multiple_of(BYTES_PER_LINE) &&
(self.cursor.tail % BYTES_PER_LINE == BYTES_PER_LINE - 1 ||
self.cursor.tail == self.contents.len() - 1)
self.cursor.tail == self.max_contents_index())
{
self.cursor.head = self.cursor.head.saturating_sub(BYTES_PER_LINE);
} else {
@@ -356,12 +364,14 @@ impl App {
}
fn delete(&mut self) {
if !self.contents.is_empty() {
self.execute_and_add(
EditAction::Delete {
cursor: self.cursor,
old_data: self.contents[self.cursor.range()].into()
}
);
}
if self.mode == Mode::Select {
self.mode = Mode::Normal;
@@ -369,7 +379,7 @@ impl App {
}
fn undo(&mut self) {
if self.time_traveling == Some(0) || self.edit_history.is_empty() { 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);
@@ -387,7 +397,7 @@ impl App {
}
fn redo(&mut self) {
let Some(previous_date) = self.time_traveling else { return };
let Some(previous_date) = self.time_traveling else { return; };
let current_date = previous_date + 1;
+6 -1
View File
@@ -183,10 +183,15 @@ impl App {
false
}
}
// returns 0 if empty
pub const fn max_contents_index(&self) -> usize {
self.contents.len().saturating_sub(1)
}
}
fn nybble_from_hex(hex: char) -> Option<u8> {
if !hex.is_ascii() { return None }
if !hex.is_ascii() { return None; }
match hex {
'0'..='9' => Some(u8::try_from(hex).unwrap() - u8::try_from('0').unwrap()),
+11 -3
View File
@@ -33,6 +33,10 @@ impl Widget for &App {
let hex_area = Rect::new(area.x, area.y, area.width, area.height - 1);
hex_text.render(hex_area, buf);
if self.contents.is_empty() {
Line::from("empty file").render(area, buf);
}
let status_line_area = Rect::new(area.x, area.bottom() - 1, area.width, 1);
self.render_status_line().render(status_line_area, buf);
@@ -416,14 +420,18 @@ mod extra_statuses {
impl App {
pub fn render_extra_statuses(&self) -> Line<'_> {
#[allow(clippy::cast_precision_loss)]
let percentage = self.cursor.head as f64 / (self.contents.len() - 1) as f64 * 100.0;
let partial_action = self.partial_action
.as_ref()
.map_or("", |partial_action| partial_action.label());
if self.contents.is_empty() {
format!("{partial_action} ").into()
} else {
#[allow(clippy::cast_precision_loss)]
let percentage = self.cursor.head as f64 / self.max_contents_index() as f64 * 100.0;
format!("{partial_action} {percentage:.0}% ").into()
}
}
}
}
+6 -6
View File
@@ -50,7 +50,7 @@ impl Cursor {
}
pub fn move_to_next_word(&mut self, max: usize) {
if self.head == max { return }
if self.head == max { return; }
if self.head.is_multiple_of(4) { // at the beginning of a word
self.head = (self.head + 4).min(max);
@@ -61,7 +61,7 @@ impl Cursor {
}
pub fn move_to_next_end(&mut self, max: usize) {
if self.head == max { return }
if self.head == max { return; }
self.collapse();
if self.head % 4 == 3 { // at the end of a word
@@ -73,7 +73,7 @@ impl Cursor {
}
pub const fn move_to_previous_beginning(&mut self) {
if self.head == 0 { return }
if self.head == 0 { return; }
self.collapse();
if self.head.is_multiple_of(4) { // at the beginning of a word
@@ -85,7 +85,7 @@ impl Cursor {
}
pub fn extend_to_next_word(&mut self, max: usize) {
if self.head == max { return }
if self.head == max { return; }
if self.head.is_multiple_of(4) { // at the beginning of a word
self.head = (self.head + 4).min(max);
@@ -95,7 +95,7 @@ impl Cursor {
}
pub fn extend_to_next_end(&mut self, max: usize) {
if self.head == max { return }
if self.head == max { return; }
if self.head % 4 == 3 { // at the end of a word
self.head = (self.head + 4).min(max);
@@ -105,7 +105,7 @@ impl Cursor {
}
pub const fn extend_to_previous_beginning(&mut self) {
if self.head == 0 { return }
if self.head == 0 { return; }
if self.head.is_multiple_of(4) { // at the beginning of a word
self.head -= 4;
+1 -1
View File
@@ -66,7 +66,7 @@ impl App {
fn delete_at(&mut self, cursor: Cursor) {
self.contents.drain(cursor.range());
self.cursor.head = min(min(cursor.head, cursor.tail), self.contents.len() - 1);
self.cursor.head = min(min(cursor.head, cursor.tail), self.max_contents_index());
self.cursor.collapse();
}
+1 -2
View File
@@ -50,8 +50,7 @@ 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..?
// TODO: quit should confirm if unsaved changes
// when AsciiChar is stabilized, use it instead of char everywhere