diff --git a/CHANGELOG.md b/CHANGELOG.md index 61759b2..8310e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Disabled continuous deployment in GHA. See `.github/workflows/build_artifact.yml` for details +* Resolve crash when user tries to switch top or bottom line outside of scope * Resolved crash when the user tries to `delete word` past the end of a line ### v0.0.3 | 17/Mar/2026 diff --git a/src/action.h b/src/action.h index e6a9455..550e2bc 100644 --- a/src/action.h +++ b/src/action.h @@ -267,8 +267,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; @@ -276,8 +276,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 6d22161..7656158 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -424,12 +424,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 4d705ca..2074183 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 std::optional current_word() const; diff --git a/tests/integration/normal_mode_test.py b/tests/integration/normal_mode_test.py index 45a184d..3f1f455 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")