From e2f4b5bd6e9d8cba940074e9959d6cebf496132a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Andr=C3=A9=20Tanner?= Date: Tue, 29 Mar 2016 09:00:55 +0200 Subject: vis: implement selection rotation In visual mode + and - will rotate the selection count times to the right or left respectively. If there exists a line containing multiple selections then the rotation happens within each line. Otherwise if each line contains at most one selection the rotation is performed among all existing selections. --- README.md | 2 ++ config.def.h | 6 +++-- main.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index da9ccf3..c8b0ddb 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,8 @@ Operators can be forced to work line wise by specifying `V`. Ctrl-P remove primary cursor Ctrl-U make the count previous cursor primary Ctrl-D make the count next cursor primary + + rotates selections rightwards count times + - rotates selections leftwards count times Esc clear all selections, switch to normal mode In insert/replace mode diff --git a/config.def.h b/config.def.h index 0b91be8..67e4c73 100644 --- a/config.def.h +++ b/config.def.h @@ -280,8 +280,10 @@ static const KeyBinding bindings_visual[] = { { "o", ACTION(SELECTION_FLIP) }, { ">", ALIAS("gv") }, { "<", ALIAS("gv") }, - { "" , ACTION(CURSORS_ALIGN_INDENT_LEFT) }, - { "" , ACTION(CURSORS_ALIGN_INDENT_RIGHT) }, + { "", ACTION(CURSORS_ALIGN_INDENT_LEFT) }, + { "", ACTION(CURSORS_ALIGN_INDENT_RIGHT) }, + { "-", ACTION(SELECTIONS_ROTATE_LEFT) }, + { "+", ACTION(SELECTIONS_ROTATE_RIGHT) }, { 0 /* empty last element, array terminator */ }, }; diff --git a/main.c b/main.c index a5fc2d3..6e8e245 100644 --- a/main.c +++ b/main.c @@ -12,6 +12,7 @@ #include "text-objects.h" #include "util.h" #include "libutf.h" +#include "array.h" #define PAGE INT_MAX #define PAGE_HALF (INT_MAX-1) @@ -56,6 +57,8 @@ static const char *cursors_select(Vis*, const char *keys, const Arg *arg); static const char *cursors_select_next(Vis*, const char *keys, const Arg *arg); /* clear current selection but select next match */ static const char *cursors_select_skip(Vis*, const char *keys, const Arg *arg); +/* rotate selection content count times left (arg->i < 0) or right (arg->i > 0) */ +static const char *selections_rotate(Vis*, const char *keys, const Arg *arg); /* adjust current used count according to keys */ static const char *count(Vis*, const char *keys, const Arg *arg); /* move to the count-th line or if not given either to the first (arg->i < 0) @@ -251,6 +254,8 @@ enum { VIS_ACTION_CURSORS_REMOVE_LAST, VIS_ACTION_CURSORS_PREV, VIS_ACTION_CURSORS_NEXT, + VIS_ACTION_SELECTIONS_ROTATE_LEFT, + VIS_ACTION_SELECTIONS_ROTATE_RIGHT, VIS_ACTION_TEXT_OBJECT_WORD_OUTER, VIS_ACTION_TEXT_OBJECT_WORD_INNER, VIS_ACTION_TEXT_OBJECT_LONGWORD_OUTER, @@ -961,6 +966,16 @@ static const KeyAction vis_action[] = { "Move to the next cursor", cursors_navigate, { .i = +PAGE_HALF } }, + [VIS_ACTION_SELECTIONS_ROTATE_LEFT] = { + "selections-rotate-left", + "Rotate selections left", + selections_rotate, { .i = -1 } + }, + [VIS_ACTION_SELECTIONS_ROTATE_RIGHT] = { + "selections-rotate-right", + "Rotate selections right", + selections_rotate, { .i = +1 } + }, [VIS_ACTION_TEXT_OBJECT_WORD_OUTER] = { "text-object-word-outer", "A word leading and trailing whitespace included", @@ -1405,6 +1420,70 @@ static const char *cursors_navigate(Vis *vis, const char *keys, const Arg *arg) return keys; } +static const char *selections_rotate(Vis *vis, const char *keys, const Arg *arg) { + + typedef struct { + Cursor *cursor; + char *data; + size_t len; + } Rotate; + + Array arr; + Text *txt = vis_text(vis); + View *view = vis_view(vis); + int columns = view_cursors_column_count(view); + int selections = columns == 1 ? view_cursors_count(view) : columns; + int count = vis_count_get_default(vis, 1); + array_init_sized(&arr, sizeof(Rotate)); + if (!array_reserve(&arr, selections)) + return keys; + size_t line_prev = 0; + + for (Cursor *c = view_cursors(view), *next; c; c = next) { + next = view_cursors_next(c); + + Filerange sel = view_cursors_selection_get(c); + Rotate rot; + rot.cursor = c; + rot.len = text_range_size(&sel); + if ((rot.data = malloc(rot.len))) + rot.len = text_bytes_get(txt, sel.start, rot.len, rot.data); + else + rot.len = 0; + array_add(&arr, &rot); + + size_t pos = view_cursors_pos(c); + size_t line = text_lineno_by_pos(txt, pos); + if (!next || (columns > 1 && line_prev && line != line_prev)) { + size_t len = array_length(&arr); + size_t off = arg->i > 0 ? count % len : len - (count % len); + for (size_t i = 0; i < len; i++) { + size_t j = (i + off) % len; + Rotate *oldrot = array_get(&arr, i); + Rotate *newrot = array_get(&arr, j); + if (!oldrot || !newrot || oldrot == newrot) + continue; + Filerange newsel = view_cursors_selection_get(newrot->cursor); + if (!text_range_valid(&newsel)) + continue; + if (!text_delete_range(txt, &newsel)) + continue; + if (!text_insert(txt, newsel.start, oldrot->data, oldrot->len)) + continue; + newsel.end = newsel.start + oldrot->len; + view_cursors_selection_set(newrot->cursor, &newsel); + view_cursors_selection_sync(newrot->cursor); + free(oldrot->data); + } + array_clear(&arr); + } + line_prev = line; + } + + vis_count_set(vis, VIS_COUNT_UNKNOWN); + return keys; +} + static const char *replace(Vis *vis, const char *keys, const Arg *arg) { if (!keys[0]) return NULL; -- cgit v1.2.3