aboutsummaryrefslogtreecommitdiff
path: root/view.c
diff options
context:
space:
mode:
authorAndrey Proskurin <andreyproskurin@protonmail.com>2021-05-09 18:17:20 +0000
committerRandy Palamar <palamar@ualberta.ca>2023-07-28 12:46:29 -0600
commit5d7d62c73b258d06efaf5d93026b9e556e4e84d8 (patch)
tree303ae344ee97f48a5bab4d6b98153c2f6e1a17c6 /view.c
parent1a81e0997422f0cbe249095d1b1b8ed5f4a4f983 (diff)
downloadvis-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.c116
1 files changed, 86 insertions, 30 deletions
diff --git a/view.c b/view.c
index e5c110d..0b7ce36 100644
--- 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, 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)