diff options
| author | Andrey Proskurin <andreyproskurin@protonmail.com> | 2021-05-09 18:17:20 +0000 |
|---|---|---|
| committer | Randy Palamar <palamar@ualberta.ca> | 2023-07-28 12:46:29 -0600 |
| commit | 5d7d62c73b258d06efaf5d93026b9e556e4e84d8 (patch) | |
| tree | 303ae344ee97f48a5bab4d6b98153c2f6e1a17c6 /view.c | |
| parent | 1a81e0997422f0cbe249095d1b1b8ed5f4a4f983 (diff) | |
| download | vis-5d7d62c73b258d06efaf5d93026b9e556e4e84d8.tar.gz vis-5d7d62c73b258d06efaf5d93026b9e556e4e84d8.tar.xz | |
view.c: add word wrapping
this is contolled by the wrapcolumn/wc and breakat/brk options
related #142: Word wrap and line breaks
related #932: Vis for Prose?
related #1092: Disabling line wrapping
Diffstat (limited to 'view.c')
| -rw-r--r-- | view.c | 116 |
1 files changed, 86 insertions, 30 deletions
@@ -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) |
