diff options
| -rw-r--r-- | man/vis.1 | 5 | ||||
| -rw-r--r-- | sam.c | 12 | ||||
| -rw-r--r-- | view.c | 116 | ||||
| -rw-r--r-- | view.h | 2 | ||||
| -rw-r--r-- | vis-cmds.c | 9 |
5 files changed, 114 insertions, 30 deletions
@@ -1433,6 +1433,11 @@ WARNING: modifying a memory mapped file in-place will cause data loss. Whether to use vertical or horizontal layout. .It Cm ignorecase , Cm ic Op Cm off Whether to ignore case when searching. +.It Ic wrapcolumn , Ic wc Op Ar 0 +Wrap lines at minimum of window width and wrapcolumn. +. +.It Ic breakat , brk Op Dq Pa "" +Characters which might cause a word wrap. .El . .Sh COMMAND and SEARCH PROMPT @@ -301,6 +301,8 @@ enum { OPTION_CHANGE_256COLORS, OPTION_LAYOUT, OPTION_IGNORECASE, + OPTION_BREAKAT, + OPTION_WRAP_COLUMN, }; static const OptionDef options[] = { @@ -394,6 +396,16 @@ static const OptionDef options[] = { VIS_OPTION_TYPE_BOOL, VIS_HELP("Ignore case when searching") }, + [OPTION_BREAKAT] = { + { "breakat", "brk" }, + VIS_OPTION_TYPE_STRING|VIS_OPTION_NEED_WINDOW, + VIS_HELP("Characters which might cause a word wrap") + }, + [OPTION_WRAP_COLUMN] = { + { "wrapcolumn", "wc" }, + VIS_OPTION_TYPE_NUMBER|VIS_OPTION_NEED_WINDOW, + VIS_HELP("Wrap lines at minimum of window width and wrapcolumn") + }, }; bool sam_init(Vis *vis) { @@ -80,6 +80,10 @@ struct View { bool need_update; /* whether view has been redrawn */ bool large_file; /* optimize for displaying large files */ int colorcolumn; + char *breakat; /* characters which might cause a word wrap */ + int wrapcolumn; /* wrap lines at minimum of window width and wrapcolumn (if != 0) */ + int wrapcol; /* used while drawing view content, column where word wrap might happen */ + bool prevch_breakat; /* used while drawing view content, previous char is part of breakat */ }; static const SyntaxSymbol symbols_none[] = { @@ -109,6 +113,7 @@ static bool view_viewport_up(View *view, int n); static bool view_viewport_down(View *view, int n); static void view_clear(View *view); +static bool view_add_cell(View *view, const Cell *cell); static bool view_addch(View *view, Cell *cell); static void selection_free(Selection*); /* set/move current cursor position to a given (line, column) pair */ @@ -156,6 +161,8 @@ static void view_clear(View *view) { view->bottomline->next = NULL; view->line = view->topline; view->col = 0; + view->wrapcol = 0; + view->prevch_breakat = false; if (view->ui) view->cell_blank.style = view->ui->style_get(view->ui, UI_STYLE_DEFAULT); } @@ -164,24 +171,57 @@ Filerange view_viewport_get(View *view) { return (Filerange){ .start = view->start, .end = view->end }; } -static bool view_add_cell(View *view, const Cell *cell) { - size_t lineno = view->line->lineno; +static int view_max_text_width(const View *view) { + if (view->wrapcolumn > 0) + return MIN(view->wrapcolumn, view->width); + return view->width; +} + +static void view_wrap_line(View *view) { + Line *wrapped_line = view->line; + int col = view->col; + int wrapcol = (view->wrapcol > 0) ? view->wrapcol : view->col; + + view->line = view->line->next; + view->col = 0; + view->wrapcol = 0; - if (view->col + cell->width > view->width) { - for (int i = view->col; i < view->width; i++) - view->line->cells[i] = view->cell_blank; - view->line = view->line->next; - view->col = 0; + if (view->line) { + view->line->lineno = wrapped_line->lineno; + /* move extra cells to the next line */ + for (int i = wrapcol; i < col; ++i) { + const Cell *cell = &wrapped_line->cells[i]; + view->line->width += cell->width; + view->line->len += cell->len; + view->line->cells[view->col++] = *cell; + } } - if (!view->line) - return false; + /* clear remaining cells on line */ + for (int i = wrapcol; i < view->width; ++i) { + if (i < col) { + wrapped_line->width -= wrapped_line->cells[i].width; + wrapped_line->len -= wrapped_line->cells[i].len; + } + wrapped_line->cells[i] = view->cell_blank; + } +} + +static bool view_add_cell(View *view, const Cell *cell) { + /* if the terminal is resized to a single (ASCII) char an out + * of bounds write could be performed for a wide char. this can + * be caught by iterating through the lines with view_wrap_line() + * until no lines remain. usually 0 or 1 iterations. + */ + while (view->col + cell->width > view_max_text_width(view)) { + view_wrap_line(view); + if (!view->line) + return false; + } view->line->width += cell->width; view->line->len += cell->len; - view->line->lineno = lineno; - view->line->cells[view->col] = *cell; - view->col++; + view->line->cells[view->col++] = *cell; /* set cells of a character which uses multiple columns */ for (int i = 1; i < cell->width; i++) view->line->cells[view->col++] = cell_unused; @@ -193,7 +233,6 @@ static bool view_expand_tab(View *view, Cell *cell) { int displayed_width = view->tabwidth - (view->col % view->tabwidth); for (int w = 0; w < displayed_width; ++w) { - int t = (w == 0) ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL; const char *symbol = view->symbols[t]->symbol; strncpy(cell->data, symbol, sizeof(cell->data) - 1); @@ -208,22 +247,18 @@ static bool view_expand_tab(View *view, Cell *cell) { } static bool view_expand_newline(View *view, Cell *cell) { + size_t lineno = view->line->lineno; const char *symbol = view->symbols[SYNTAX_SYMBOL_EOL]->symbol; + strncpy(cell->data, symbol, sizeof(cell->data) - 1); cell->width = 1; - if (!view_add_cell(view, cell)) return false; - for (int i = view->col; i < view->width; ++i) - view->line->cells[i] = view->cell_blank; - - size_t lineno = view->line->lineno; - view->line = view->line->next; - view->col = 0; + view->wrapcol = 0; + view_wrap_line(view); if (view->line) view->line->lineno = lineno + 1; - return true; } @@ -232,9 +267,15 @@ static bool view_addch(View *view, Cell *cell) { if (!view->line) return false; - unsigned char ch = (unsigned char)cell->data[0]; + bool ch_breakat = (cell->data[0] != 0) && strstr(view->breakat, cell->data); + if (view->prevch_breakat && !ch_breakat) { + /* this is a good place to wrap line if needed */ + view->wrapcol = view->col; + } + view->prevch_breakat = ch_breakat; cell->style = view->cell_blank.style; + unsigned char ch = (unsigned char)cell->data[0]; switch (ch) { case '\t': return view_expand_tab(view, cell); @@ -493,6 +534,7 @@ void view_free(View *view) { selection_free(view->selections); free(view->textbuf); free(view->lines); + free(view->breakat); free(view); } @@ -508,27 +550,27 @@ View *view_new(Text *text) { View *view = calloc(1, sizeof(View)); if (!view) return NULL; - view->text = text; - if (!view_selections_new(view, 0)) { - view_free(view); - return NULL; - } + view->text = text; + view->tabwidth = 8; + view->breakat = strdup(""); + view->wrapcolumn = 0; view->cell_blank = (Cell) { .width = 0, .len = 0, .data = " ", }; - view->tabwidth = 8; view_options_set(view, 0); - if (!view_resize(view, 1, 1)) { + if (!view->breakat || + !view_selections_new(view, 0) || + !view_resize(view, 1, 1)) + { view_free(view); return NULL; } view_cursor_to(view, 0); - return view; } @@ -863,6 +905,20 @@ int view_colorcolumn_get(View *view) { return view->colorcolumn; } +void view_wrapcolumn_set(View *view, int col) { + if (col >= 0) + view->wrapcolumn = col; +} + +bool view_breakat_set(View *view, const char *breakat) { + char *copy = strdup(breakat); + if (!copy) + return false; + free(view->breakat); + view->breakat = copy; + return true; +} + size_t view_screenline_goto(View *view, int n) { size_t pos = view->start; for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next) @@ -358,6 +358,8 @@ void view_options_set(View*, enum UiOption options); enum UiOption view_options_get(View*); void view_colorcolumn_set(View*, int col); int view_colorcolumn_get(View*); +void view_wrapcolumn_set(View*, int col); +bool view_breakat_set(View*, const char *breakat); /** Set how many spaces are used to display a tab `\t` character. */ void view_tabwidth_set(View*, int tabwidth); @@ -364,6 +364,15 @@ static bool cmd_set(Vis *vis, Win *win, Command *cmd, const char *argv[], Select case OPTION_IGNORECASE: vis->ignorecase = toggle ? !vis->ignorecase : arg.b; break; + case OPTION_BREAKAT: + if (!view_breakat_set(win->view, arg.s)) { + vis_info_show(vis, "Failed to set breakat"); + return false; + } + break; + case OPTION_WRAP_COLUMN: + view_wrapcolumn_set(win->view, arg.i); + break; default: if (!opt->func) return false; |
