static Filerange text_object_bracket(Text *txt, size_t pos, char type) { char c, open, close; int opened = 1, closed = 1; Filerange r = text_range_empty(); switch (type) { case '(': case ')': open = '('; close = ')'; break; case '{': case '}': open = '{'; close = '}'; break; case '[': case ']': open = '['; close = ']'; break; case '<': case '>': open = '<'; close = '>'; break; case '"': open = '"'; close = '"'; break; case '`': open = '`'; close = '`'; break; case '\'': open = '\''; close = '\''; break; default: return r; } Iterator it = text_iterator_get(txt, pos); if (open == close && text_iterator_byte_get(&it, &c) && (c == '"' || c == '`' || c == '\'')) { size_t match = text_bracket_match(txt, pos); r.start = MIN(pos, match) + 1; r.end = MAX(pos, match); return r; } while (text_iterator_byte_get(&it, &c)) { if (c == open && --opened == 0) { r.start = it.pos + 1; break; } else if (c == close && it.pos != pos) { opened++; } text_iterator_byte_prev(&it, NULL); } it = text_iterator_get(txt, pos); while (text_iterator_byte_get(&it, &c)) { if (c == close && --closed == 0) { r.end = it.pos; break; } else if (c == open && it.pos != pos) { closed++; } text_iterator_byte_next(&it, NULL); } if (!text_range_valid(&r)) return text_range_empty(); return r; }
enum SamError sam_cmd(Vis *vis, const char *s) { enum SamError err = SAM_ERR_OK; if (!s) return err; Command *cmd = sam_parse(vis, s, &err); if (!cmd) { if (err == SAM_ERR_OK) err = SAM_ERR_MEMORY; return err; } Filerange range = text_range_empty(); sam_execute(vis, vis->win, cmd, NULL, &range); if (vis->win) { bool completed = true; for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) { Filerange sel = view_cursors_selection_get(c); if (text_range_valid(&sel)) { completed = false; break; } } vis_mode_switch(vis, completed ? VIS_MODE_NORMAL : VIS_MODE_VISUAL); } command_free(cmd); return err; }
Filerange text_object_number(Text *txt, size_t pos) { char *buf, *err = NULL; Filerange r = text_object_range(txt, pos, is_number); if (!text_range_valid(&r)) return r; if (!(buf = text_bytes_alloc0(txt, r.start, text_range_size(&r)))) return text_range_empty(); errno = 0; strtoll(buf, &err, 0); if (errno || err == buf) r = text_range_empty(); else r.end = r.start + (err - buf); free(buf); return r; }
Filerange text_object_longword_outer(Text *txt, size_t pos) { Filerange r; char c, prev = '0', next = '0'; Iterator it = text_iterator_get(txt, pos); if (!text_iterator_byte_get(&it, &c)) return text_range_empty(); if (text_iterator_byte_prev(&it, &prev)) text_iterator_byte_next(&it, NULL); text_iterator_byte_next(&it, &next); if (isspace(c)) { /* middle of two words, include leading white space */ r.start = text_char_next(txt, text_longword_end_prev(txt, pos)); r.end = text_char_next(txt, text_longword_end_next(txt, pos)); } else if (isspace(prev) && isspace(next)) { /* on a single character */ r.start = pos; r.end = text_longword_start_next(txt, pos); } else if (isspace(prev)) { /* at start of a word */ r.start = pos; r.end = text_longword_start_next(txt, text_longword_end_next(txt, pos)); } else if (isspace(next)) { /* at end of a word */ r.start = text_longword_start_prev(txt, pos); r.end = text_longword_start_next(txt, pos); } else { /* in the middle of a word */ r.start = text_longword_start_prev(txt, pos); r.end = text_longword_start_next(txt, text_longword_end_next(txt, pos)); } return r; }
Filerange text_object_function(Text *txt, size_t pos) { size_t a = text_function_start_prev(txt, pos); size_t b = text_function_end_next(txt, pos); if (text_function_end_next(txt, a) == b) { Filerange r = text_range_new(a, b+1); return text_range_linewise(txt, &r); } return text_range_empty(); }
Filerange text_object_search_forward(Text *txt, size_t pos, Regex *regex) { size_t start = pos; size_t end = text_size(txt); RegexMatch match[1]; bool found = start < end && !text_search_range_forward(txt, start, end - start, regex, 1, match, 0); if (found) return text_range_new(match[0].start, match[0].end); return text_range_empty(); }
Filerange text_object_search_backward(Text *txt, size_t pos, Regex *regex) { size_t start = 0; size_t end = pos; RegexMatch match[1]; bool found = !text_search_range_backward(txt, start, end, regex, 1, match, 0); if (found) return text_range_new(match[0].start, match[0].end); return text_range_empty(); }
Filerange text_object_indentation(Text *txt, size_t pos) { char c; size_t bol = text_line_begin(txt, pos); size_t sol = text_line_start(txt, bol); size_t start = bol; size_t end = text_line_next(txt, bol); size_t line_indent = sol - bol; bool line_empty = text_byte_get(txt, bol, &c) && (c == '\r' || c == '\n'); char *buf = text_bytes_alloc0(txt, bol, line_indent); char *tmp = malloc(line_indent); if (!buf || !tmp) { free(buf); free(tmp); return text_range_empty(); } while ((bol = text_line_begin(txt, text_line_prev(txt, start))) != start) { sol = text_line_start(txt, bol); size_t indent = sol - bol; if (indent < line_indent) break; bool empty = text_byte_get(txt, bol, &c) && (c == '\r' || c == '\n'); if (line_empty && !empty) break; if (line_indent == 0 && empty) break; text_bytes_get(txt, bol, line_indent, tmp); if (memcmp(buf, tmp, line_indent)) break; start = bol; } do { bol = end; sol = text_line_start(txt, bol); size_t indent = sol - bol; if (indent < line_indent) break; bool empty = text_byte_get(txt, bol, &c) && (c == '\r' || c == '\n'); if (line_empty && !empty) break; if (line_indent == 0 && empty) break; text_bytes_get(txt, bol, line_indent, tmp); if (memcmp(buf, tmp, line_indent)) break; end = text_line_next(txt, bol); } while (bol != end); free(buf); free(tmp); return text_range_new(start, end); }
Filerange view_selection_get(View *view) { Filerange sel = view->sel; if (sel.start > sel.end) { size_t tmp = sel.start; sel.start = sel.end; sel.end = tmp; } if (!text_range_valid(&sel)) return text_range_empty(); sel.end = text_char_next(view->text, sel.end); return sel; }
Filerange text_object_range(Text *txt, size_t pos, int (*isboundary)(int)) { char c; size_t start; Iterator it = text_iterator_get(txt, pos), rit = it; if (!text_iterator_byte_get(&rit, &c) || boundary(c)) return text_range_empty(); char tmp = c; do start = rit.pos; while (text_iterator_char_prev(&rit, &c) && !boundary(c)); for (c = tmp; !boundary(c) && text_iterator_byte_next(&it, &c);); return text_range_new(start, it.pos); }
static bool cmd_select(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) { Filerange sel = text_range_empty(); if (!win) return sam_execute(vis, NULL, cmd->cmd, NULL, &sel); bool ret = true; View *view = win->view; Text *txt = win->file->text; bool multiple_cursors = view_cursors_multiple(view); Cursor *primary = view_cursors_primary_get(view); for (Cursor *c = view_cursors(view), *next; c && ret; c = next) { next = view_cursors_next(c); size_t pos = view_cursors_pos(c); if (vis->mode->visual) { sel = view_cursors_selection_get(c); } else if (cmd->cmd->address) { /* convert a single line range to a goto line motion */ if (!multiple_cursors && cmd->cmd->cmddef->func == cmd_print) { Address *addr = cmd->cmd->address; switch (addr->type) { case '+': case '-': addr = addr->right; /* fall through */ case 'l': if (addr && addr->type == 'l' && !addr->right) addr->type = 'g'; break; } } sel = text_range_new(pos, pos); } else if (cmd->cmd->cmddef->flags & CMD_ADDRESS_POS) { sel = text_range_new(pos, pos); } else if (cmd->cmd->cmddef->flags & CMD_ADDRESS_LINE) { sel = text_object_line(txt, pos); } else if (cmd->cmd->cmddef->flags & CMD_ADDRESS_AFTER) { size_t next_line = text_line_next(txt, pos); sel = text_range_new(next_line, next_line); } else if (multiple_cursors) { sel = text_object_line(txt, pos); } else { sel = text_range_new(0, text_size(txt)); } if (text_range_valid(&sel)) ret &= sam_execute(vis, win, cmd->cmd, c, &sel); if (cmd->cmd->cmddef->flags & CMD_ONCE) break; } if (vis->win && vis->win->view == view && primary != view_cursors_primary_get(view)) view_cursors_primary_set(view_cursors(view)); return ret; }
Filerange text_object_word_find_prev(Text *txt, size_t pos, const char *word) { size_t len = strlen(word); for (;;) { size_t match_pos = text_find_prev(txt, pos, word); if (match_pos != pos) { Filerange match_word = text_object_word(txt, match_pos); if (text_range_size(&match_word) == len) return match_word; pos = match_pos; } else { return text_range_empty(); } } }
static Filerange object_function(Text *txt, size_t pos) { size_t start_prev = text_function_start_prev(txt, pos); size_t end_next = text_function_end_next(txt, pos); size_t start = text_function_start_next(txt, start_prev); size_t end = text_function_end_prev(txt, end_next); if (start == pos) start_prev = pos; if (end == pos) end_next = pos; if (text_function_end_next(txt, start_prev) == end_next && text_function_start_prev(txt, end_next) == start_prev) { return text_range_new(start_prev, end_next); } return text_range_empty(); }
static Filerange text_object_customword(Text *txt, size_t pos, int (*isboundary)(int)) { Filerange r; char c, prev = '0', next = '0'; Iterator it = text_iterator_get(txt, pos); if (!text_iterator_byte_get(&it, &c)) return text_range_empty(); if (text_iterator_byte_prev(&it, &prev)) text_iterator_byte_next(&it, NULL); text_iterator_byte_next(&it, &next); if (space(c)) { r.start = text_char_next(txt, text_customword_end_prev(txt, pos, isboundary)); r.end = text_customword_start_next(txt, pos, isboundary); } else if (boundary(prev) && boundary(next)) { if (boundary(c)) { r.start = text_char_next(txt, text_customword_end_prev(txt, pos, isboundary)); r.end = text_char_next(txt, text_customword_end_next(txt, pos, isboundary)); } else { /* on a single character */ r.start = pos; r.end = text_char_next(txt, pos); } } else if (boundary(prev)) { /* at start of a word */ r.start = pos; r.end = text_char_next(txt, text_customword_end_next(txt, pos, isboundary)); } else if (boundary(next)) { /* at end of a word */ r.start = text_customword_start_prev(txt, pos, isboundary); r.end = text_char_next(txt, pos); } else { /* in the middle of a word */ r.start = text_customword_start_prev(txt, pos, isboundary); r.end = text_char_next(txt, text_customword_end_next(txt, pos, isboundary)); } return r; }
void view_selection_clear(View *view) { view->sel = text_range_empty(); view_draw(view); view_cursor_update(view); }
static bool cmd_extract(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) { if (!win) return false; bool ret = true; Text *txt = win->file->text; if (cmd->regex) { size_t start = range->start, end = range->end, last_start = EPOS; RegexMatch match[1]; while (start < end) { bool found = text_search_range_forward(txt, start, end - start, cmd->regex, 1, match, start > range->start ? REG_NOTBOL : 0) == 0; Filerange r = text_range_empty(); if (found) { if (argv[0][0] == 'x') r = text_range_new(match[0].start, match[0].end); else r = text_range_new(start, match[0].start); if (match[0].start == match[0].end) { if (last_start == match[0].start) { start++; continue; } /* in Plan 9's regexp library ^ matches the beginning * of a line, however in POSIX with REG_NEWLINE ^ * matches the zero-length string immediately after a * newline. Try filtering out the last such match at EOF. */ if (end == match[0].start && start > range->start) break; } start = match[0].end; } else { if (argv[0][0] == 'y') r = text_range_new(start, end); start = end; } if (text_range_valid(&r)) { Mark mark_start = text_mark_set(txt, start); Mark mark_end = text_mark_set(txt, end); ret &= sam_execute(vis, win, cmd->cmd, NULL, &r); last_start = start = text_mark_get(txt, mark_start); if (start == EPOS && last_start != r.end) last_start = start = r.end; end = text_mark_get(txt, mark_end); if (start == EPOS || end == EPOS) { ret = false; break; } } } } else { size_t start = range->start, end = range->end; while (start < end) { size_t next = text_line_next(txt, start); if (next > end) next = end; Filerange r = text_range_new(start, next); if (start == next || !text_range_valid(&r)) break; start = next; Mark mark_start = text_mark_set(txt, start); Mark mark_end = text_mark_set(txt, end); ret &= sam_execute(vis, win, cmd->cmd, NULL, &r); start = text_mark_get(txt, mark_start); if (start == EPOS) start = r.end; end = text_mark_get(txt, mark_end); if (end == EPOS) { ret = false; break; } } } view_cursors_dispose(cur); return ret; }
void action_do(Vis *vis, Action *a) { Win *win = vis->win; Text *txt = win->file->text; View *view = win->view; if (a->op == &vis_operators[VIS_OP_FILTER] && !vis->mode->visual) vis_mode_switch(vis, VIS_MODE_VISUAL_LINE); int count = MAX(a->count, 1); bool repeatable = a->op && !vis->macro_operator; bool multiple_cursors = view_cursors_multiple(view); bool linewise = !(a->type & CHARWISE) && ( a->type & LINEWISE || (a->movement && a->movement->type & LINEWISE) || vis->mode == &vis_modes[VIS_MODE_VISUAL_LINE]); for (Cursor *cursor = view_cursors(view), *next; cursor; cursor = next) { next = view_cursors_next(cursor); size_t pos = view_cursors_pos(cursor); Register *reg = multiple_cursors ? view_cursors_register(cursor) : a->reg; if (!reg) reg = &vis->registers[win->file->internal ? VIS_REG_PROMPT : VIS_REG_DEFAULT]; OperatorContext c = { .count = count, .pos = pos, .newpos = EPOS, .range = text_range_empty(), .reg = reg, .linewise = linewise, .arg = &a->arg, }; if (a->movement) { size_t start = pos; for (int i = 0; i < count; i++) { size_t pos_prev = pos; if (a->movement->txt) pos = a->movement->txt(txt, pos); else if (a->movement->cur) pos = a->movement->cur(cursor); else if (a->movement->file) pos = a->movement->file(vis, vis->win->file, pos); else if (a->movement->vis) pos = a->movement->vis(vis, txt, pos); else if (a->movement->view) pos = a->movement->view(vis, view); else if (a->movement->win) pos = a->movement->win(vis, win, pos); else if (a->movement->user) pos = a->movement->user(vis, win, a->movement->data, pos); if (pos == EPOS || a->movement->type & IDEMPOTENT || pos == pos_prev) break; } if (pos == EPOS) { c.range.start = start; c.range.end = start; pos = start; } else { c.range = text_range_new(start, pos); c.newpos = pos; } if (!a->op) { if (a->movement->type & CHARWISE) view_cursors_scroll_to(cursor, pos); else view_cursors_to(cursor, pos); if (vis->mode->visual) c.range = view_cursors_selection_get(cursor); if (a->movement->type & JUMP) window_jumplist_add(win, pos); else window_jumplist_invalidate(win); } else if (a->movement->type & INCLUSIVE || (linewise && a->movement->type & LINEWISE_INCLUSIVE)) { c.range.end = text_char_next(txt, c.range.end); } } else if (a->textobj) { if (vis->mode->visual) c.range = view_cursors_selection_get(cursor); else c.range.start = c.range.end = pos; for (int i = 0; i < count; i++) { Filerange r = text_range_empty(); if (a->textobj->txt) r = a->textobj->txt(txt, pos); else if (a->textobj->vis) r = a->textobj->vis(vis, txt, pos); else if (a->textobj->user) r = a->textobj->user(vis, win, a->textobj->data, pos); if (!text_range_valid(&r)) break; if (a->textobj->type & OUTER) { r.start--; r.end++; } if (a->textobj->type & SPLIT) c.range = r; else c.range = text_range_union(&c.range, &r); if (i < count - 1) pos = c.range.end + 1; } } else if (vis->mode->visual) { c.range = view_cursors_selection_get(cursor); if (!text_range_valid(&c.range)) c.range.start = c.range.end = pos; } if (linewise && vis->mode != &vis_modes[VIS_MODE_VISUAL]) c.range = text_range_linewise(txt, &c.range); if (vis->mode->visual) { view_cursors_selection_set(cursor, &c.range); if (vis->mode == &vis_modes[VIS_MODE_VISUAL] || a->textobj) view_cursors_selection_sync(cursor); } if (a->op) { size_t pos = a->op->func(vis, txt, &c); if (pos == EPOS) { view_cursors_dispose(cursor); } else if (pos <= text_size(txt)) { /* moving the cursor will affect the selection. * because we want to be able to later restore * the old selection we update it again before * leaving visual mode. */ Filerange sel = view_cursors_selection_get(cursor); view_cursors_to(cursor, pos); if (vis->mode->visual) { if (sel.start == EPOS && sel.end == EPOS) sel = c.range; else if (sel.start == EPOS) sel = text_range_new(c.range.start, sel.end); else if (sel.end == EPOS) sel = text_range_new(c.range.start, sel.start); if (vis->mode == &vis_modes[VIS_MODE_VISUAL_LINE]) sel = text_range_linewise(txt, &sel); view_cursors_selection_set(cursor, &sel); } } } } if (a->op) { /* we do not support visual repeat, still do something resonable */ if (vis->mode->visual && !a->movement && !a->textobj) a->movement = &vis_motions[VIS_MOVE_NOP]; /* operator implementations must not change the mode, * they might get called multiple times (once for every cursor) */ if (a->op == &vis_operators[VIS_OP_INSERT] || a->op == &vis_operators[VIS_OP_CHANGE]) { vis_mode_switch(vis, VIS_MODE_INSERT); } else if (a->op == &vis_operators[VIS_OP_REPLACE]) { vis_mode_switch(vis, VIS_MODE_REPLACE); } else if (a->op == &vis_operators[VIS_OP_FILTER]) { if (a->arg.s) vis_cmd(vis, a->arg.s); else vis_prompt_show(vis, ":|"); } else if (vis->mode == &vis_modes[VIS_MODE_OPERATOR_PENDING]) { mode_set(vis, vis->mode_prev); } else if (vis->mode->visual) { vis_mode_switch(vis, VIS_MODE_NORMAL); } text_snapshot(txt); vis_draw(vis); } if (a != &vis->action_prev) { if (repeatable) { if (!a->macro) a->macro = vis->macro_operator; vis->action_prev = *a; } action_reset(a); } } void action_reset(Action *a) { memset(a, 0, sizeof(*a)); a->count = VIS_COUNT_UNKNOWN; }
static Filerange address_evaluate(Address *addr, File *file, Filerange *range, int sign) { Filerange ret = text_range_empty(); do { switch (addr->type) { case '#': if (sign > 0) ret.start = ret.end = range->end + addr->number; else if (sign < 0) ret.start = ret.end = range->start - addr->number; else ret = text_range_new(addr->number, addr->number); break; case 'l': case 'g': ret = address_line_evaluate(addr, file, range, sign); break; case '?': sign = sign == 0 ? -1 : -sign; /* fall through */ case '/': if (sign >= 0) ret = text_object_search_forward(file->text, range->end, addr->regex); else ret = text_object_search_backward(file->text, range->start, addr->regex); break; case '$': { size_t size = text_size(file->text); ret = text_range_new(size, size); break; } case '.': ret = *range; break; case '+': case '-': sign = addr->type == '+' ? +1 : -1; if (!addr->right || addr->right->type == '+' || addr->right->type == '-') ret = address_line_evaluate(addr, file, range, sign); break; case ',': case ';': { Filerange left, right; if (addr->left) left = address_evaluate(addr->left, file, range, 0); else left = text_range_new(0, 0); if (addr->type == ';') range = &left; if (addr->right) { right = address_evaluate(addr->right, file, range, 0); } else { size_t size = text_size(file->text); right = text_range_new(size, size); } /* TODO: enforce strict ordering? */ return text_range_union(&left, &right); } case '%': return text_range_new(0, text_size(file->text)); } if (text_range_valid(&ret)) range = &ret; } while ((addr = addr->right)); return ret; }