1 From cc3a7e5566f7a33deeed5cbdcb9057e585c91dde Mon Sep 17 00:00:00 2001
2 From: Andrey Proskurin <>
3 Date: Sun, 9 May 2021 00:34:16 +0000
4 Subject: [PATCH 1/5] view: refactor view_addch
7 view.c | 158 ++++++++++++++++++++++++++++-----------------------------
8 1 file changed, 79 insertions(+), 79 deletions(-)
10 diff --git a/view.c b/view.c
11 index 74967dc6..b10deb92 100644
14 @@ -164,98 +164,98 @@ Filerange view_viewport_get(View *view) {
15 return (Filerange){ .start = view->start, .end = view->end };
18 +static bool view_add_cell(View *view, const Cell *cell) {
19 + size_t lineno = view->line->lineno;
21 + if (view->col + cell->width > view->width) {
22 + for (int i = view->col; i < view->width; i++)
23 + view->line->cells[i] = view->cell_blank;
24 + view->line = view->line->next;
31 + view->line->width += cell->width;
32 + view->line->len += cell->len;
33 + view->line->lineno = lineno;
34 + view->line->cells[view->col] = *cell;
36 + /* set cells of a character which uses multiple columns */
37 + for (int i = 1; i < cell->width; i++)
38 + view->line->cells[view->col++] = cell_unused;
42 +static bool view_expand_tab(View *view, Cell *cell) {
45 + int displayed_width = view->tabwidth - (view->col % view->tabwidth);
46 + for (int w = 0; w < displayed_width; ++w) {
48 + int t = (w == 0) ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
49 + const char *symbol = view->symbols[t]->symbol;
50 + strncpy(cell->data, symbol, sizeof(cell->data) - 1);
51 + cell->len = (w == 0) ? 1 : 0;
53 + if (!view_add_cell(view, cell))
61 +static bool view_expand_newline(View *view, Cell *cell) {
62 + const char *symbol = view->symbols[SYNTAX_SYMBOL_EOL]->symbol;
63 + strncpy(cell->data, symbol, sizeof(cell->data) - 1);
66 + if (!view_add_cell(view, cell))
69 + for (int i = view->col; i < view->width; ++i)
70 + view->line->cells[i] = view->cell_blank;
72 + size_t lineno = view->line->lineno;
73 + view->line = view->line->next;
76 + view->line->lineno = lineno + 1;
81 /* try to add another character to the view, return whether there was space left */
82 static bool view_addch(View *view, Cell *cell) {
87 - size_t lineno = view->line->lineno;
88 unsigned char ch = (unsigned char)cell->data[0];
89 cell->style = view->cell_blank.style;
94 - width = view->tabwidth - (view->col % view->tabwidth);
95 - for (int w = 0; w < width; w++) {
96 - if (view->col + 1 > view->width) {
97 - view->line = view->line->next;
101 - view->line->lineno = lineno;
104 - cell->len = w == 0 ? 1 : 0;
105 - int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
106 - strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1);
107 - view->line->cells[view->col] = *cell;
108 - view->line->len += cell->len;
109 - view->line->width += cell->width;
114 + return view_expand_tab(view, cell);
117 - if (view->col + cell->width > view->width) {
118 - view->line = view->line->next;
122 - view->line->lineno = lineno;
125 - strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1);
127 - view->line->cells[view->col] = *cell;
128 - view->line->len += cell->len;
129 - view->line->width += cell->width;
130 - for (int i = view->col + 1; i < view->width; i++)
131 - view->line->cells[i] = view->cell_blank;
133 - view->line = view->line->next;
135 - view->line->lineno = lineno + 1;
139 - if (ch < 128 && !isprint(ch)) {
140 - /* non-printable ascii char, represent it as ^(char + 64) */
142 - .data = { '^', ch == 127 ? '?' : ch + 64, '\0' },
145 - .style = cell->style,
150 - strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1);
154 - if (view->col + cell->width > view->width) {
155 - for (int i = view->col; i < view->width; i++)
156 - view->line->cells[i] = view->cell_blank;
157 - view->line = view->line->next;
160 + return view_expand_newline(view, cell);
162 + const char *symbol = view->symbols[SYNTAX_SYMBOL_SPACE]->symbol;
163 + strncpy(cell->data, symbol, sizeof(cell->data) - 1);
164 + return view_add_cell(view, cell);
168 - view->line->width += cell->width;
169 - view->line->len += cell->len;
170 - view->line->lineno = lineno;
171 - view->line->cells[view->col] = *cell;
173 - /* set cells of a character which uses multiple columns */
174 - for (int i = 1; i < cell->width; i++)
175 - view->line->cells[view->col++] = cell_unused;
179 + if (ch < 128 && !isprint(ch)) {
180 + /* non-printable ascii char, represent it as ^(char + 64) */
182 + .data = { '^', ch == 127 ? '?' : ch + 64, '\0' },
185 + .style = cell->style,
188 + return view_add_cell(view, cell);
191 static void cursor_to(Selection *s, size_t pos) {
193 From 50e75ddf8a73feab300d7789d000f9687a509f18 Mon Sep 17 00:00:00 2001
194 From: Andrey Proskurin <>
195 Date: Sun, 9 May 2021 18:17:20 +0000
196 Subject: [PATCH 2/5] view.c: add word wrapping
199 view.c | 61 +++++++++++++++++++++++++++++++++++++++++++---------------
200 1 file changed, 45 insertions(+), 16 deletions(-)
202 diff --git a/view.c b/view.c
203 index b10deb92..e7ca8141 100644
206 @@ -80,6 +80,10 @@ struct View {
207 bool need_update; /* whether view has been redrawn */
208 bool large_file; /* optimize for displaying large files */
210 + // TODO lua option: breakat / brk
211 + const char *breakat; /* characters which might cause a word wrap */
212 + int wrapcol; /* used while drawing view content, column where word wrap might happen */
213 + bool prevch_breakat; /* used while drawing view content, previous char is part of breakat */
216 static const SyntaxSymbol symbols_none[] = {
217 @@ -109,6 +113,7 @@ static bool view_viewport_up(View *view, int n);
218 static bool view_viewport_down(View *view, int n);
220 static void view_clear(View *view);
221 +static bool view_add_cell(View *view, const Cell *cell);
222 static bool view_addch(View *view, Cell *cell);
223 static void selection_free(Selection*);
224 /* set/move current cursor position to a given (line, column) pair */
225 @@ -156,6 +161,8 @@ static void view_clear(View *view) {
226 view->bottomline->next = NULL;
227 view->line = view->topline;
230 + view->prevch_breakat = false;
232 view->cell_blank.style = view->ui->style_get(view->ui, UI_STYLE_DEFAULT);
234 @@ -164,19 +171,37 @@ Filerange view_viewport_get(View *view) {
235 return (Filerange){ .start = view->start, .end = view->end };
238 +static void view_wrap_line(View *view) {
239 + Line *cur_line = view->line;
240 + int cur_col = view->col;
241 + int wrapcol = (view->wrapcol > 0) ? view->wrapcol : cur_col;
243 + view->line = cur_line->next;
247 + /* move extra cells to the next line */
248 + for (int i = wrapcol; i < cur_col; ++i) {
249 + const Cell *cell = &cur_line->cells[i];
250 + view_add_cell(view, cell);
251 + cur_line->width -= cell->width;
252 + cur_line->len -= cell->len;
255 + for (int i = wrapcol; i < view->width; ++i) {
256 + /* clear remaining of line */
257 + cur_line->cells[i] = view->cell_blank;
261 static bool view_add_cell(View *view, const Cell *cell) {
262 size_t lineno = view->line->lineno;
264 - if (view->col + cell->width > view->width) {
265 - for (int i = view->col; i < view->width; i++)
266 - view->line->cells[i] = view->cell_blank;
267 - view->line = view->line->next;
270 + if (view->col + cell->width > view->width)
271 + view_wrap_line(view);
276 view->line->width += cell->width;
277 view->line->len += cell->len;
278 view->line->lineno = lineno;
279 @@ -208,22 +233,18 @@ static bool view_expand_tab(View *view, Cell *cell) {
282 static bool view_expand_newline(View *view, Cell *cell) {
283 + size_t lineno = view->line->lineno;
284 const char *symbol = view->symbols[SYNTAX_SYMBOL_EOL]->symbol;
286 strncpy(cell->data, symbol, sizeof(cell->data) - 1);
289 if (!view_add_cell(view, cell))
292 - for (int i = view->col; i < view->width; ++i)
293 - view->line->cells[i] = view->cell_blank;
295 - size_t lineno = view->line->lineno;
296 - view->line = view->line->next;
299 + view_wrap_line(view);
301 view->line->lineno = lineno + 1;
306 @@ -233,8 +254,14 @@ static bool view_addch(View *view, Cell *cell) {
309 unsigned char ch = (unsigned char)cell->data[0];
310 + bool ch_breakat = strchr(view->breakat, ch);
311 + if (view->prevch_breakat && !ch_breakat) {
312 + /* this is a good place to wrap line if needed */
313 + view->wrapcol = view->col;
315 + view->prevch_breakat = ch_breakat;
316 cell->style = view->cell_blank.style;
321 return view_expand_tab(view, cell);
322 @@ -519,6 +546,8 @@ View *view_new(Text *text) {
326 + // TODO default value
327 + view->breakat = "";
328 view_options_set(view, 0);
330 if (!view_resize(view, 1, 1)) {
332 From b50672e3233e5e2d2a537d697082806a5012d6ac Mon Sep 17 00:00:00 2001
333 From: Andrey Proskurin <>
334 Date: Sun, 9 May 2021 21:56:36 +0000
335 Subject: [PATCH 3/5] add `wrapcolumn / wc` and `breakat / brk` options
339 sam.c | 12 ++++++++++++
340 view.c | 29 +++++++++++++++++++++++------
342 vis-cmds.c | 6 ++++++
343 5 files changed, 48 insertions(+), 6 deletions(-)
345 diff --git a/man/vis.1 b/man/vis.1
346 index 05433663..2f6b4754 100644
349 @@ -1423,6 +1423,11 @@ WARNING: modifying a memory mapped file in-place will cause data loss.
350 Whether to use vertical or horizontal layout.
351 .It Cm ignorecase , Cm ic Op Cm off
352 Whether to ignore case when searching.
353 +.It Ic wrapcolumn , Ic wc Op Ar 0
354 +Wrap lines at minimum of window width and wrapcolumn.
356 +.It Ic breakat , brk Op Dq Pa ""
357 +Characters which might cause a word wrap.
360 .Sh COMMAND and SEARCH PROMPT
361 diff --git a/sam.c b/sam.c
362 index 29e9c583..d7540e07 100644
365 @@ -301,6 +301,8 @@ enum {
366 OPTION_CHANGE_256COLORS,
370 + OPTION_WRAP_COLUMN,
373 static const OptionDef options[] = {
374 @@ -394,6 +396,16 @@ static const OptionDef options[] = {
375 VIS_OPTION_TYPE_BOOL,
376 VIS_HELP("Ignore case when searching")
378 + [OPTION_BREAKAT] = {
379 + { "breakat", "brk" },
380 + VIS_OPTION_TYPE_STRING|VIS_OPTION_NEED_WINDOW,
381 + VIS_HELP("Characters which might cause a word wrap")
383 + [OPTION_WRAP_COLUMN] = {
384 + { "wrapcolumn", "wc" },
385 + VIS_OPTION_TYPE_NUMBER|VIS_OPTION_NEED_WINDOW,
386 + VIS_HELP("Wrap lines at minimum of window width and wrapcolumn")
390 bool sam_init(Vis *vis) {
391 diff --git a/view.c b/view.c
392 index e7ca8141..79fc7bc1 100644
395 @@ -80,9 +80,9 @@ struct View {
396 bool need_update; /* whether view has been redrawn */
397 bool large_file; /* optimize for displaying large files */
399 - // TODO lua option: breakat / brk
400 - const char *breakat; /* characters which might cause a word wrap */
401 - int wrapcol; /* used while drawing view content, column where word wrap might happen */
402 + char *breakat; /* characters which might cause a word wrap */
403 + int wrapcolumn; /* wrap lines at minimum of window width and wrapcolumn (if != 0) */
404 + int wrapcol; /* used while drawing view content, column where word wrap might happen */
405 bool prevch_breakat; /* used while drawing view content, previous char is part of breakat */
408 @@ -171,6 +171,12 @@ Filerange view_viewport_get(View *view) {
409 return (Filerange){ .start = view->start, .end = view->end };
412 +static int view_max_text_width(const View *view) {
413 + if (view->wrapcolumn > 0)
414 + return MIN(view->wrapcolumn, view->width);
415 + return view->width;
418 static void view_wrap_line(View *view) {
419 Line *cur_line = view->line;
420 int cur_col = view->col;
421 @@ -197,7 +203,7 @@ static void view_wrap_line(View *view) {
422 static bool view_add_cell(View *view, const Cell *cell) {
423 size_t lineno = view->line->lineno;
425 - if (view->col + cell->width > view->width)
426 + if (view->col + cell->width > view_max_text_width(view))
427 view_wrap_line(view);
430 @@ -519,6 +525,7 @@ void view_free(View *view) {
431 selection_free(view->selections);
434 + free(view->breakat);
438 @@ -546,8 +553,8 @@ View *view_new(Text *text) {
442 - // TODO default value
443 - view->breakat = "";
444 + view->breakat = strdup("");
445 + view->wrapcolumn = 0;
446 view_options_set(view, 0);
448 if (!view_resize(view, 1, 1)) {
449 @@ -891,6 +898,16 @@ int view_colorcolumn_get(View *view) {
450 return view->colorcolumn;
453 +void view_wrapcolumn_set(View *view, int col) {
455 + view->wrapcolumn = col;
458 +void view_breakat_set(View *view, const char *breakat) {
459 + free(view->breakat);
460 + view->breakat = strdup(breakat);
463 size_t view_screenline_goto(View *view, int n) {
464 size_t pos = view->start;
465 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
466 diff --git a/view.h b/view.h
467 index 31b044b8..65bcb29d 100644
470 @@ -358,6 +358,8 @@ void view_options_set(View*, enum UiOption options);
471 enum UiOption view_options_get(View*);
472 void view_colorcolumn_set(View*, int col);
473 int view_colorcolumn_get(View*);
474 +void view_wrapcolumn_set(View*, int col);
475 +void view_breakat_set(View*, const char *breakat);
477 /** Set how many spaces are used to display a tab `\t` character. */
478 void view_tabwidth_set(View*, int tabwidth);
479 diff --git a/vis-cmds.c b/vis-cmds.c
480 index f5221d14..e2bff70d 100644
483 @@ -364,6 +364,12 @@ static bool cmd_set(Vis *vis, Win *win, Command *cmd, const char *argv[], Select
484 case OPTION_IGNORECASE:
485 vis->ignorecase = toggle ? !vis->ignorecase : arg.b;
487 + case OPTION_BREAKAT:
488 + view_breakat_set(win->view, arg.s);
490 + case OPTION_WRAP_COLUMN:
491 + view_wrapcolumn_set(win->view, arg.i);
497 From ee36292c44370678f261ea843c3ebcf02fa19156 Mon Sep 17 00:00:00 2001
498 From: Andrey Proskurin <>
499 Date: Fri, 14 May 2021 16:44:44 +0000
500 Subject: [PATCH 4/5] view.c: check return value of strdup
503 view.c | 32 +++++++++++++++++---------------
506 3 files changed, 22 insertions(+), 17 deletions(-)
508 diff --git a/view.c b/view.c
509 index 79fc7bc1..f1864e8b 100644
512 @@ -273,11 +273,11 @@ static bool view_addch(View *view, Cell *cell) {
513 return view_expand_tab(view, cell);
515 return view_expand_newline(view, cell);
518 const char *symbol = view->symbols[SYNTAX_SYMBOL_SPACE]->symbol;
519 strncpy(cell->data, symbol, sizeof(cell->data) - 1);
520 return view_add_cell(view, cell);
524 if (ch < 128 && !isprint(ch)) {
525 /* non-printable ascii char, represent it as ^(char + 64) */
526 @@ -541,29 +541,27 @@ View *view_new(Text *text) {
527 View *view = calloc(1, sizeof(View));
531 - if (!view_selections_new(view, 0)) {
537 + view->tabwidth = 8;
538 + view->breakat = strdup("");
539 + view->wrapcolumn = 0;
540 view->cell_blank = (Cell) {
545 - view->tabwidth = 8;
546 - view->breakat = strdup("");
547 - view->wrapcolumn = 0;
548 view_options_set(view, 0);
550 - if (!view_resize(view, 1, 1)) {
551 + if (!view->breakat ||
552 + !view_selections_new(view, 0) ||
553 + !view_resize(view, 1, 1))
560 view_cursor_to(view, 0);
565 @@ -903,9 +901,13 @@ void view_wrapcolumn_set(View *view, int col) {
566 view->wrapcolumn = col;
569 -void view_breakat_set(View *view, const char *breakat) {
570 +bool view_breakat_set(View *view, const char *breakat) {
571 + char *copy = strdup(breakat);
575 - view->breakat = strdup(breakat);
576 + view->breakat = copy;
580 size_t view_screenline_goto(View *view, int n) {
581 diff --git a/view.h b/view.h
582 index 65bcb29d..dadecb48 100644
585 @@ -359,7 +359,7 @@ enum UiOption view_options_get(View*);
586 void view_colorcolumn_set(View*, int col);
587 int view_colorcolumn_get(View*);
588 void view_wrapcolumn_set(View*, int col);
589 -void view_breakat_set(View*, const char *breakat);
590 +bool view_breakat_set(View*, const char *breakat);
592 /** Set how many spaces are used to display a tab `\t` character. */
593 void view_tabwidth_set(View*, int tabwidth);
594 diff --git a/vis-cmds.c b/vis-cmds.c
595 index e2bff70d..d3b5f89a 100644
598 @@ -365,7 +365,10 @@ static bool cmd_set(Vis *vis, Win *win, Command *cmd, const char *argv[], Select
599 vis->ignorecase = toggle ? !vis->ignorecase : arg.b;
602 - view_breakat_set(win->view, arg.s);
603 + if (!view_breakat_set(win->view, arg.s)) {
604 + vis_info_show(vis, "Failed to set breakat");
608 case OPTION_WRAP_COLUMN:
609 view_wrapcolumn_set(win->view, arg.i);
611 From f698e53e4772497c41a12288339e3841dbca9680 Mon Sep 17 00:00:00 2001
612 From: Andrey Proskurin <andreyproskurin@protonmail.com>
613 Date: Fri, 14 May 2021 18:46:20 +0000
614 Subject: [PATCH 5/5] view.c: add utf-8 support to `breakat`
618 1 file changed, 1 insertion(+), 1 deletion(-)
620 diff --git a/view.c b/view.c
621 index f1864e8b..a399dd02 100644
624 @@ -260,7 +260,7 @@ static bool view_addch(View *view, Cell *cell) {
627 unsigned char ch = (unsigned char)cell->data[0];
628 - bool ch_breakat = strchr(view->breakat, ch);
629 + bool ch_breakat = strstr(view->breakat, cell->data);
630 if (view->prevch_breakat && !ch_breakat) {
631 /* this is a good place to wrap line if needed */
632 view->wrapcol = view->col;