Filerange text_range_linewise(Text *txt, Filerange *rin) { Filerange rout = *rin; rout.start = text_line_begin(txt, rin->start); if (rin->end != text_line_begin(txt, rin->end)) rout.end = text_line_next(txt, rin->end); return rout; }
static size_t op_shift_left(Vis *vis, Text *txt, OperatorContext *c) { size_t pos = text_line_begin(txt, c->range.end), prev_pos; size_t tabwidth = vis->tabwidth, tablen; size_t deleted = 0; /* if range ends at the begin of a line, skip line break */ if (pos == c->range.end) pos = text_line_prev(txt, pos); do { char c; size_t len = 0; prev_pos = pos = text_line_begin(txt, pos); Iterator it = text_iterator_get(txt, pos); if (text_iterator_byte_get(&it, &c) && c == '\t') { len = 1; } else { for (len = 0; text_iterator_byte_get(&it, &c) && c == ' '; len++) text_iterator_byte_next(&it, NULL); } tablen = MIN(len, tabwidth); text_delete(txt, pos, tablen); pos = text_line_prev(txt, pos); deleted += tablen; } while (pos >= c->range.start && pos != prev_pos); return c->pos - deleted; }
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); }
size_t text_range_line_last(Text *txt, Filerange *r) { if (!text_range_valid(r)) return EPOS; size_t pos = text_line_begin(txt, r->end); if (pos == r->end) { /* range ends at a begin of a line, skip last line ending */ pos = text_line_prev(txt, pos); pos = text_line_begin(txt, pos); } return r->start <= pos ? pos : r->start; }
static size_t op_join(Vis *vis, Text *txt, OperatorContext *c) { size_t pos = text_line_begin(txt, c->range.end), prev_pos; Mark mark = NULL; /* if operator and range are both linewise, skip last line break */ if (c->linewise && text_range_is_linewise(txt, &c->range)) { size_t line_prev = text_line_prev(txt, pos); size_t line_prev_prev = text_line_prev(txt, line_prev); if (line_prev_prev >= c->range.start) pos = line_prev; } size_t len = c->arg->s ? strlen(c->arg->s) : 0; do { prev_pos = pos; size_t end = text_line_start(txt, pos); pos = text_line_prev(txt, end); if (pos < c->range.start || end <= pos) break; text_delete(txt, pos, end - pos); char prev, next; if (text_byte_get(txt, pos-1, &prev) && !isspace((unsigned char)prev) && text_byte_get(txt, pos, &next) && next != '\r' && next != '\n') text_insert(txt, pos, c->arg->s, len); if (!mark) mark = text_mark_set(txt, pos); } while (pos != prev_pos); size_t newpos = text_mark_get(txt, mark); return newpos != EPOS ? newpos : c->range.start; }
static size_t op_join(Vis *vis, Text *txt, OperatorContext *c) { size_t pos = text_line_begin(txt, c->range.end), prev_pos; Mark mark = NULL; /* if operator and range are both linewise, skip last line break */ if (c->linewise && text_range_is_linewise(txt, &c->range)) { size_t line_prev = text_line_prev(txt, pos); size_t line_prev_prev = text_line_prev(txt, line_prev); if (line_prev_prev >= c->range.start) pos = line_prev; } do { prev_pos = pos; size_t end = text_line_start(txt, pos); pos = text_char_next(txt, text_line_finish(txt, text_line_prev(txt, end))); if (pos >= c->range.start && end > pos) { text_delete(txt, pos, end - pos); text_insert(txt, pos, " ", 1); if (!mark) mark = text_mark_set(txt, pos); } else { break; } } while (pos != prev_pos); size_t newpos = text_mark_get(txt, mark); return newpos != EPOS ? newpos : c->range.start; }
size_t text_line_start(Text *txt, size_t pos) { char c; Iterator it = text_iterator_get(txt, text_line_begin(txt, pos)); while (text_iterator_byte_get(&it, &c) && c != '\n' && space(c)) text_iterator_byte_next(&it, NULL); return it.pos; }
/* move the cursor to the character at pos bytes from the begining of the file. * if pos is not in the current viewport, redraw the view to make it visible */ void view_cursor_to(View *view, size_t pos) { size_t max = text_size(view->text); if (pos > max) pos = max > 0 ? max - 1 : 0; if (pos == max && view->end != max) { /* do not display an empty screen when shoviewg the end of the file */ view->start = max - 1; view_viewport_up(view, view->height / 2); } else { /* set the start of the viewable region to the start of the line on which * the cursor should be placed. if this line requires more space than * available in the view then simply start displaying text at the new * cursor position */ for (int i = 0; i < 2 && (pos < view->start || pos > view->end); i++) { view->start = i == 0 ? text_line_begin(view->text, pos) : pos; view_draw(view); } } view->cursor.pos = pos; view_cursor_sync(view); view_cursor_update(view); }
size_t text_line_char_set(Text *txt, size_t pos, int count) { char c; size_t bol = text_line_begin(txt, pos); Iterator it = text_iterator_get(txt, bol); while (count-- > 0 && text_iterator_byte_get(&it, &c) && c != '\r' && c != '\n') text_iterator_char_next(&it, NULL); return it.pos; }
size_t text_line_offset(Text *txt, size_t pos, size_t off) { char c; size_t bol = text_line_begin(txt, pos); Iterator it = text_iterator_get(txt, bol); while (off-- > 0 && text_iterator_byte_get(&it, &c) && c != '\r' && c != '\n') text_iterator_byte_next(&it, NULL); return it.pos; }
static size_t op_delete(Vis *vis, Text *txt, OperatorContext *c) { c->reg->linewise = c->linewise; register_put_range(vis, c->reg, txt, &c->range); text_delete_range(txt, &c->range); size_t pos = c->range.start; if (c->linewise && pos == text_size(txt)) pos = text_line_begin(txt, text_line_prev(txt, pos)); return pos; }
static size_t op_put(Vis *vis, Text *txt, OperatorContext *c) { size_t pos = c->pos; bool sel = text_range_size(&c->range) > 0; bool sel_linewise = sel && text_range_is_linewise(txt, &c->range); if (sel) { text_delete_range(txt, &c->range); pos = c->pos = c->range.start; } switch (c->arg->i) { case VIS_OP_PUT_AFTER: case VIS_OP_PUT_AFTER_END: if (c->reg->linewise && !sel_linewise) pos = text_line_next(txt, pos); else if (!sel) pos = text_char_next(txt, pos); break; case VIS_OP_PUT_BEFORE: case VIS_OP_PUT_BEFORE_END: if (c->reg->linewise) pos = text_line_begin(txt, pos); break; } size_t len; const char *data = register_get(vis, c->reg, &len); for (int i = 0; i < c->count; i++) { text_insert(txt, pos, data, len); pos += len; } if (c->reg->linewise) { switch (c->arg->i) { case VIS_OP_PUT_BEFORE_END: case VIS_OP_PUT_AFTER_END: pos = text_line_start(txt, pos); break; case VIS_OP_PUT_AFTER: pos = text_line_start(txt, text_line_next(txt, c->pos)); break; case VIS_OP_PUT_BEFORE: pos = text_line_start(txt, c->pos); break; } } else { switch (c->arg->i) { case VIS_OP_PUT_AFTER: case VIS_OP_PUT_BEFORE: pos = text_char_prev(txt, pos); break; } } return pos; }
int text_line_char_get(Text *txt, size_t pos) { char c; int count = 0; size_t bol = text_line_begin(txt, pos); Iterator it = text_iterator_get(txt, bol); while (text_iterator_byte_get(&it, &c) && it.pos < pos && c != '\r' && c != '\n') { text_iterator_char_next(&it, NULL); count++; } return count; }
static size_t op_shift_right(Vis *vis, Text *txt, OperatorContext *c) { char spaces[9] = " "; spaces[MIN(vis->tabwidth, LENGTH(spaces) - 1)] = '\0'; const char *tab = vis->expandtab ? spaces : "\t"; size_t tablen = strlen(tab); size_t pos = text_line_begin(txt, c->range.end), prev_pos; size_t inserted = 0; /* if range ends at the begin of a line, skip line break */ if (pos == c->range.end) pos = text_line_prev(txt, pos); do { prev_pos = pos = text_line_begin(txt, pos); text_insert(txt, pos, tab, tablen); pos = text_line_prev(txt, pos); inserted += tablen; } while (pos >= c->range.start && pos != prev_pos); return c->pos + inserted; }
size_t text_line_width_set(Text *txt, size_t pos, int width) { int cur_width = 0; mbstate_t ps = { 0 }; size_t bol = text_line_begin(txt, pos); Iterator it = text_iterator_get(txt, bol); for (;;) { char buf[MB_CUR_MAX]; size_t len = text_bytes_get(txt, it.pos, sizeof buf, buf); if (len == 0 || buf[0] == '\r' || buf[0] == '\n') break; wchar_t wc; size_t wclen = mbrtowc(&wc, buf, len, &ps); if (wclen == (size_t)-1 && errno == EILSEQ) { /* assume a replacement symbol will be displayed */ cur_width++; } else if (wclen == (size_t)-2) { /* do nothing, advance to next character */ } else if (wclen == 0) { /* assume NUL byte will be displayed as ^@ */ cur_width += 2; } else if (buf[0] == '\t') { cur_width++; } else { int w = wcwidth(wc); if (w == -1) w = 2; /* assume non-printable will be displayed as ^{char} */ cur_width += w; } if (cur_width >= width || !text_iterator_codepoint_next(&it, NULL)) break; } return it.pos; }
size_t text_range_line_prev(Text *txt, Filerange *r, size_t pos) { if (!text_range_contains(r, pos)) return EPOS; size_t newpos = text_line_begin(txt, text_line_prev(txt, pos)); return newpos != pos && r->start <= newpos ? newpos : EPOS; }
Filerange text_object_line(Text *txt, size_t pos) { Filerange r; r.start = text_line_begin(txt, pos); r.end = text_line_next(txt, pos); return r; }
bool text_range_is_linewise(Text *txt, Filerange *r) { return text_range_valid(r) && r->start == text_line_begin(txt, r->start) && r->end == text_line_begin(txt, r->end); }