385 lines
11 KiB
Diff
385 lines
11 KiB
Diff
|
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;
|