vis

a vi-like editor based on Plan 9's structural regular expressions

git clone https://9o.is/git/vis.git

view.c

(36650B)


      1 #include <ctype.h>
      2 #include <errno.h>
      3 #include <limits.h>
      4 #include <stdio.h>
      5 #include <stdlib.h>
      6 #include <string.h>
      7 #include <wchar.h>
      8 
      9 #include "vis-core.h"
     10 #include "view.h"
     11 #include "text.h"
     12 #include "text-motions.h"
     13 #include "text-util.h"
     14 #include "util.h"
     15 
     16 /* A selection is made up of two marks named cursor and anchor.
     17  * While the anchor remains fixed the cursor mark follows cursor motions.
     18  * For a selection (indicated by []), the marks (^) are placed as follows:
     19  *
     20  *     [some text]              [!]
     21  *      ^       ^                ^
     22  *                               ^
     23  *
     24  * That is the marks point to the *start* of the first and last character
     25  * of the selection. In particular for a single character selection (as
     26  * depicted on the right above) both marks point to the same location.
     27  *
     28  * The view_selections_{get,set} functions take care of adding/removing
     29  * the necessary offset for the last character.
     30  */
     31 
     32 static const char *symbols_none[] = {
     33 	[SYNTAX_SYMBOL_SPACE]    = " ",
     34 	[SYNTAX_SYMBOL_TAB]      = " ",
     35 	[SYNTAX_SYMBOL_TAB_FILL] = " ",
     36 	[SYNTAX_SYMBOL_EOL]      = " ",
     37 	[SYNTAX_SYMBOL_EOF]      = " ",
     38 };
     39 
     40 static const char *symbols_default[] = {
     41 	[SYNTAX_SYMBOL_SPACE]    = "·", /* Middle Dot U+00B7 */
     42 	[SYNTAX_SYMBOL_TAB]      = "›", /* Single Right-Pointing Angle Quotation Mark U+203A */
     43 	[SYNTAX_SYMBOL_TAB_FILL] = " ",
     44 	[SYNTAX_SYMBOL_EOL]      = "↵", /* Downwards Arrow with Corner Leftwards U+21B5 */
     45 	[SYNTAX_SYMBOL_EOF]      = "~",
     46 };
     47 
     48 static Cell cell_blank = { .width = 0, .len = 0, .data = " ", };
     49 
     50 /* move visible viewport n-lines up/down, redraws the view but does not change
     51  * cursor position which becomes invalid and should be corrected by calling
     52  * view_cursors_to. the return value indicates whether the visible area changed.
     53  */
     54 static bool view_viewport_up(View *view, int n);
     55 static bool view_viewport_down(View *view, int n);
     56 
     57 static void view_clear(View *view);
     58 static bool view_add_cell(View *view, const Cell *cell);
     59 static bool view_addch(View *view, Cell *cell);
     60 static void selection_free(Selection*);
     61 /* set/move current cursor position to a given (line, column) pair */
     62 static size_t cursor_set(Selection*, Line *line, int col);
     63 
     64 void window_status_update(Vis *vis, Win *win) {
     65 	char left_parts[4][255] = { "", "", "", "" };
     66 	char right_parts[4][32] = { "", "", "", "" };
     67 	char left[sizeof(left_parts)+LENGTH(left_parts)*8];
     68 	char right[sizeof(right_parts)+LENGTH(right_parts)*8];
     69 	char status[sizeof(left)+sizeof(right)+1];
     70 	size_t left_count = 0;
     71 	size_t right_count = 0;
     72 
     73 	View *view = &win->view;
     74 	File *file = win->file;
     75 	Text *txt = file->text;
     76 	int width = win->width;
     77 	enum UiOption options = win->options;
     78 	bool focused = vis->win == win;
     79 	const char *filename = file_name_get(file);
     80 	const char *mode = vis->mode->status;
     81 
     82 	if (focused && mode)
     83 		strcpy(left_parts[left_count++], mode);
     84 
     85 	snprintf(left_parts[left_count++], sizeof(left_parts[0]), "%s%s%s",
     86 	         filename ? filename : "[No Name]",
     87 	         text_modified(txt) ? " [+]" : "",
     88 	         vis_macro_recording(vis) ? " @": "");
     89 
     90 	int count = vis->action.count;
     91 	const char *keys = buffer_content0(&vis->input_queue);
     92 	if (keys && keys[0])
     93 		snprintf(right_parts[right_count++], sizeof(right_parts[0]), "%s", keys);
     94 	else if (count != VIS_COUNT_UNKNOWN)
     95 		snprintf(right_parts[right_count++], sizeof(right_parts[0]), "%d", count);
     96 
     97 	int sel_count = view->selection_count;
     98 	if (sel_count > 1) {
     99 		Selection *s = view_selections_primary_get(view);
    100 		int sel_number = view_selections_number(s) + 1;
    101 		snprintf(right_parts[right_count++], sizeof(right_parts[0]),
    102 		         "%d/%d", sel_number, sel_count);
    103 	}
    104 
    105 	size_t size = text_size(txt);
    106 	size_t pos = view_cursor_get(view);
    107 	size_t percent = 0;
    108 	if (size > 0) {
    109 		double tmp = ((double)pos/(double)size)*100;
    110 		percent = (size_t)(tmp+1);
    111 	}
    112 	snprintf(right_parts[right_count++], sizeof(right_parts[0]),
    113 	         "%zu%%", percent);
    114 
    115 	if (!(options & UI_OPTION_LARGE_FILE)) {
    116 		Selection *sel = view_selections_primary_get(&win->view);
    117 		size_t line = view_cursors_line(sel);
    118 		size_t col = view_cursors_col(sel);
    119 		if (col > UI_LARGE_FILE_LINE_SIZE) {
    120 			options |= UI_OPTION_LARGE_FILE;
    121 			win_options_set(win, options);
    122 		}
    123 		snprintf(right_parts[right_count++], sizeof(right_parts[0]),
    124 		         "%zu, %zu", line, col);
    125 	}
    126 
    127 	int left_len = snprintf(left, sizeof(left), " %s%s%s%s%s%s%s",
    128 	         left_parts[0],
    129 	         left_parts[1][0] ? " » " : "",
    130 	         left_parts[1],
    131 	         left_parts[2][0] ? " » " : "",
    132 	         left_parts[2],
    133 	         left_parts[3][0] ? " » " : "",
    134 	         left_parts[3]);
    135 
    136 	int right_len = snprintf(right, sizeof(right), "%s%s%s%s%s%s%s ",
    137 	         right_parts[0],
    138 	         right_parts[1][0] ? " « " : "",
    139 	         right_parts[1],
    140 	         right_parts[2][0] ? " « " : "",
    141 	         right_parts[2],
    142 	         right_parts[3][0] ? " « " : "",
    143 	         right_parts[3]);
    144 
    145 	if (left_len < 0 || right_len < 0)
    146 		return;
    147 	int left_width = text_string_width(left, left_len);
    148 	int right_width = text_string_width(right, right_len);
    149 
    150 	int spaces = width - left_width - right_width;
    151 	if (spaces < 1)
    152 		spaces = 1;
    153 
    154 	snprintf(status, sizeof(status), "%s%*s%s", left, spaces, " ", right);
    155 	ui_window_status(win, status);
    156 }
    157 
    158 void view_tabwidth_set(View *view, int tabwidth) {
    159 	if (tabwidth < 1 || tabwidth > 8)
    160 		return;
    161 	view->tabwidth = tabwidth;
    162 	view_draw(view);
    163 }
    164 
    165 /* reset internal view data structures (cell matrix, line offsets etc.) */
    166 static void view_clear(View *view) {
    167 	memset(view->lines, 0, view->lines_size);
    168 	if (view->start != view->start_last) {
    169 		if (view->start == 0)
    170 			view->start_mark = EMARK;
    171 		else
    172 			view->start_mark = text_mark_set(view->text, view->start);
    173 	} else {
    174 		size_t start;
    175 		if (view->start_mark == EMARK)
    176 			start = 0;
    177 		else
    178 			start = text_mark_get(view->text, view->start_mark);
    179 		if (start != EPOS)
    180 			view->start = start;
    181 	}
    182 
    183 	view->start_last = view->start;
    184 	view->topline = view->lines;
    185 	view->topline->lineno = view->large_file ? 1 : text_lineno_by_pos(view->text, view->start);
    186 	view->lastline = view->topline;
    187 
    188 	size_t line_size = sizeof(Line) + view->width*sizeof(Cell);
    189 	size_t end = view->height * line_size;
    190 	Line *prev = NULL;
    191 	for (size_t i = 0; i < end; i += line_size) {
    192 		Line *line = (Line*)(((char*)view->lines) + i);
    193 		line->prev = prev;
    194 		if (prev)
    195 			prev->next = line;
    196 		prev = line;
    197 	}
    198 	view->bottomline = prev ? prev : view->topline;
    199 	view->bottomline->next = NULL;
    200 	view->line = view->topline;
    201 	view->col = 0;
    202 	view->wrapcol = 0;
    203 	view->prevch_breakat = false;
    204 
    205 	/* FIXME: awful garbage that only exists because every
    206 	 * struct in this program is an interdependent hellscape */
    207 	Win *win = (Win *)((char *)view - offsetof(Win, view));
    208 	ui_window_style_set(&win->vis->ui, win->id, &cell_blank, UI_STYLE_DEFAULT, false);
    209 }
    210 
    211 static int view_max_text_width(const View *view) {
    212 	if (view->wrapcolumn > 0)
    213 		return MIN(view->wrapcolumn, view->width);
    214 	return view->width;
    215 }
    216 
    217 static void view_wrap_line(View *view) {
    218 	Line *wrapped_line = view->line;
    219 	int col = view->col;
    220 	int wrapcol = (view->wrapcol > 0) ? view->wrapcol : view->col;
    221 
    222 	view->line = view->line->next;
    223 	view->col = 0;
    224 	view->wrapcol = 0;
    225 
    226 	if (view->line) {
    227 		view->line->lineno = wrapped_line->lineno;
    228 		/* move extra cells to the next line */
    229 		for (int i = wrapcol; i < col; ++i) {
    230 			const Cell *cell = &wrapped_line->cells[i];
    231 			view->line->width += cell->width;
    232 			view->line->len += cell->len;
    233 			view->line->cells[view->col++] = *cell;
    234 		}
    235 	}
    236 
    237 	/* clear remaining cells on line */
    238 	for (int i = wrapcol; i < view->width; ++i) {
    239 		if (i < col) {
    240 			wrapped_line->width -= wrapped_line->cells[i].width;
    241 			wrapped_line->len -= wrapped_line->cells[i].len;
    242 		}
    243 		wrapped_line->cells[i] = cell_blank;
    244 	}
    245 }
    246 
    247 static bool view_add_cell(View *view, const Cell *cell) {
    248 	/* if the terminal is resized to a single (ASCII) char an out
    249 	 * of bounds write could be performed for a wide char. this can
    250 	 * be caught by iterating through the lines with view_wrap_line()
    251 	 * until no lines remain. usually 0 or 1 iterations.
    252 	 */
    253 	while (view->col + cell->width > view_max_text_width(view)) {
    254 		view_wrap_line(view);
    255 		if (!view->line)
    256 			return false;
    257 	}
    258 
    259 	view->line->width += cell->width;
    260 	view->line->len += cell->len;
    261 	view->line->cells[view->col++] = *cell;
    262 	/* set cells of a character which uses multiple columns */
    263 	for (int i = 1; i < cell->width; i++)
    264 		view->line->cells[view->col++] = (Cell){0};
    265 	return true;
    266 }
    267 
    268 static bool view_expand_tab(View *view, Cell *cell) {
    269 	cell->width = 1;
    270 
    271 	int displayed_width = view->tabwidth - (view->col % view->tabwidth);
    272 	for (int w = 0; w < displayed_width; ++w) {
    273 		int t = (w == 0) ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
    274 		const char *symbol = view->symbols[t];
    275 		strncpy(cell->data, symbol, sizeof(cell->data) - 1);
    276 		cell->len = (w == 0) ? 1 : 0;
    277 
    278 		if (!view_add_cell(view, cell))
    279 			return false;
    280 	}
    281 
    282 	cell->len = 1;
    283 	return true;
    284 }
    285 
    286 static bool view_expand_newline(View *view, Cell *cell) {
    287 	size_t lineno = view->line->lineno;
    288 	const char *symbol = view->symbols[SYNTAX_SYMBOL_EOL];
    289 
    290 	strncpy(cell->data, symbol, sizeof(cell->data) - 1);
    291 	cell->width = 1;
    292 	if (!view_add_cell(view, cell))
    293 		return false;
    294 
    295 	view->wrapcol = 0;
    296 	view_wrap_line(view);
    297 	if (view->line)
    298 		view->line->lineno = lineno + 1;
    299 	return true;
    300 }
    301 
    302 /* try to add another character to the view, return whether there was space left */
    303 static bool view_addch(View *view, Cell *cell) {
    304 	if (!view->line)
    305 		return false;
    306 
    307 	bool ch_breakat = (cell->data[0] != 0) && strstr(view->breakat, cell->data);
    308 	if (view->prevch_breakat && !ch_breakat) {
    309 		/* this is a good place to wrap line if needed */
    310 		view->wrapcol = view->col;
    311 	}
    312 	view->prevch_breakat = ch_breakat;
    313 	cell->style = cell_blank.style;
    314 
    315 	unsigned char ch = (unsigned char)cell->data[0];
    316 	switch (ch) {
    317 	case '\t':
    318 		return view_expand_tab(view, cell);
    319 	case '\n':
    320 		return view_expand_newline(view, cell);
    321 	case ' ': {
    322 		const char *symbol = view->symbols[SYNTAX_SYMBOL_SPACE];
    323 		strncpy(cell->data, symbol, sizeof(cell->data) - 1);
    324 		return view_add_cell(view, cell);
    325 	}}
    326 
    327 	if (ch < 128 && !isprint(ch)) {
    328 		/* non-printable ascii char, represent it as ^(char + 64) */
    329 		*cell = (Cell) {
    330 			.data = { '^', ch == 127 ? '?' : ch + 64, '\0' },
    331 			.len = 1,
    332 			.width = 2,
    333 			.style = cell->style,
    334 		};
    335 	}
    336 	return view_add_cell(view, cell);
    337 }
    338 
    339 static void cursor_to(Selection *s, size_t pos) {
    340 	Text *txt = s->view->text;
    341 	s->cursor = text_mark_set(txt, pos);
    342 	if (!s->anchored)
    343 		s->anchor = s->cursor;
    344 	if (pos != s->pos)
    345 		s->lastcol = 0;
    346 	s->pos = pos;
    347 	if (!view_coord_get(s->view, pos, &s->line, &s->row, &s->col)) {
    348 		if (s->view->selection == s) {
    349 			s->line = s->view->topline;
    350 			s->row = 0;
    351 			s->col = 0;
    352 		}
    353 		return;
    354 	}
    355 	// TODO: minimize number of redraws
    356 	view_draw(s->view);
    357 }
    358 
    359 bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) {
    360 	int row = 0, col = 0;
    361 	size_t cur = view->start;
    362 	Line *line = view->topline;
    363 
    364 	if (pos < view->start || pos > view->end) {
    365 		if (retline) *retline = NULL;
    366 		if (retrow) *retrow = -1;
    367 		if (retcol) *retcol = -1;
    368 		return false;
    369 	}
    370 
    371 	while (line && line != view->lastline && cur < pos) {
    372 		if (cur + line->len > pos)
    373 			break;
    374 		cur += line->len;
    375 		line = line->next;
    376 		row++;
    377 	}
    378 
    379 	if (line) {
    380 		int max_col = MIN(view->width, line->width);
    381 		while (cur < pos && col < max_col) {
    382 			cur += line->cells[col].len;
    383 			/* skip over columns occupied by the same character */
    384 			while (++col < max_col && line->cells[col].len == 0);
    385 		}
    386 	} else {
    387 		line = view->bottomline;
    388 		row = view->height - 1;
    389 	}
    390 
    391 	if (retline) *retline = line;
    392 	if (retrow) *retrow = row;
    393 	if (retcol) *retcol = col;
    394 	return true;
    395 }
    396 
    397 /* redraw the complete with data starting from view->start bytes into the file.
    398  * stop once the screen is full, update view->end, view->lastline */
    399 void view_draw(View *view) {
    400 	view_clear(view);
    401 	/* read a screenful of text considering each character as 4-byte UTF character*/
    402 	const size_t size = view->width * view->height * 4;
    403 	/* current buffer to work with */
    404 	char *text = view->textbuf;
    405 	/* remaining bytes to process in buffer */
    406 	size_t rem = text_bytes_get(view->text, view->start, size, text);
    407 	/* NUL terminate text section */
    408 	text[rem] = '\0';
    409 	/* absolute position of character currently being added to display */
    410 	size_t pos = view->start;
    411 	/* current position into buffer from which to interpret a character */
    412 	char *cur = text;
    413 	/* start from known multibyte state */
    414 	mbstate_t mbstate = { 0 };
    415 
    416 	Cell cell = { .data = "", .len = 0, .width = 0, }, prev_cell = cell;
    417 
    418 	while (rem > 0) {
    419 
    420 		/* current 'parsed' character' */
    421 		wchar_t wchar;
    422 
    423 		size_t len = mbrtowc(&wchar, cur, rem, &mbstate);
    424 		if (len == (size_t)-1 && errno == EILSEQ) {
    425 			/* ok, we encountered an invalid multibyte sequence,
    426 			 * replace it with the Unicode Replacement Character
    427 			 * (FFFD) and skip until the start of the next utf8 char */
    428 			mbstate = (mbstate_t){0};
    429 			for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
    430 			cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1 };
    431 		} else if (len == (size_t)-2) {
    432 			/* not enough bytes available to convert to a
    433 			 * wide character. Advance file position and read
    434 			 * another junk into buffer.
    435 			 */
    436 			rem = text_bytes_get(view->text, pos+prev_cell.len, size, text);
    437 			text[rem] = '\0';
    438 			cur = text;
    439 			continue;
    440 		} else if (len == 0) {
    441 			/* NUL byte encountered, store it and continue */
    442 			cell = (Cell){ .data = "\x00", .len = 1, .width = 2 };
    443 		} else {
    444 			if (len >= sizeof(cell.data))
    445 				len = sizeof(cell.data)-1;
    446 			for (size_t i = 0; i < len; i++)
    447 				cell.data[i] = cur[i];
    448 			cell.data[len] = '\0';
    449 			cell.len = len;
    450 			cell.width = wcwidth(wchar);
    451 			if (cell.width == -1)
    452 				cell.width = 1;
    453 		}
    454 
    455 		if (cell.width == 0) {
    456 			strncat(prev_cell.data, cell.data, sizeof(prev_cell.data)-strlen(prev_cell.data)-1);
    457 			prev_cell.len += cell.len;
    458 		} else {
    459 			if (prev_cell.len && !view_addch(view, &prev_cell))
    460 				break;
    461 			pos += prev_cell.len;
    462 			prev_cell = cell;
    463 		}
    464 
    465  		rem -= cell.len;
    466 		cur += cell.len;
    467 
    468 		memset(&cell, 0, sizeof cell);
    469 	}
    470 
    471 	if (prev_cell.len && view_addch(view, &prev_cell))
    472 		pos += prev_cell.len;
    473 
    474 	/* set end of viewing region */
    475 	view->end = pos;
    476 	if (view->line) {
    477 		bool eof = view->end == text_size(view->text);
    478 		if (view->line->len == 0 && eof && view->line->prev)
    479 			view->lastline = view->line->prev;
    480 		else
    481 			view->lastline = view->line;
    482 	} else {
    483 		view->lastline = view->bottomline;
    484 	}
    485 
    486 	/* clear remaining of line, important to show cursor at end of file */
    487 	if (view->line) {
    488 		for (int x = view->col; x < view->width; x++)
    489 			view->line->cells[x] = cell_blank;
    490 	}
    491 
    492 	/* resync position of cursors within visible area */
    493 	for (Selection *s = view->selections; s; s = s->next) {
    494 		size_t pos = view_cursors_pos(s);
    495 		if (!view_coord_get(view, pos, &s->line, &s->row, &s->col) &&
    496 		    s == view->selection) {
    497 			s->line = view->topline;
    498 			s->row = 0;
    499 			s->col = 0;
    500 		}
    501 	}
    502 
    503 	view->need_update = true;
    504 }
    505 
    506 bool view_update(View *view) {
    507 	if (!view->need_update)
    508 		return false;
    509 	for (Line *l = view->lastline->next; l; l = l->next) {
    510 		for (int x = 0; x < view->width; x++)
    511 			l->cells[x] = cell_blank;
    512 	}
    513 	view->need_update = false;
    514 	return true;
    515 }
    516 
    517 bool view_resize(View *view, int width, int height) {
    518 	if (width <= 0)
    519 		width = 1;
    520 	if (height <= 0)
    521 		height = 1;
    522 	if (view->width == width && view->height == height) {
    523 		view->need_update = true;
    524 		return true;
    525 	}
    526 	char *textbuf = malloc(width * height * 4 + 1);
    527 	if (!textbuf)
    528 		return false;
    529 	size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
    530 	if (lines_size > view->lines_size) {
    531 		Line *lines = realloc(view->lines, lines_size);
    532 		if (!lines) {
    533 			free(textbuf);
    534 			return false;
    535 		}
    536 		view->lines = lines;
    537 		view->lines_size = lines_size;
    538 	}
    539 	free(view->textbuf);
    540 	view->textbuf = textbuf;
    541 	view->width = width;
    542 	view->height = height;
    543 	memset(view->lines, 0, view->lines_size);
    544 	view_draw(view);
    545 	return true;
    546 }
    547 
    548 void view_free(View *view) {
    549 	if (!view)
    550 		return;
    551 	while (view->selections)
    552 		selection_free(view->selections);
    553 	free(view->textbuf);
    554 	free(view->lines);
    555 	free(view->breakat);
    556 }
    557 
    558 void view_reload(View *view, Text *text) {
    559 	view->text = text;
    560 	view_selections_clear_all(view);
    561 	view_cursors_to(view->selection, 0);
    562 }
    563 
    564 bool view_init(Win *win, Text *text) {
    565 	View *view = &win->view;
    566 	if (!text)
    567 		return false;
    568 
    569 	view->text = text;
    570 	view->tabwidth = 8;
    571 	view->breakat = strdup("");
    572 	view->wrapcolumn = 0;
    573 	win_options_set(win, 0);
    574 
    575 	if (!view->breakat ||
    576 	    !view_selections_new(view, 0) ||
    577 	    !view_resize(view, 1, 1))
    578 	{
    579 		return false;
    580 	}
    581 
    582 	view_cursors_to(view->selection, 0);
    583 	return true;
    584 }
    585 
    586 static size_t cursor_set(Selection *sel, Line *line, int col) {
    587 	int row = 0;
    588 	View *view = sel->view;
    589 	size_t pos = view->start;
    590 	/* get row number and file offset at start of the given line */
    591 	for (Line *l = view->topline; l && l != line; l = l->next) {
    592 		pos += l->len;
    593 		row++;
    594 	}
    595 
    596 	/* for characters which use more than 1 column, make sure we are on the left most */
    597 	while (col > 0 && line->cells[col].len == 0)
    598 		col--;
    599 	/* calculate offset within the line */
    600 	for (int i = 0; i < col; i++)
    601 		pos += line->cells[i].len;
    602 
    603 	sel->col = col;
    604 	sel->row = row;
    605 	sel->line = line;
    606 
    607 	cursor_to(sel, pos);
    608 
    609 	return pos;
    610 }
    611 
    612 static bool view_viewport_down(View *view, int n) {
    613 	Line *line;
    614 	if (view->end >= text_size(view->text))
    615 		return false;
    616 	if (n >= view->height) {
    617 		view->start = view->end;
    618 	} else {
    619 		for (line = view->topline; line && n > 0; line = line->next, n--)
    620 			view->start += line->len;
    621 	}
    622 	view_draw(view);
    623 	return true;
    624 }
    625 
    626 static bool view_viewport_up(View *view, int n) {
    627 	/* scrolling up is somewhat tricky because we do not yet know where
    628 	 * the lines start, therefore scan backwards but stop at a reasonable
    629 	 * maximum in case we are dealing with a file without any newlines
    630 	 */
    631 	if (view->start == 0)
    632 		return false;
    633 	size_t max = view->width * view->height;
    634 	char c;
    635 	Iterator it = text_iterator_get(view->text, view->start - 1);
    636 
    637 	if (!text_iterator_byte_get(&it, &c))
    638 		return false;
    639 	size_t off = 0;
    640 	/* skip newlines immediately before display area */
    641 	if (c == '\n' && text_iterator_byte_prev(&it, &c))
    642 		off++;
    643 	do {
    644 		if (c == '\n' && --n == 0)
    645 			break;
    646 		if (++off > max)
    647 			break;
    648 	} while (text_iterator_byte_prev(&it, &c));
    649 	view->start -= MIN(view->start, off);
    650 	view_draw(view);
    651 	return true;
    652 }
    653 
    654 void view_redraw_top(View *view) {
    655 	Line *line = view->selection->line;
    656 	for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
    657 		view->start += cur->len;
    658 	view_draw(view);
    659 	/* FIXME: does this logic make sense */
    660 	view_cursors_to(view->selection, view->selection->pos);
    661 }
    662 
    663 void view_redraw_center(View *view) {
    664 	int center = view->height / 2;
    665 	size_t pos = view->selection->pos;
    666 	for (int i = 0; i < 2; i++) {
    667 		int linenr = 0;
    668 		Line *line = view->selection->line;
    669 		for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
    670 			linenr++;
    671 		if (linenr < center) {
    672 			view_slide_down(view, center - linenr);
    673 			continue;
    674 		}
    675 		for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
    676 			view->start += cur->len;
    677 			linenr--;
    678 		}
    679 		break;
    680 	}
    681 	view_draw(view);
    682 	view_cursors_to(view->selection, pos);
    683 }
    684 
    685 void view_redraw_bottom(View *view) {
    686 	size_t pos = view->selection->pos;
    687 	view_viewport_up(view, view->height);
    688 	while (pos >= view->end && view_viewport_down(view, 1));
    689 	cursor_to(view->selection, pos);
    690 }
    691 
    692 size_t view_slide_up(View *view, int lines) {
    693 	Selection *sel = view->selection;
    694 	if (view_viewport_down(view, lines)) {
    695 		if (sel->line == view->topline)
    696 			cursor_set(sel, view->topline, sel->col);
    697 		else
    698 			view_cursors_to(view->selection, sel->pos);
    699 	} else {
    700 		view_screenline_down(sel);
    701 	}
    702 	return sel->pos;
    703 }
    704 
    705 size_t view_slide_down(View *view, int lines) {
    706 	Selection *sel = view->selection;
    707 	bool lastline = sel->line == view->lastline;
    708 	size_t col = sel->col;
    709 	if (view_viewport_up(view, lines)) {
    710 		if (lastline)
    711 			cursor_set(sel, view->lastline, col);
    712 		else
    713 			view_cursors_to(view->selection, sel->pos);
    714 	} else {
    715 		view_screenline_up(sel);
    716 	}
    717 	return sel->pos;
    718 }
    719 
    720 size_t view_scroll_up(View *view, int lines) {
    721 	Selection *sel = view->selection;
    722 	if (view_viewport_up(view, lines)) {
    723 		Line *line = sel->line < view->lastline ? sel->line : view->lastline;
    724 		cursor_set(sel, line, view->selection->col);
    725 	} else {
    726 		view_cursors_to(view->selection, 0);
    727 	}
    728 	return sel->pos;
    729 }
    730 
    731 size_t view_scroll_page_up(View *view) {
    732 	Selection *sel = view->selection;
    733 	if (view->start == 0) {
    734 		view_cursors_to(view->selection, 0);
    735 	} else {
    736 		view_cursors_to(view->selection, view->start-1);
    737 		view_redraw_bottom(view);
    738 		view_screenline_begin(sel);
    739 	}
    740 	return sel->pos;
    741 }
    742 
    743 size_t view_scroll_page_down(View *view) {
    744 	view_scroll_down(view, view->height);
    745 	return view_screenline_begin(view->selection);
    746 }
    747 
    748 size_t view_scroll_halfpage_up(View *view) {
    749 	Selection *sel = view->selection;
    750 	if (view->start == 0) {
    751 		view_cursors_to(view->selection, 0);
    752 	} else {
    753 		view_cursors_to(view->selection, view->start-1);
    754 		view_redraw_center(view);
    755 		view_screenline_begin(sel);
    756 	}
    757 	return sel->pos;
    758 }
    759 
    760 size_t view_scroll_halfpage_down(View *view) {
    761 	size_t end = view->end;
    762 	size_t pos = view_scroll_down(view, view->height/2);
    763 	if (pos < text_size(view->text))
    764 		view_cursors_to(view->selection, end);
    765 	return view->selection->pos;
    766 }
    767 
    768 size_t view_scroll_down(View *view, int lines) {
    769 	Selection *sel = view->selection;
    770 	if (view_viewport_down(view, lines)) {
    771 		Line *line = sel->line > view->topline ? sel->line : view->topline;
    772 		cursor_set(sel, line, sel->col);
    773 	} else {
    774 		view_cursors_to(view->selection, text_size(view->text));
    775 	}
    776 	return sel->pos;
    777 }
    778 
    779 size_t view_line_up(Selection *sel) {
    780 	View *view = sel->view;
    781 	int lastcol = sel->lastcol;
    782 	if (!lastcol)
    783 		lastcol = sel->col;
    784 	size_t pos = text_line_up(sel->view->text, sel->pos);
    785 	bool offscreen = view->selection == sel && pos < view->start;
    786 	view_cursors_to(sel, pos);
    787 	if (offscreen)
    788 		view_redraw_top(view);
    789 	if (sel->line)
    790 		cursor_set(sel, sel->line, lastcol);
    791 	sel->lastcol = lastcol;
    792 	return sel->pos;
    793 }
    794 
    795 size_t view_line_down(Selection *sel) {
    796 	View *view = sel->view;
    797 	int lastcol = sel->lastcol;
    798 	if (!lastcol)
    799 		lastcol = sel->col;
    800 	size_t pos = text_line_down(sel->view->text, sel->pos);
    801 	bool offscreen = view->selection == sel && pos > view->end;
    802 	view_cursors_to(sel, pos);
    803 	if (offscreen)
    804 		view_redraw_bottom(view);
    805 	if (sel->line)
    806 		cursor_set(sel, sel->line, lastcol);
    807 	sel->lastcol = lastcol;
    808 	return sel->pos;
    809 }
    810 
    811 size_t view_screenline_up(Selection *sel) {
    812 	if (!sel->line)
    813 		return view_line_up(sel);
    814 	int lastcol = sel->lastcol;
    815 	if (!lastcol)
    816 		lastcol = sel->col;
    817 	if (!sel->line->prev)
    818 		view_scroll_up(sel->view, 1);
    819 	if (sel->line->prev)
    820 		cursor_set(sel, sel->line->prev, lastcol);
    821 	sel->lastcol = lastcol;
    822 	return sel->pos;
    823 }
    824 
    825 size_t view_screenline_down(Selection *sel) {
    826 	if (!sel->line)
    827 		return view_line_down(sel);
    828 	int lastcol = sel->lastcol;
    829 	if (!lastcol)
    830 		lastcol = sel->col;
    831 	if (!sel->line->next && sel->line == sel->view->bottomline)
    832 		view_scroll_down(sel->view, 1);
    833 	if (sel->line->next)
    834 		cursor_set(sel, sel->line->next, lastcol);
    835 	sel->lastcol = lastcol;
    836 	return sel->pos;
    837 }
    838 
    839 size_t view_screenline_begin(Selection *sel) {
    840 	if (!sel->line)
    841 		return sel->pos;
    842 	return cursor_set(sel, sel->line, 0);
    843 }
    844 
    845 size_t view_screenline_middle(Selection *sel) {
    846 	if (!sel->line)
    847 		return sel->pos;
    848 	return cursor_set(sel, sel->line, sel->line->width / 2);
    849 }
    850 
    851 size_t view_screenline_end(Selection *sel) {
    852 	if (!sel->line)
    853 		return sel->pos;
    854 	int col = sel->line->width - 1;
    855 	return cursor_set(sel, sel->line, col >= 0 ? col : 0);
    856 }
    857 
    858 size_t view_cursor_get(View *view) {
    859 	return view_cursors_pos(view->selection);
    860 }
    861 
    862 void view_scroll_to(View *view, size_t pos) {
    863 	view_cursors_scroll_to(view->selection, pos);
    864 }
    865 
    866 void win_options_set(Win *win, enum UiOption options) {
    867 	const int mapping[] = {
    868 		[SYNTAX_SYMBOL_SPACE]    = UI_OPTION_SYMBOL_SPACE,
    869 		[SYNTAX_SYMBOL_TAB]      = UI_OPTION_SYMBOL_TAB,
    870 		[SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
    871 		[SYNTAX_SYMBOL_EOL]      = UI_OPTION_SYMBOL_EOL,
    872 		[SYNTAX_SYMBOL_EOF]      = UI_OPTION_SYMBOL_EOF,
    873 	};
    874 
    875 	for (int i = 0; i < LENGTH(mapping); i++) {
    876 		win->view.symbols[i] = (options & mapping[i]) ? symbols_default[i] :
    877 			symbols_none[i];
    878 	}
    879 
    880 	if (options & UI_OPTION_LINE_NUMBERS_ABSOLUTE)
    881 		options &= ~UI_OPTION_LARGE_FILE;
    882 
    883 	win->view.large_file = (options & UI_OPTION_LARGE_FILE);
    884 
    885 	ui_window_options_set(win, options);
    886 }
    887 
    888 bool view_breakat_set(View *view, const char *breakat) {
    889 	char *copy = strdup(breakat);
    890 	if (!copy)
    891 		return false;
    892 	free(view->breakat);
    893 	view->breakat = copy;
    894 	return true;
    895 }
    896 
    897 size_t view_screenline_goto(View *view, int n) {
    898 	size_t pos = view->start;
    899 	for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
    900 		pos += line->len;
    901 	return pos;
    902 }
    903 
    904 static Selection *selections_new(View *view, size_t pos, bool force) {
    905 	if (pos > text_size(view->text))
    906 		return NULL;
    907 	Selection *s = calloc(1, sizeof(*s));
    908 	if (!s)
    909 		return NULL;
    910 	s->view = view;
    911 	s->generation = view->selection_generation;
    912 	if (!view->selections) {
    913 		view->selection = s;
    914 		view->selection_latest = s;
    915 		view->selections = s;
    916 		view->selection_count = 1;
    917 		return s;
    918 	}
    919 
    920 	Selection *prev = NULL, *next = NULL;
    921 	Selection *latest = view->selection_latest ? view->selection_latest : view->selection;
    922 	size_t cur = view_cursors_pos(latest);
    923 	if (pos == cur) {
    924 		prev = latest;
    925 		next = prev->next;
    926 	} else if (pos > cur) {
    927 		prev = latest;
    928 		for (next = prev->next; next; prev = next, next = next->next) {
    929 			cur = view_cursors_pos(next);
    930 			if (pos <= cur)
    931 				break;
    932 		}
    933 	} else if (pos < cur) {
    934 		next = latest;
    935 		for (prev = next->prev; prev; next = prev, prev = prev->prev) {
    936 			cur = view_cursors_pos(prev);
    937 			if (pos >= cur)
    938 				break;
    939 		}
    940 	}
    941 
    942 	if (pos == cur && !force)
    943 		goto err;
    944 
    945 	for (Selection *after = next; after; after = after->next)
    946 		after->number++;
    947 
    948 	s->prev = prev;
    949 	s->next = next;
    950 	if (next)
    951 		next->prev = s;
    952 	if (prev) {
    953 		prev->next = s;
    954 		s->number = prev->number + 1;
    955 	} else {
    956 		view->selections = s;
    957 	}
    958 	view->selection_latest = s;
    959 	view->selection_count++;
    960 	view_selections_dispose(view->selection_dead);
    961 	view_cursors_to(s, pos);
    962 	return s;
    963 err:
    964 	free(s);
    965 	return NULL;
    966 }
    967 
    968 Selection *view_selections_new(View *view, size_t pos) {
    969 	return selections_new(view, pos, false);
    970 }
    971 
    972 Selection *view_selections_new_force(View *view, size_t pos) {
    973 	return selections_new(view, pos, true);
    974 }
    975 
    976 int view_selections_number(Selection *sel) {
    977 	return sel->number;
    978 }
    979 
    980 int view_selections_column_count(View *view) {
    981 	Text *txt = view->text;
    982 	int cpl_max = 0, cpl = 0; /* cursors per line */
    983 	size_t line_prev = 0;
    984 	for (Selection *sel = view->selections; sel; sel = sel->next) {
    985 		size_t pos = view_cursors_pos(sel);
    986 		size_t line = text_lineno_by_pos(txt, pos);
    987 		if (line == line_prev)
    988 			cpl++;
    989 		else
    990 			cpl = 1;
    991 		line_prev = line;
    992 		if (cpl > cpl_max)
    993 			cpl_max = cpl;
    994 	}
    995 	return cpl_max;
    996 }
    997 
    998 static Selection *selections_column_next(View *view, Selection *sel, int column) {
    999 	size_t line_cur = 0;
   1000 	int column_cur = 0;
   1001 	Text *txt = view->text;
   1002 	if (sel) {
   1003 		size_t pos = view_cursors_pos(sel);
   1004 		line_cur = text_lineno_by_pos(txt, pos);
   1005 		column_cur = INT_MIN;
   1006 	} else {
   1007 		sel = view->selections;
   1008 	}
   1009 
   1010 	for (; sel; sel = sel->next) {
   1011 		size_t pos = view_cursors_pos(sel);
   1012 		size_t line = text_lineno_by_pos(txt, pos);
   1013 		if (line != line_cur) {
   1014 			line_cur = line;
   1015 			column_cur = 0;
   1016 		} else {
   1017 			column_cur++;
   1018 		}
   1019 		if (column == column_cur)
   1020 			return sel;
   1021 	}
   1022 	return NULL;
   1023 }
   1024 
   1025 Selection *view_selections_column(View *view, int column) {
   1026 	return selections_column_next(view, NULL, column);
   1027 }
   1028 
   1029 Selection *view_selections_column_next(Selection *sel, int column) {
   1030 	return selections_column_next(sel->view, sel, column);
   1031 }
   1032 
   1033 static void selection_free(Selection *s) {
   1034 	if (!s)
   1035 		return;
   1036 	for (Selection *after = s->next; after; after = after->next)
   1037 		after->number--;
   1038 	if (s->prev)
   1039 		s->prev->next = s->next;
   1040 	if (s->next)
   1041 		s->next->prev = s->prev;
   1042 	if (s->view->selections == s)
   1043 		s->view->selections = s->next;
   1044 	if (s->view->selection == s)
   1045 		s->view->selection = s->next ? s->next : s->prev;
   1046 	if (s->view->selection_dead == s)
   1047 		s->view->selection_dead = NULL;
   1048 	if (s->view->selection_latest == s)
   1049 		s->view->selection_latest = s->prev ? s->prev : s->next;
   1050 	s->view->selection_count--;
   1051 	free(s);
   1052 }
   1053 
   1054 bool view_selections_dispose(Selection *sel) {
   1055 	if (!sel)
   1056 		return true;
   1057 	View *view = sel->view;
   1058 	if (!view->selections || !view->selections->next)
   1059 		return false;
   1060 	selection_free(sel);
   1061 	view_selections_primary_set(view->selection);
   1062 	return true;
   1063 }
   1064 
   1065 bool view_selections_dispose_force(Selection *sel) {
   1066 	if (view_selections_dispose(sel))
   1067 		return true;
   1068 	View *view = sel->view;
   1069 	if (view->selection_dead)
   1070 		return false;
   1071 	view_selection_clear(sel);
   1072 	view->selection_dead = sel;
   1073 	return true;
   1074 }
   1075 
   1076 Selection *view_selection_disposed(View *view) {
   1077 	Selection *sel = view->selection_dead;
   1078 	view->selection_dead = NULL;
   1079 	return sel;
   1080 }
   1081 
   1082 Selection *view_selections(View *view) {
   1083 	view->selection_generation++;
   1084 	return view->selections;
   1085 }
   1086 
   1087 Selection *view_selections_primary_get(View *view) {
   1088 	view->selection_generation++;
   1089 	return view->selection;
   1090 }
   1091 
   1092 void view_selections_primary_set(Selection *s) {
   1093 	if (!s)
   1094 		return;
   1095 	s->view->selection = s;
   1096 	Mark anchor = s->anchor;
   1097 	view_cursors_to(s, view_cursors_pos(s));
   1098 	s->anchor = anchor;
   1099 }
   1100 
   1101 Selection *view_selections_prev(Selection *s) {
   1102 	View *view = s->view;
   1103 	for (s = s->prev; s; s = s->prev) {
   1104 		if (s->generation != view->selection_generation)
   1105 			return s;
   1106 	}
   1107 	view->selection_generation++;
   1108 	return NULL;
   1109 }
   1110 
   1111 Selection *view_selections_next(Selection *s) {
   1112 	View *view = s->view;
   1113 	for (s = s->next; s; s = s->next) {
   1114 		if (s->generation != view->selection_generation)
   1115 			return s;
   1116 	}
   1117 	view->selection_generation++;
   1118 	return NULL;
   1119 }
   1120 
   1121 size_t view_cursors_pos(Selection *s) {
   1122 	return text_mark_get(s->view->text, s->cursor);
   1123 }
   1124 
   1125 size_t view_cursors_line(Selection *s) {
   1126 	size_t pos = view_cursors_pos(s);
   1127 	return text_lineno_by_pos(s->view->text, pos);
   1128 }
   1129 
   1130 size_t view_cursors_col(Selection *s) {
   1131 	size_t pos = view_cursors_pos(s);
   1132 	return text_line_char_get(s->view->text, pos) + 1;
   1133 }
   1134 
   1135 int view_cursors_cell_set(Selection *s, int cell) {
   1136 	if (!s->line || cell < 0)
   1137 		return -1;
   1138 	cursor_set(s, s->line, cell);
   1139 	return s->col;
   1140 }
   1141 
   1142 void view_cursors_scroll_to(Selection *s, size_t pos) {
   1143 	View *view = s->view;
   1144 	if (view->selection == s) {
   1145 		view_draw(view);
   1146 		while (pos < view->start && view_viewport_up(view, 1));
   1147 		while (pos > view->end && view_viewport_down(view, 1));
   1148 	}
   1149 	view_cursors_to(s, pos);
   1150 }
   1151 
   1152 void view_cursors_to(Selection *s, size_t pos) {
   1153 	View *view = s->view;
   1154 	if (pos == EPOS)
   1155 		return;
   1156 	size_t size = text_size(view->text);
   1157 	if (pos > size)
   1158 		pos = size;
   1159 	if (s->view->selection == s) {
   1160 		/* make sure we redraw changes to the very first character of the window */
   1161 		if (view->start == pos)
   1162 			view->start_last = 0;
   1163 
   1164 		if (view->end == pos && view->lastline == view->bottomline) {
   1165 			view->start += view->topline->len;
   1166 			view_draw(view);
   1167 		}
   1168 
   1169 		if (pos < view->start || pos > view->end) {
   1170 			view->start = pos;
   1171 			view_viewport_up(view, view->height / 2);
   1172 		}
   1173 
   1174 		if (pos <= view->start || pos > view->end) {
   1175 			view->start = text_line_begin(view->text, pos);
   1176 			view_draw(view);
   1177 		}
   1178 
   1179 		if (pos <= view->start || pos > view->end) {
   1180 			view->start = pos;
   1181 			view_draw(view);
   1182 		}
   1183 	}
   1184 
   1185 	cursor_to(s, pos);
   1186 }
   1187 
   1188 void view_cursors_place(Selection *s, size_t line, size_t col) {
   1189 	Text *txt = s->view->text;
   1190 	size_t pos = text_pos_by_lineno(txt, line);
   1191 	pos = text_line_char_set(txt, pos, col > 0 ? col-1 : col);
   1192 	view_cursors_to(s, pos);
   1193 }
   1194 
   1195 void view_selection_clear(Selection *s) {
   1196 	s->anchored = false;
   1197 	s->anchor = s->cursor;
   1198 	s->view->need_update = true;
   1199 }
   1200 
   1201 void view_selections_flip(Selection *s) {
   1202 	Mark temp = s->anchor;
   1203 	s->anchor = s->cursor;
   1204 	s->cursor = temp;
   1205 	view_cursors_to(s, text_mark_get(s->view->text, s->cursor));
   1206 }
   1207 
   1208 void view_selections_clear_all(View *view) {
   1209 	for (Selection *s = view->selections; s; s = s->next)
   1210 		view_selection_clear(s);
   1211 	view_draw(view);
   1212 }
   1213 
   1214 void view_selections_dispose_all(View *view) {
   1215 	Selection *last = view->selections;
   1216 	while (last->next)
   1217 		last = last->next;
   1218 	for (Selection *s = last, *prev; s; s = prev) {
   1219 		prev = s->prev;
   1220 		if (s != view->selection)
   1221 			selection_free(s);
   1222 	}
   1223 	view_draw(view);
   1224 }
   1225 
   1226 Filerange view_selections_get(Selection *s) {
   1227 	if (!s)
   1228 		return text_range_empty();
   1229 	Text *txt = s->view->text;
   1230 	size_t anchor = text_mark_get(txt, s->anchor);
   1231 	size_t cursor = text_mark_get(txt, s->cursor);
   1232 	Filerange sel = text_range_new(anchor, cursor);
   1233 	if (text_range_valid(&sel))
   1234 		sel.end = text_char_next(txt, sel.end);
   1235 	return sel;
   1236 }
   1237 
   1238 bool view_selections_set(Selection *s, const Filerange *r) {
   1239 	Text *txt = s->view->text;
   1240 	size_t max = text_size(txt);
   1241 	if (!text_range_valid(r) || r->start >= max)
   1242 		return false;
   1243 	size_t anchor = text_mark_get(txt, s->anchor);
   1244 	size_t cursor = text_mark_get(txt, s->cursor);
   1245 	bool left_extending = anchor != EPOS && anchor > cursor;
   1246 	size_t end = r->end > max ? max : r->end;
   1247 	if (r->start != end)
   1248 		end = text_char_prev(txt, end);
   1249 	view_cursors_to(s, left_extending ? r->start : end);
   1250 	s->anchor = text_mark_set(txt, left_extending ? end : r->start);
   1251 	return true;
   1252 }
   1253 
   1254 Filerange view_regions_restore(View *view, SelectionRegion *s) {
   1255 	Text *txt = view->text;
   1256 	size_t anchor = text_mark_get(txt, s->anchor);
   1257 	size_t cursor = text_mark_get(txt, s->cursor);
   1258 	Filerange sel = text_range_new(anchor, cursor);
   1259 	if (text_range_valid(&sel))
   1260 		sel.end = text_char_next(txt, sel.end);
   1261 	return sel;
   1262 }
   1263 
   1264 bool view_regions_save(View *view, Filerange *r, SelectionRegion *s) {
   1265 	Text *txt = view->text;
   1266 	size_t max = text_size(txt);
   1267 	if (!text_range_valid(r) || r->start >= max)
   1268 		return false;
   1269 	size_t end = r->end > max ? max : r->end;
   1270 	if (r->start != end)
   1271 		end = text_char_prev(txt, end);
   1272 	s->anchor = text_mark_set(txt, r->start);
   1273 	s->cursor = text_mark_set(txt, end);
   1274 	return true;
   1275 }
   1276 
   1277 void view_selections_set_all(View *view, Array *arr, bool anchored) {
   1278 	Selection *s;
   1279 	Filerange *r;
   1280 	size_t i = 0;
   1281 	for (s = view->selections; s; s = s->next) {
   1282 		if (!(r = array_get(arr, i++)) || !view_selections_set(s, r)) {
   1283 			for (Selection *next; s; s = next) {
   1284 				next = view_selections_next(s);
   1285 				if (i == 1 && s == view->selection)
   1286 					view_selection_clear(s);
   1287 				else
   1288 					view_selections_dispose(s);
   1289 			}
   1290 			break;
   1291 		}
   1292 		s->anchored = anchored;
   1293 	}
   1294 	while ((r = array_get(arr, i++))) {
   1295 		s = view_selections_new_force(view, r->start);
   1296 		if (!s || !view_selections_set(s, r))
   1297 			break;
   1298 		s->anchored = anchored;
   1299 	}
   1300 	view_selections_primary_set(view->selections);
   1301 }
   1302 
   1303 Array view_selections_get_all(View *view) {
   1304 	Array arr;
   1305 	array_init_sized(&arr, sizeof(Filerange));
   1306 	if (!array_reserve(&arr, view->selection_count))
   1307 		return arr;
   1308 	for (Selection *s = view->selections; s; s = s->next) {
   1309 		Filerange r = view_selections_get(s);
   1310 		if (text_range_valid(&r))
   1311 			array_add(&arr, &r);
   1312 	}
   1313 	return arr;
   1314 }
   1315 
   1316 void view_selections_normalize(View *view) {
   1317 	Selection *prev = NULL;
   1318 	Filerange range_prev = text_range_empty();
   1319 	for (Selection *s = view->selections, *next; s; s = next) {
   1320 		next = s->next;
   1321 		Filerange range = view_selections_get(s);
   1322 		if (!text_range_valid(&range)) {
   1323 			view_selections_dispose(s);
   1324 		} else if (prev && text_range_overlap(&range_prev, &range)) {
   1325 			range_prev = text_range_union(&range_prev, &range);
   1326 			view_selections_dispose(s);
   1327 		} else {
   1328 			if (prev)
   1329 				view_selections_set(prev, &range_prev);
   1330 			range_prev = range;
   1331 			prev = s;
   1332 		}
   1333 	}
   1334 	if (prev)
   1335 		view_selections_set(prev, &range_prev);
   1336 }
   1337 
   1338 void win_style(Win *win, enum UiStyle style, size_t start, size_t end, bool keep_non_default) {
   1339 	View *view = &win->view;
   1340 	if (end < view->start || start > view->end)
   1341 		return;
   1342 
   1343 	size_t pos = view->start;
   1344 	Line *line = view->topline;
   1345 
   1346 	/* skip lines before range to be styled */
   1347 	while (line && pos + line->len <= start) {
   1348 		pos += line->len;
   1349 		line = line->next;
   1350 	}
   1351 
   1352 	if (!line)
   1353 		return;
   1354 
   1355 	int col = 0, width = view->width;
   1356 
   1357 	/* skip columns before range to be styled */
   1358 	while (pos < start && col < width)
   1359 		pos += line->cells[col++].len;
   1360 
   1361 	/* skip empty columns */
   1362 	while (!line->cells[col].len && col < width)
   1363 		col++;
   1364 
   1365 	do {
   1366 		while (pos <= end && col < width) {
   1367 			pos += line->cells[col].len;
   1368 			ui_window_style_set(&win->vis->ui, win->id, &line->cells[col++], style, keep_non_default);
   1369 		}
   1370 		col = 0;
   1371 	} while (pos <= end && (line = line->next));
   1372 }