From cc3a7e5566f7a33deeed5cbdcb9057e585c91dde Mon Sep 17 00:00:00 2001 From: Andrey Proskurin <> Date: Sun, 9 May 2021 00:34:16 +0000 Subject: [PATCH 1/5] view: refactor view_addch --- man/vis.1 | 5 + sam.c | 12 +++ view.c | 224 +++++++++++++++++++++++++++++++++++++------------------------ view.h | 2 vis-cmds.c | 9 ++ 5 files changed, 164 insertions(+), 88 deletions(-) --- a/man/vis.1 +++ b/man/vis.1 @@ -1423,6 +1423,11 @@ WARNING: modifying a memory mapped file 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 --- a/sam.c +++ b/sam.c @@ -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) { --- a/view.c +++ b/view.c @@ -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, 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,98 +171,124 @@ Filerange view_viewport_get(View *view) return (Filerange){ .start = view->start, .end = view->end }; } -/* try to add another character to the view, return whether there was space left */ -static bool view_addch(View *view, Cell *cell) { +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 *cur_line = view->line; + int cur_col = view->col; + int wrapcol = (view->wrapcol > 0) ? view->wrapcol : cur_col; + + view->line = cur_line->next; + view->col = 0; + view->wrapcol = 0; + if (view->line) { + /* move extra cells to the next line */ + for (int i = wrapcol; i < cur_col; ++i) { + const Cell *cell = &cur_line->cells[i]; + view_add_cell(view, cell); + cur_line->width -= cell->width; + cur_line->len -= cell->len; + } + } + for (int i = wrapcol; i < view->width; ++i) { + /* clear remaining of line */ + cur_line->cells[i] = view->cell_blank; + } +} + +static bool view_add_cell(View *view, const Cell *cell) { + size_t lineno = view->line->lineno; + + if (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++; + /* set cells of a character which uses multiple columns */ + for (int i = 1; i < cell->width; i++) + view->line->cells[view->col++] = cell_unused; + return true; +} - int width; - size_t lineno = view->line->lineno; - unsigned char ch = (unsigned char)cell->data[0]; - cell->style = view->cell_blank.style; +static bool view_expand_tab(View *view, Cell *cell) { + cell->width = 1; - switch (ch) { - case '\t': - cell->width = 1; - width = view->tabwidth - (view->col % view->tabwidth); - for (int w = 0; w < width; w++) { - if (view->col + 1 > view->width) { - view->line = view->line->next; - view->col = 0; - if (!view->line) - return false; - view->line->lineno = lineno; - } - - cell->len = w == 0 ? 1 : 0; - int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL; - strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1); - view->line->cells[view->col] = *cell; - view->line->len += cell->len; - view->line->width += cell->width; - view->col++; - } - cell->len = 1; - return true; - case '\n': - cell->width = 1; - if (view->col + cell->width > view->width) { - view->line = view->line->next; - view->col = 0; - if (!view->line) - return false; - view->line->lineno = lineno; - } + int displayed_width = view->tabwidth - (view->col % view->tabwidth); + for (int w = 0; w < displayed_width; ++w) { - strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1); + 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); + cell->len = (w == 0) ? 1 : 0; - view->line->cells[view->col] = *cell; - view->line->len += cell->len; - view->line->width += cell->width; - for (int i = view->col + 1; i < view->width; i++) - view->line->cells[i] = view->cell_blank; - - view->line = view->line->next; - if (view->line) - view->line->lineno = lineno + 1; - view->col = 0; - return true; - default: - if (ch < 128 && !isprint(ch)) { - /* non-printable ascii char, represent it as ^(char + 64) */ - *cell = (Cell) { - .data = { '^', ch == 127 ? '?' : ch + 64, '\0' }, - .len = 1, - .width = 2, - .style = cell->style, - }; - } + if (!view_add_cell(view, cell)) + return false; + } - if (ch == ' ') { - strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1); + cell->len = 1; + return true; +} - } +static bool view_expand_newline(View *view, Cell *cell) { + size_t lineno = view->line->lineno; + const char *symbol = view->symbols[SYNTAX_SYMBOL_EOL]->symbol; - 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; - } + strncpy(cell->data, symbol, sizeof(cell->data) - 1); + cell->width = 1; + if (!view_add_cell(view, cell)) + return false; - if (view->line) { - view->line->width += cell->width; - view->line->len += cell->len; - view->line->lineno = lineno; - view->line->cells[view->col] = *cell; - view->col++; - /* set cells of a character which uses multiple columns */ - for (int i = 1; i < cell->width; i++) - view->line->cells[view->col++] = cell_unused; - return true; - } + view->wrapcol = 0; + view_wrap_line(view); + if (view->line) + view->line->lineno = lineno + 1; + return true; +} + +/* try to add another character to the view, return whether there was space left */ +static bool view_addch(View *view, Cell *cell) { + if (!view->line) return false; + + unsigned char ch = (unsigned char)cell->data[0]; + bool ch_breakat = 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; + + switch (ch) { + case '\t': + return view_expand_tab(view, cell); + case '\n': + return view_expand_newline(view, cell); + case ' ': { + const char *symbol = view->symbols[SYNTAX_SYMBOL_SPACE]->symbol; + strncpy(cell->data, symbol, sizeof(cell->data) - 1); + return view_add_cell(view, cell); + }} + + if (ch < 128 && !isprint(ch)) { + /* non-printable ascii char, represent it as ^(char + 64) */ + *cell = (Cell) { + .data = { '^', ch == 127 ? '?' : ch + 64, '\0' }, + .len = 1, + .width = 2, + .style = cell->style, + }; + } + return view_add_cell(view, cell); } static void cursor_to(Selection *s, size_t pos) { @@ -492,6 +525,7 @@ void view_free(View *view) { selection_free(view->selections); free(view->textbuf); free(view->lines); + free(view->breakat); free(view); } @@ -507,27 +541,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; } @@ -862,6 +896,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) --- a/view.h +++ b/view.h @@ -358,6 +358,8 @@ void view_options_set(View*, enum UiOpti 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); --- a/vis-cmds.c +++ b/vis-cmds.c @@ -364,6 +364,15 @@ static bool cmd_set(Vis *vis, Win *win, 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;