From: Michael D. Lowis Date: Wed, 7 Dec 2016 20:36:10 +0000 (-0500) Subject: added tests for text editing keyboard shortcuts X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=e97d801c8fd47012f7dc5f717dc929cbacd2e28f;p=projs%2Ftide.git added tests for text editing keyboard shortcuts --- diff --git a/TODO.md b/TODO.md index ae3df7d..18124f2 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,6 @@ # Implementation Tweaks and Bug Fixes +* investigate weird behavior when editing CRLF files and copy-pasting * Expand tabs setting should be disabled if opened file contains tabs * Add tag for ctags lookup and line number jump * add a shortcut to autocomplete ctag @@ -14,6 +15,10 @@ * check for file changes when window regains focus * check for file changes on save * backspace should delete indent if preceded by whitespace +* indenting with a reverse selection expands the selection the wrong way +* add command line flags to toggle options (Tabs, Indent, etc..) +* shift+click to extend selection +* drag with middle and right mouse buttons causes infinite loops # Auxillary Programs diff --git a/config.mk b/config.mk index 85d96ed..1002217 100644 --- a/config.mk +++ b/config.mk @@ -24,5 +24,5 @@ LIBS += -L/usr/X11/lib INCS += -I/usr/include/freetype2 # Gcov Coverage -#CFLAGS += --coverage -#LDFLAGS += --coverage +CFLAGS += --coverage +LDFLAGS += --coverage diff --git a/inc/edit.h b/inc/edit.h index e3da9fa..cef36be 100644 --- a/inc/edit.h +++ b/inc/edit.h @@ -143,6 +143,7 @@ void view_selext(View* view, size_t row, size_t col); void view_selword(View* view, size_t row, size_t col); void view_selprev(View* view); void view_select(View* view, size_t row, size_t col); +size_t view_selsize(View* view); char* view_fetch(View* view, size_t row, size_t col); void view_find(View* view, size_t row, size_t col); void view_findstr(View* view, char* str); diff --git a/libedit/view.c b/libedit/view.c index 97b2324..c2da611 100644 --- a/libedit/view.c +++ b/libedit/view.c @@ -360,6 +360,10 @@ void view_select(View* view, size_t row, size_t col) { view->selection = sel; } +size_t view_selsize(View* view) { + return num_selected(view->selection); +} + char* view_fetch(View* view, size_t row, size_t col) { char* str = NULL; size_t off = getoffset(view, row, col); diff --git a/tests/xedit.c b/tests/xedit.c index 8abfffe..89673c3 100644 --- a/tests/xedit.c +++ b/tests/xedit.c @@ -14,6 +14,11 @@ void setup_view(enum RegionId id, char* text, unsigned cursor) { getsel(id)->col = buf_getcol(getbuf(id), getsel(id)->end); } +void send_keys(int mods, Rune key) { + Mods = mods; + key_handler(mods, key); +} + /* Stubbed Functions *****************************************************************************/ bool x11_keymodsset(int mask) { @@ -39,133 +44,332 @@ TEST_SUITE(XeditTests) { *************************************************************************/ TEST(left should do nothing for empty buffer) { setup_view(EDIT, "", 0); - key_handler(ModNone, KEY_LEFT); + send_keys(ModNone, KEY_LEFT); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + } + + TEST(ctrl+left should do nothing for empty buffer) { + setup_view(EDIT, "", 0); + send_keys(ModCtrl, KEY_LEFT); CHECK(getsel(EDIT)->beg == 0); CHECK(getsel(EDIT)->end == 0); } TEST(left should do nothing at beginning of buffer) { setup_view(EDIT, "AB", 0); - key_handler(ModNone, KEY_LEFT); + send_keys(ModNone, KEY_LEFT); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + } + + TEST(ctrl+left should do nothing at beginning of buffer) { + setup_view(EDIT, "AB", 0); + send_keys(ModCtrl, KEY_LEFT); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + } + + TEST(ctrl+left should move left by one word) { + setup_view(EDIT, "AB CD", 3); + send_keys(ModCtrl, KEY_LEFT); CHECK(getsel(EDIT)->beg == 0); CHECK(getsel(EDIT)->end == 0); } TEST(left should move left by one rune) { setup_view(EDIT, "AB", 1); - key_handler(ModNone, KEY_LEFT); + send_keys(ModNone, KEY_LEFT); CHECK(getsel(EDIT)->beg == 0); CHECK(getsel(EDIT)->end == 0); } TEST(right should do nothing for empty buffer) { setup_view(EDIT, "", 0); - key_handler(ModNone, KEY_RIGHT); + send_keys(ModNone, KEY_RIGHT); CHECK(getsel(EDIT)->beg == 0); CHECK(getsel(EDIT)->end == 0); } - TEST(right should do nothing at end of buffer) { + TEST(right should do nothing for empty buffer) { + setup_view(EDIT, "", 0); + send_keys(ModCtrl, KEY_RIGHT); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + } + + TEST(ctrl+right should do nothing at end of buffer) { + setup_view(EDIT, "AB", 2); + send_keys(ModNone, KEY_RIGHT); + CHECK(getsel(EDIT)->beg == 2); + CHECK(getsel(EDIT)->end == 2); + } + + TEST(ctrl+right should do nothing at end of buffer) { setup_view(EDIT, "AB", 2); - key_handler(ModNone, KEY_RIGHT); + send_keys(ModCtrl, KEY_RIGHT); CHECK(getsel(EDIT)->beg == 2); CHECK(getsel(EDIT)->end == 2); } + TEST(ctrl+right should move right by one word) { + setup_view(EDIT, "AB CD", 0); + send_keys(ModCtrl, KEY_RIGHT); + CHECK(getsel(EDIT)->beg == 3); + CHECK(getsel(EDIT)->end == 3); + } + TEST(right should move right by one rune) { setup_view(EDIT, "AB", 1); - key_handler(ModNone, KEY_RIGHT); + send_keys(ModNone, KEY_RIGHT); CHECK(getsel(EDIT)->beg == 2); CHECK(getsel(EDIT)->end == 2); } TEST(up should do nothing for empty buffer) { setup_view(EDIT, "", 0); - key_handler(ModNone, KEY_UP); + send_keys(ModNone, KEY_UP); CHECK(getsel(EDIT)->beg == 0); CHECK(getsel(EDIT)->end == 0); } TEST(up should move cursor up one line) { setup_view(EDIT, "AB\nCD", 4); - key_handler(ModNone, KEY_UP); + send_keys(ModNone, KEY_UP); CHECK(getsel(EDIT)->beg == 1); CHECK(getsel(EDIT)->end == 1); } TEST(up should do nothing for first line) { setup_view(EDIT, "AB\nCD", 1); - key_handler(ModNone, KEY_UP); + send_keys(ModNone, KEY_UP); CHECK(getsel(EDIT)->beg == 1); CHECK(getsel(EDIT)->end == 1); } TEST(down should do nothing for empty buffer) { setup_view(EDIT, "", 0); - key_handler(ModNone, KEY_DOWN); + send_keys(ModNone, KEY_DOWN); CHECK(getsel(EDIT)->beg == 0); CHECK(getsel(EDIT)->end == 0); } TEST(down should move down one line) { setup_view(EDIT, "AB\nCD", 1); - key_handler(ModNone, KEY_DOWN); + send_keys(ModNone, KEY_DOWN); CHECK(getsel(EDIT)->beg == 4); CHECK(getsel(EDIT)->end == 4); } TEST(down should do nothing on last line) { setup_view(EDIT, "AB\nCD", 4); - key_handler(ModNone, KEY_DOWN); + send_keys(ModNone, KEY_DOWN); CHECK(getsel(EDIT)->beg == 4); CHECK(getsel(EDIT)->end == 4); } TEST(home should do nothing for empty buffer) { setup_view(EDIT, "", 0); - key_handler(ModNone, KEY_HOME); + send_keys(ModNone, KEY_HOME); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + } + + TEST(ctrl+home should do nothing for empty buffer) { + setup_view(EDIT, "", 0); + send_keys(ModCtrl, KEY_HOME); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + } + + TEST(ctrl+home should move to beginning of buffer) { + setup_view(EDIT, "ABCD", 4); + send_keys(ModCtrl, KEY_HOME); CHECK(getsel(EDIT)->beg == 0); CHECK(getsel(EDIT)->end == 0); } TEST(home should move to beginning of indented content) { setup_view(EDIT, " ABCD", 7); - key_handler(ModNone, KEY_HOME); + send_keys(ModNone, KEY_HOME); CHECK(getsel(EDIT)->beg == 4); CHECK(getsel(EDIT)->end == 4); } TEST(home should move to beginning of line if at beginning of indented content) { setup_view(EDIT, " ABCD", 7); - key_handler(ModNone, KEY_HOME); + send_keys(ModNone, KEY_HOME); CHECK(getsel(EDIT)->beg == 4); CHECK(getsel(EDIT)->end == 4); } TEST(home should move to beginning of indented content when at beginning of line) { setup_view(EDIT, " ABCD", 0); - key_handler(ModNone, KEY_HOME); + send_keys(ModNone, KEY_HOME); CHECK(getsel(EDIT)->beg == 4); CHECK(getsel(EDIT)->end == 4); } TEST(end should do nothing for empty buffer) { setup_view(EDIT, "", 0); - key_handler(ModNone, KEY_END); + send_keys(ModNone, KEY_END); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + } + + TEST(ctrl+end should do nothing for empty buffer) { + setup_view(EDIT, "", 0); + send_keys(ModCtrl, KEY_END); CHECK(getsel(EDIT)->beg == 0); CHECK(getsel(EDIT)->end == 0); } + TEST(ctrl+end should move to end of buffer) { + setup_view(EDIT, "ABCD", 0); + send_keys(ModCtrl, KEY_END); + CHECK(getsel(EDIT)->beg == 4); + CHECK(getsel(EDIT)->end == 4); + } + TEST(end should do nothing at end of line) { setup_view(EDIT, "AB\nCD", 2); - key_handler(ModNone, KEY_END); + send_keys(ModNone, KEY_END); CHECK(getsel(EDIT)->beg == 2); CHECK(getsel(EDIT)->end == 2); } TEST(end should move to end of line) { setup_view(EDIT, "AB\nCD", 0); - key_handler(ModNone, KEY_END); + send_keys(ModNone, KEY_END); + CHECK(getsel(EDIT)->beg == 2); + CHECK(getsel(EDIT)->end == 2); + } + + /* Key Handling - Unix Standard Shortcuts + *************************************************************************/ + TEST(ctrl+u should do nothing for empty buffer) { + setup_view(EDIT, "", 0); + send_keys(ModCtrl, 'u'); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + } + + TEST(ctrl+u should delete to beginning of line) { + setup_view(EDIT, "\nABC\n", 2); + send_keys(ModCtrl, 'u'); + CHECK(getsel(EDIT)->beg == 1); + CHECK(getsel(EDIT)->end == 1); + CHECK(buf_end(getbuf(EDIT)) == 4); + } + + TEST(ctrl+u should delete entire lines contents) { + setup_view(EDIT, "\nABC\n", 3); + send_keys(ModCtrl, 'u'); + CHECK(getsel(EDIT)->beg == 1); + CHECK(getsel(EDIT)->end == 1); + CHECK(buf_end(getbuf(EDIT)) == 3); + } + + TEST(ctrl+k should do nothing for empty buffer) { + setup_view(EDIT, "", 0); + send_keys(ModCtrl, 'k'); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + CHECK(buf_end(getbuf(EDIT)) == 0); + } + + TEST(ctrl+k should delete to end of line) { + setup_view(EDIT, "\nABC\n", 2); + send_keys(ModCtrl, 'k'); + CHECK(getsel(EDIT)->beg == 2); + CHECK(getsel(EDIT)->end == 2); + CHECK(buf_end(getbuf(EDIT)) == 3); + } + + TEST(ctrl+k should delete entire lines contents) { + setup_view(EDIT, "\nABC\n", 1); + send_keys(ModCtrl, 'k'); + CHECK(getsel(EDIT)->beg == 1); + CHECK(getsel(EDIT)->end == 1); + CHECK(buf_end(getbuf(EDIT)) == 2); + } + + TEST(ctrl+w should do nothing for empty buffer) { + setup_view(EDIT, "", 0); + send_keys(ModCtrl, 'w'); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + CHECK(buf_end(getbuf(EDIT)) == 0); + } + + TEST(ctrl+w should delete previous word) { + setup_view(EDIT, "abc def", 4); + send_keys(ModCtrl, 'w'); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + CHECK(buf_end(getbuf(EDIT)) == 3); + } + + TEST(ctrl+h should do nothing for empty buffer) { + setup_view(EDIT, "", 0); + send_keys(ModCtrl, 'h'); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + } + + TEST(ctrl+h should delete previous character) { + setup_view(EDIT, "AB", 1); + send_keys(ModCtrl, 'h'); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + CHECK(buf_end(getbuf(EDIT)) == 1); + } + + TEST(ctrl+a should do nothing for empty buffer) { + setup_view(EDIT, "", 0); + send_keys(ModCtrl, 'a'); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + } + + TEST(ctrl+a should move to beginning of indented content) { + setup_view(EDIT, " ABCD", 7); + send_keys(ModCtrl, 'a'); + CHECK(getsel(EDIT)->beg == 4); + CHECK(getsel(EDIT)->end == 4); + } + + TEST(ctrl+a should move to beginning of line if at beginning of indented content) { + setup_view(EDIT, " ABCD", 7); + send_keys(ModCtrl, 'a'); + CHECK(getsel(EDIT)->beg == 4); + CHECK(getsel(EDIT)->end == 4); + } + + TEST(ctrl+a should move to beginning of indented content when at beginning of line) { + setup_view(EDIT, " ABCD", 0); + send_keys(ModCtrl, 'a'); + CHECK(getsel(EDIT)->beg == 4); + CHECK(getsel(EDIT)->end == 4); + } + + TEST(ctrl+e should do nothing for empty buffer) { + setup_view(EDIT, "", 0); + send_keys(ModCtrl, 'e'); + CHECK(getsel(EDIT)->beg == 0); + CHECK(getsel(EDIT)->end == 0); + } + + TEST(ctrl+e should do nothing at end of line) { + setup_view(EDIT, "AB\nCD", 2); + send_keys(ModCtrl, 'e'); + CHECK(getsel(EDIT)->beg == 2); + CHECK(getsel(EDIT)->end == 2); + } + + TEST(ctrl+e should move to end of line) { + setup_view(EDIT, "AB\nCD", 0); + send_keys(ModCtrl, 'e'); CHECK(getsel(EDIT)->beg == 2); CHECK(getsel(EDIT)->end == 2); } diff --git a/xedit.c b/xedit.c index e7b7648..a9575f5 100644 --- a/xedit.c +++ b/xedit.c @@ -26,6 +26,7 @@ static void redraw(int width, int height); // UI Callbacks static void delete(void); static void del_to_bol(void); +static void del_to_eol(void); static void del_to_bow(void); static void backspace(void); static void cursor_bol(void); @@ -116,14 +117,17 @@ void (*MouseActs[MOUSE_BTN_COUNT])(enum RegionId id, size_t count, size_t row, s }; static KeyBinding Bindings[] = { - /* Function Keys */ - //{ KEY_CTRL_F1, welcome }, - //{ KEY_CTRL_F2, ctags_scan }, - //{ KEY_CTRL_F11, fullscreen }, - + /* Cursor Movements */ + { ModAny, KEY_HOME, cursor_home }, + { ModAny, KEY_END, cursor_end }, + { ModAny, KEY_UP, cursor_up }, + { ModAny, KEY_DOWN, cursor_dn }, + { ModAny, KEY_LEFT, cursor_left }, + { ModAny, KEY_RIGHT, cursor_right }, + /* Standard Unix Shortcuts */ { ModCtrl, 'u', del_to_bol }, - //{ ModCtrl, 'k', del_to_eol }, + { ModCtrl, 'k', del_to_eol }, { ModCtrl, 'w', del_to_bow }, { ModCtrl, 'h', backspace }, { ModCtrl, 'a', cursor_bol }, @@ -147,14 +151,6 @@ static KeyBinding Bindings[] = { { ModAny, KEY_DELETE, delete }, { ModAny, KEY_BACKSPACE, backspace }, - /* Cursor Movements */ - { ModAny, KEY_HOME, cursor_home }, - { ModAny, KEY_END, cursor_end }, - { ModAny, KEY_UP, cursor_up }, - { ModAny, KEY_DOWN, cursor_dn }, - { ModAny, KEY_LEFT, cursor_left }, - { ModAny, KEY_RIGHT, cursor_right }, - /* Implementation Specific */ { ModNone, KEY_ESCAPE, select_prev }, { ModCtrl, 't', change_focus }, @@ -388,12 +384,20 @@ static void delete(void) { static void del_to_bol(void) { view_bol(currview(), true); - delete(); + if (view_selsize(currview()) > 0) + delete(); +} + +static void del_to_eol(void) { + view_eol(currview(), true); + if (view_selsize(currview()) > 0) + delete(); } static void del_to_bow(void) { view_byword(currview(), LEFT, true); - delete(); + if (view_selsize(currview()) > 0) + delete(); } static void backspace(void) {