diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3020216..af6393f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ moves back that amount of space
* Added `;f` command to find a given string further on in the buffer
* Iris now handles horizontal scrolling when a line is longer than the screen
* Allow for specifying file to save to from command bar
+* `e` key now moves cursor to end of the current word
* Resolved issue with iris crashing after opening an existing file with 0 bytes
* Resolved issue where filename isn't centered in the status bar
diff --git a/README.md b/README.md
index 7d1eed4..f6527c3 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,8 @@ perform the following actions (alphabetically ordered):
| b | Move cursor back one word |
| dl | Delete the current line |
| dw | Delete the current word |
-| f | Find next entered char ahead in file |
+| e | Find next entered char ahead in file |
+| f | Move cursor to end of current word |
| F | Find next entered char back in file |
| g | Go to top of file |
| G | Go to bottom of file |
diff --git a/milestones.md b/milestones.md
index 81b7307..092b579 100644
--- a/milestones.md
+++ b/milestones.md
@@ -72,5 +72,5 @@
* [x] `w` (delete word)
* [x] `J`
* [x] `K`
-* [ ] `e`
+* [x] `e`
* [ ] `>` / `<` (indentation)
diff --git a/src/action.h b/src/action.h
index 28c6973..67a74f1 100644
--- a/src/action.h
+++ b/src/action.h
@@ -19,6 +19,7 @@ enum class ActionType {
DelCurrentLine,
DelCurrentWord,
EndOfLine,
+ JumpEndOfWord,
JumpNextPara,
JumpPrevPara,
JumpNextWord,
@@ -170,6 +171,21 @@ template
v->cursor_end_of_line();
} break;
+ case ActionType::JumpEndOfWord: {
+ auto logger = spdlog::get("basic_logger");
+ if (logger != nullptr) {
+ logger->info("Action called: JumpEndOfWord");
+ }
+
+ std::optional count = v->get_active_model()->end_of_word_pos();
+ if (count.has_value()) {
+ for (int i = 0; i < count.value(); i++) {
+ v->cursor_right();
+ }
+ }
+
+ } break;
+
case ActionType::JumpNextPara: {
auto logger = spdlog::get("basic_logger");
if (logger != nullptr) {
diff --git a/src/controller.cpp b/src/controller.cpp
index 81f6c67..3f5c8a9 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -211,6 +211,10 @@ void Controller::start_action_engine() {
continue;
}
+ // Go to end of current word
+ } else if (k.value() == rawterm::Key('e')) {
+ parse_action(&view, Action {ActionType::JumpEndOfWord});
+
// find forward
} else if (k.value() == rawterm::Key('f')) {
auto k2 = rawterm::wait_for_input();
diff --git a/src/model.cpp b/src/model.cpp
index ebd8527..e19c536 100644
--- a/src/model.cpp
+++ b/src/model.cpp
@@ -113,7 +113,7 @@ void Model::insert(const char c) {
return (idx < static_cast(buf.size()) && idx >= 0);
}
-// Word (noun) - a sequence of characters that match regex A-Za-z
+// Word (noun) - a sequence of characters that match regex A-Za-z0-9
[[nodiscard]] std::optional Model::next_word_pos() {
if (current_char == buf.at(current_line).size() - 1) {
return {};
@@ -216,6 +216,34 @@ void Model::insert(const char c) {
return distance;
}
+[[nodiscard]] std::optional Model::end_of_word_pos() {
+ if (current_char == buf.at(current_line).size() - 1) {
+ return {};
+ } else if (buf.at(current_line).empty()) {
+ return {};
+ }
+
+ uint incrementer = 0;
+ std::string_view line_frag =
+ std::string_view(buf.at(current_line)).substr(current_char, buf.at(current_line).size());
+
+ // If the _next_ char is a space, we want to go past that
+ if (line_frag.at(1) == ' ') {
+ incrementer += 2;
+ };
+
+ // go to end of current "word"
+ while (is_letter(line_frag.at(incrementer))) {
+ incrementer++;
+
+ if (incrementer == line_frag.size() - 1) {
+ return incrementer;
+ }
+ }
+
+ return --incrementer;
+}
+
void Model::replace_char(const char c) {
if (buf.at(current_line).empty()) {
buf.at(current_line).push_back(c);
diff --git a/src/model.h b/src/model.h
index 2f538f5..d07f2f8 100644
--- a/src/model.h
+++ b/src/model.h
@@ -54,6 +54,7 @@ struct Model {
[[nodiscard]] std::optional prev_word_pos();
[[nodiscard]] std::optional next_para_pos();
[[nodiscard]] std::optional prev_para_pos();
+ [[nodiscard]] std::optional end_of_word_pos();
void replace_char(const char);
void toggle_case();
[[nodiscard]] std::optional find_next(const char);
diff --git a/src/text_io.cpp b/src/text_io.cpp
index efc488b..e18ac71 100644
--- a/src/text_io.cpp
+++ b/src/text_io.cpp
@@ -103,11 +103,13 @@ void rtrim(std::string& str) {
return result;
}
+// TODO: Investigate if symbols should count here
[[nodiscard]] bool is_letter(const char& c) {
- std::array alphabet = {
- 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
- 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
- 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
+ std::array alphabet = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F',
+ 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+ 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
return std::find(alphabet.begin(), alphabet.end(), c) != alphabet.end();
}
diff --git a/tests/integration/normal_mode_test.py b/tests/integration/normal_mode_test.py
index c998d22..bffc955 100644
--- a/tests/integration/normal_mode_test.py
+++ b/tests/integration/normal_mode_test.py
@@ -521,3 +521,14 @@ def test_dw_key(r: TmuxRunner):
r.type_str("dw") # delete word
assert "Lorem ipsum" not in r.lines()[0]
+
+
+@setup("tests/fixture/test_file_1.txt")
+def test_e_key(r: TmuxRunner):
+ assert r.statusbar_parts()[-1] == "1:1"
+
+ r.press("e")
+ assert r.statusbar_parts()[-1] == "1:4"
+
+ r.press("e")
+ assert r.statusbar_parts()[-1] == "1:7"
diff --git a/tests/model_test.cpp b/tests/model_test.cpp
index 79a566e..067e3f1 100644
--- a/tests/model_test.cpp
+++ b/tests/model_test.cpp
@@ -264,6 +264,41 @@ TEST_CASE("prev_para_pos", "[model]") {
REQUIRE(opt.value() == 2);
}
+TEST_CASE("end_of_word_pos", "[model]") {
+ auto m = Model({"line one", "", "line five"}, "");
+
+ m.current_char = 7;
+ REQUIRE(m.end_of_word_pos() == std::nullopt);
+
+ m.current_char = 0;
+ m.current_line = 1;
+ REQUIRE(m.end_of_word_pos() == std::nullopt);
+
+ m.current_line = 0;
+ REQUIRE(m.end_of_word_pos().has_value());
+ REQUIRE(m.end_of_word_pos().value() == 3);
+
+ m.current_char = 3;
+ REQUIRE(m.end_of_word_pos().has_value());
+ REQUIRE(m.end_of_word_pos().value() == 4);
+}
+
+TEST_CASE("replace_char", "[model]") {
+ auto m = Model({"line one", "", "line five"}, "");
+
+ m.current_line = 1;
+ m.replace_char('c');
+ REQUIRE(m.buf.at(1).size() == 1);
+ REQUIRE(m.buf.at(1) == "c");
+
+ m.current_line = 2;
+ m.current_char = 4;
+ m.replace_char('_');
+ REQUIRE(m.buf.at(2).size() == 9);
+ REQUIRE(m.buf.at(2).at(4) == '_');
+ REQUIRE(m.buf.at(2) == "line_five");
+}
+
TEST_CASE("toggle_case", "[model]") {
auto m = Model({"line one", "line two", "line three", "", "line four", "line five"}, "");