From 18e3d743682c6c8fe9eecef7265c781b8a7ca5e8 Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Tue, 24 Mar 2026 11:49:27 +0000 Subject: [PATCH] Resolve issue when user tries to swap the top or bottom line with a line out of scope --- CHANGELOG.md | 2 ++ src/action.h | 8 ++++---- src/model.cpp | 8 ++++++-- src/model.h | 4 ++-- tests/integration/normal_mode_test.py | 21 +++++++++++++++++++++ 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7028c74..14a72e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ### main / head +* Resolve crash when user tries to switch top or bottom line outside of scope + ### v0.0.3 | 17/Mar/2026 * Use `;e` to open a different file from within iris * Keyboard commands `tt`, `tn`, `tp` to create and switch between tabs diff --git a/src/action.h b/src/action.h index 2bed2d2..7f7540f 100644 --- a/src/action.h +++ b/src/action.h @@ -249,8 +249,8 @@ template auto logger = spdlog::get("basic_logger"); if (logger != nullptr) { logger->info("Action called: MoveLineDown"); } - v->get_active_model()->move_line_down(); - v->cursor_down(); + bool actionable = v->get_active_model()->move_line_down(); + if (actionable) { v->cursor_down(); } return {}; } break; @@ -258,8 +258,8 @@ template auto logger = spdlog::get("basic_logger"); if (logger != nullptr) { logger->info("Action called: MoveLineUp"); } - v->get_active_model()->move_line_up(); - v->cursor_up(); + bool actionable = v->get_active_model()->move_line_up(); + if (actionable) { v->cursor_up(); } return {}; } break; diff --git a/src/model.cpp b/src/model.cpp index 55c960d..58e2d08 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -420,12 +420,16 @@ void Model::toggle_case() { return false; } -void Model::move_line_down() { +[[nodiscard]] bool Model::move_line_down() { + if (current_line == buf.size() - 1) { return false; } std::iter_swap(buf.begin() + current_line, buf.begin() + current_line + 1); + return true; } -void Model::move_line_up() { +[[nodiscard]] bool Model::move_line_up() { + if (!current_line) { return false; } std::iter_swap(buf.begin() + current_line, buf.begin() + current_line - 1); + return true; } void Model::set_read_only(std::string_view file) { diff --git a/src/model.h b/src/model.h index d07f2f8..b5d7dac 100644 --- a/src/model.h +++ b/src/model.h @@ -62,8 +62,8 @@ struct Model { [[nodiscard]] bool undo(const int); [[nodiscard]] char get_current_char() const; [[nodiscard]] bool redo(const int); - void move_line_down(); - void move_line_up(); + [[nodiscard]] bool move_line_down(); + [[nodiscard]] bool move_line_up(); void set_read_only(std::string_view); void delete_current_line(); [[nodiscard]] const WordPos current_word() const; diff --git a/tests/integration/normal_mode_test.py b/tests/integration/normal_mode_test.py index 4001ad9..8cfe86c 100644 --- a/tests/integration/normal_mode_test.py +++ b/tests/integration/normal_mode_test.py @@ -430,6 +430,27 @@ def test_upper_k_key(r: TmuxRunner): assert "here is a newline and a tab" in r.lines()[2] +@setup("tests/fixture/test_file_1.txt") +def test_upper_j_key_end_of_file(r: TmuxRunner): + r.press("G") + r.press("J") + + # Check nothing has changed + # TODO: replace with r.MODIFIED_MARKER + assert "[X]" not in r.statusbar_parts() + assert len(r.statusbar_parts()) + + +@setup("tests/fixture/test_file_1.txt") +def test_upper_k_key_start_of_file(r: TmuxRunner): + r.press("K") + + # Check nothing has changed + # TODO: replace with r.MODIFIED_MARKER + assert "[X]" not in r.statusbar_parts() + assert len(r.statusbar_parts()) + + @setup("tests/fixture/test_file_1.txt") def test_tt_key(r: TmuxRunner): r.type_str("tt")