ledit

Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/ledit.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
Log | Files | Refs | README | LICENSE

view.c (71541B)


      1 #include <stdio.h>
      2 #include <errno.h>
      3 #include <string.h>
      4 #include <limits.h>
      5 #include <stdlib.h>
      6 
      7 #include <X11/Xlib.h>
      8 #include <X11/Xutil.h>
      9 #include <X11/Xatom.h>
     10 #include <pango/pangoxft.h>
     11 #include <pango/pango-utils.h> /* for PANGO_VERSION_CHECK */
     12 #include <X11/extensions/Xdbe.h>
     13 
     14 #include "util.h"
     15 #include "pango-compat.h"
     16 #include "memory.h"
     17 #include "common.h"
     18 #include "clipboard.h"
     19 #include "txtbuf.h"
     20 #include "undo.h"
     21 #include "cache.h"
     22 #include "window.h"
     23 #include "buffer.h"
     24 #include "assert.h"
     25 #include "configparser.h"
     26 
     27 /* FIXME: handle selections better - it can happen that the cursor is moved independently of
     28    the selection, leading to weird "jumping selection" - this should be made impossible */
     29 
     30 /* Basic attributes set for all text. */
     31 static PangoAttrList *basic_attrs = NULL;
     32 
     33 /* Initialize line with default values. */
     34 static void init_line(ledit_view *view, ledit_view_line *line);
     35 
     36 /* Copy given selection to x primary selection - must be sorted already. */
     37 static void copy_selection_to_x_primary(ledit_view *view, size_t line1, size_t byte1, size_t line2, size_t byte2);
     38 
     39 /* Callbacks for cache handling */
     40 static void set_pixmap_line_helper(void *data, size_t line, size_t index);
     41 static void invalidate_pixmap_line_helper(void *data, size_t line);
     42 static void set_layout_line_helper(void *data, size_t line, size_t index);
     43 static void invalidate_layout_line_helper(void *data, size_t line);
     44 
     45 /* line_visible_callback just converts void *data to ledit_view *view */
     46 static int view_line_visible(ledit_view *view, size_t index);
     47 static int line_visible_callback(void *data, size_t line);
     48 
     49 /* Redraw just the text. */
     50 static void view_redraw_text(ledit_view *view);
     51 
     52 /* Callbacks */
     53 static void view_button_handler(void *data, XEvent *event);
     54 static void view_scroll_handler(void *view, long pos);
     55 
     56 /* Render a line onto a pixmap that is assigned from the cache. */
     57 static void render_line(ledit_view *view, size_t line_index);
     58 
     59 /* Assign a cache index to line and set text and highlight of the pango layout. */
     60 static void set_pango_text_and_highlight(ledit_view *view, size_t line);
     61 
     62 /*
     63  * Get the pango layout for line.
     64  * This first assigns a cache index (by calling set_pango_text_and_highlight).
     65  */
     66 static PangoLayout *get_pango_layout(ledit_view *view, size_t line);
     67 
     68 /* Get an attribute list for a text highlight between the given range. */
     69 static PangoAttrList *get_pango_attributes(
     70     size_t start_byte, size_t end_byte,
     71     XRenderColor fg, XRenderColor bg
     72 );
     73 
     74 /*
     75  * Set the attributes for a PangoLayout belonging to the given line index.
     76  * If the line is part of the view's selection, the selection is set.
     77  * If that is not the case but cursor_index is set for the line, the character
     78  * at that position is highlighted (this is used for the normal mode cursor).
     79  * Otherwise, the default attributes (basic_attrs) are set.
     80  */
     81 static void set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *layout);
     82 
     83 /* Move the gap of the line gap buffer to index 'index'. */
     84 static void move_line_gap(ledit_view *view, size_t index);
     85 
     86 /*
     87  * Resize the line gap buffer so it can hold at least 'min_size' lines and
     88  * move the gap to line at position 'index'.
     89  */
     90 static void resize_and_move_line_gap(ledit_view *view, size_t min_size, size_t index);
     91 
     92 /* FIXME: This is weird because mode is per-view but the undo mode group
     93    is changed for the entire buffer. */
     94 void
     95 view_set_mode(ledit_view *view, ledit_mode mode) {
     96 	view->mode = mode;
     97 	undo_change_mode_group(view->buffer->undo);
     98 }
     99 
    100 ledit_view *
    101 view_create(ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos) {
    102 	if (basic_attrs == NULL) {
    103 		basic_attrs = pango_attr_list_new();
    104 		#if PANGO_VERSION_CHECK(1, 44, 0)
    105 		PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE);
    106 		pango_attr_list_insert(basic_attrs, no_hyphens);
    107 		#endif
    108 	}
    109 
    110 	ledit_view *view = ledit_malloc(sizeof(ledit_view));
    111 	view->mode = mode;
    112 	view->buffer = buffer;
    113 	view->window = window_create(buffer->common, buffer->clipboard);
    114 	view->cache = cache_create(buffer->common->dpy);
    115 	view->lock_text = NULL;
    116 	view->cur_action = (struct action){ACTION_NONE, NULL};
    117 	window_set_scroll_callback(view->window, &view_scroll_handler, view);
    118 	window_set_button_callback(view->window, &view_button_handler, view);
    119 	window_set_resize_callback(view->window, &view_resize_textview, view);
    120 
    121 	view->lines = ledit_reallocarray(NULL, buffer->lines_cap, sizeof(ledit_view_line));
    122 	view->lines_cap = buffer->lines_cap;
    123 	view->lines_gap = buffer->lines_num;
    124 	view->lines_num = buffer->lines_num;
    125 	for (size_t i = 0; i < view->lines_num; i++) {
    126 		init_line(view, &view->lines[i]);
    127 	}
    128 	view->cur_line = line;
    129 	view->cur_index = pos;
    130 	if (line >= buffer->lines_num)
    131 		view->cur_line = buffer->lines_num - 1;
    132 	ledit_line *ll = buffer_get_line(buffer, view->cur_line);
    133 	if (pos > ll->len)
    134 		pos = 0; /* should never happen anyways */
    135 	view->total_height = 0;
    136 	view->display_offset = 0;
    137 	view->sel.line1 = view->sel.byte1 = 0;
    138 	view->sel.line2 = view->sel.byte2 = 0;
    139 	view->destroy = 0;
    140 	view->button2_pressed = 0;
    141 	view->selecting = 0;
    142 	view->sel_valid = 0;
    143 	view->redraw = 1;
    144 	ledit_view_line *vl = view_get_line(view, line);
    145 	vl->cursor_index = pos;
    146 	vl->cursor_index_valid = 1;
    147 
    148 	view_recalc_all_lines(view);
    149 
    150 	return view;
    151 }
    152 
    153 void
    154 view_lock(ledit_view *view, char *lock_text) {
    155 	free(view->lock_text);
    156 	view->lock_text = ledit_strdup(lock_text);
    157 }
    158 
    159 void
    160 view_unlock(ledit_view *view) {
    161 	free(view->lock_text);
    162 	view->lock_text = NULL;
    163 }
    164 
    165 #if 0
    166 ledit_view_line *
    167 view_get_line(ledit_view *view, size_t index) {
    168 	ledit_assert(index < view->lines_num);
    169 	return index < view->lines_gap ?
    170 	       &view->lines[index] :
    171 	       &view->lines[index + view->lines_cap - view->lines_num];
    172 }
    173 #endif
    174 
    175 static void
    176 move_line_gap(ledit_view *view, size_t index) {
    177 	move_gap(
    178 	    view->lines, sizeof(ledit_view_line), index,
    179 	    view->lines_gap, view->lines_cap, view->lines_num,
    180 	    &view->lines_gap
    181 	);
    182 }
    183 
    184 static void
    185 resize_and_move_line_gap(ledit_view *view, size_t min_size, size_t index) {
    186 	view->lines = resize_and_move_gap(
    187 	    view->lines, sizeof(ledit_view_line),
    188 	    view->lines_gap, view->lines_cap, view->lines_num,
    189 	    min_size, index,
    190 	    &view->lines_gap, &view->lines_cap
    191 	);
    192 }
    193 
    194 /* Checking vl->cursor_index_valid in these notify functions is needed
    195    to avoid re-setting the cursor index for lines that were wiped but
    196    where the line/index of the view hasn't been updated yet (e.g. when
    197    the current line is wiped, then view_delete_range is called to delete
    198    a part, which may cause these notification functions to be called) */
    199 void
    200 view_notify_insert_text(ledit_view *view, size_t line, size_t index, size_t len) {
    201 	int sel_valid = view->sel_valid;
    202 	view_wipe_selection(view);
    203 	ledit_view_line *vl = view_get_line(view, line);
    204 	vl->text_dirty = 1;
    205 	if (line == view->cur_line && index < view->cur_index) {
    206 		view->cur_index += len;
    207 		ledit_view_line *vl = view_get_line(view, line);
    208 		if (vl->cursor_index_valid)
    209 			view_set_line_cursor_attrs(view, line, view->cur_index);
    210 	}
    211 	if (sel_valid)
    212 		view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
    213 }
    214 
    215 void
    216 view_notify_delete_text(ledit_view *view, size_t line, size_t index, size_t len) {
    217 	int sel_valid = view->sel_valid;
    218 	view_wipe_selection(view);
    219 	ledit_view_line *vl = view_get_line(view, line);
    220 	vl->text_dirty = 1;
    221 	if (line == view->cur_line) {
    222 		if (index + len <= view->cur_index) {
    223 			view->cur_index -= len;
    224 		} else if (index < view->cur_index && index + len > view->cur_index) {
    225 			view->cur_index = index;
    226 		}
    227 		/* just so it isn't stuck at end of line after deletion */
    228 		if (view->mode == NORMAL) {
    229 			view->cur_index = view_get_legal_normal_pos(
    230 			    view, view->cur_line, view->cur_index
    231 			);
    232 		}
    233 		ledit_view_line *vl = view_get_line(view, line);
    234 		if (vl->cursor_index_valid)
    235 			view_set_line_cursor_attrs(view, line, view->cur_index);
    236 	}
    237 	if (sel_valid)
    238 		view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
    239 }
    240 
    241 void
    242 view_notify_append_line(ledit_view *view, size_t line) {
    243 	int sel_valid = view->sel_valid;
    244 	view_wipe_selection(view);
    245 	cache_invalidate_from_line(
    246 	    view->cache, line + 1, view,
    247 	    &invalidate_pixmap_line_helper, &invalidate_layout_line_helper
    248 	);
    249 	resize_and_move_line_gap(view, add_sz(view->lines_num, 1), line + 1);
    250 	if (line < view->cur_line)
    251 		view->cur_line++;
    252 	view->lines_num++;
    253 	view->lines_gap++;
    254 	ledit_view_line *vl = view_get_line(view, line + 1);
    255 	init_line(view, vl);
    256 	if (sel_valid)
    257 		view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
    258 }
    259 
    260 void
    261 view_notify_delete_lines(ledit_view *view, size_t index1, size_t index2) {
    262 	/* FIXME: this is needed to avoid some bugs, but maybe check if it breaks anything */
    263 	int sel_valid = view->sel_valid;
    264 	view_wipe_selection(view);
    265 	if (index2 < view->cur_line) {
    266 		view->cur_line -= index2 - index1 + 1;
    267 	} else if (index1 <= view->cur_line) {
    268 		/* FIXME: set cur_index properly */
    269 		if (index2 < view->lines_num - 1) {
    270 			view->cur_line = index1;
    271 			view->cur_index = 0;
    272 		} else if (index1 > 0) {
    273 			view->cur_line = index1 - 1;
    274 			view->cur_index = 0;
    275 		} else {
    276 			/* should never happen */
    277 			view->cur_line = 0;
    278 			view->cur_index = 0;
    279 		}
    280 		ledit_view_line *vl = view_get_line(view, view->cur_line);
    281 		if (vl->cursor_index_valid)
    282 			view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
    283 	}
    284 	cache_invalidate_from_line(
    285 	    view->cache, index1, view,
    286 	    &invalidate_pixmap_line_helper, &invalidate_layout_line_helper
    287 	);
    288 	move_line_gap(view, index1);
    289 	view->lines_num -= index2 - index1 + 1;
    290 	/* possibly decrease size of array - this needs to be after
    291 	   actually deleting the lines so the length is already less */
    292 	size_t min_size = ideal_array_size(view->lines_cap, view->lines_num);
    293 	if (min_size != view->lines_cap)
    294 		resize_and_move_line_gap(view, view->lines_num, view->lines_gap);
    295 	/* force first entry to offset 0 if first line was deleted */
    296 	if (index1 == 0) {
    297 		ledit_view_line *vl = view_get_line(view, 0);
    298 		vl->y_offset = 0;
    299 	}
    300 	if (sel_valid)
    301 		view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
    302 }
    303 
    304 void
    305 view_destroy(ledit_view *view) {
    306 	cache_destroy(view->cache);
    307 	window_destroy(view->window);
    308 	free(view->lock_text);
    309 	free(view->lines);
    310 	free(view);
    311 }
    312 
    313 void
    314 view_cleanup(void) {
    315 	if (basic_attrs)
    316 		pango_attr_list_unref(basic_attrs);
    317 	basic_attrs = NULL;
    318 }
    319 
    320 static PangoAttrList *
    321 get_pango_attributes(size_t start_byte, size_t end_byte, XRenderColor fg, XRenderColor bg) {
    322 	PangoAttribute *attr0 = pango_attr_foreground_new(fg.red, fg.green, fg.blue);
    323 	PangoAttribute *attr1 = pango_attr_background_new(bg.red, bg.green, bg.blue);
    324 	attr0->start_index = start_byte;
    325 	attr0->end_index = end_byte;
    326 	attr1->start_index = start_byte;
    327 	attr1->end_index = end_byte;
    328 	PangoAttrList *list = pango_attr_list_new();
    329 	pango_attr_list_insert(list, attr0);
    330 	pango_attr_list_insert(list, attr1);
    331 	#if PANGO_VERSION_CHECK(1, 44, 0)
    332 	PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE);
    333 	pango_attr_list_insert(list, attr2);
    334 	#endif
    335 	return list;
    336 }
    337 
    338 /* this takes layout directly to possibly avoid infinite recursion */
    339 static void
    340 set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *layout) {
    341 	ledit_theme *theme = config_get_theme();
    342 	ledit_line *ll = buffer_get_line(view->buffer, line);
    343 	ledit_view_line *vl = view_get_line(view, line);
    344 	PangoAttrList *list = NULL;
    345 	if (view->sel_valid) {
    346 		XRenderColor fg = theme->selection_fg.color;
    347 		XRenderColor bg = theme->selection_bg.color;
    348 		ledit_range sel = view->sel;
    349 		sort_range(&sel.line1, &sel.byte1, &sel.line2, &sel.byte2);
    350 		if (sel.line1 < line && sel.line2 > line) {
    351 			list = get_pango_attributes(0, ll->len, fg, bg);
    352 		} else if (sel.line1 == line && sel.line2 == line) {
    353 			size_t start = sel.byte1, end = sel.byte2;
    354 			if (start > end)
    355 				swap_sz(&start, &end);
    356 			list = get_pango_attributes(start, end, fg, bg);
    357 		} else if (sel.line1 == line && sel.line2 > line) {
    358 			list = get_pango_attributes(sel.byte1, ll->len, fg, bg);
    359 		} else if (sel.line1 < line && sel.line2 == line) {
    360 			list = get_pango_attributes(0, sel.byte2, fg, bg);
    361 		}
    362 	} else if (vl->cursor_index_valid) {
    363 		XRenderColor fg = theme->cursor_fg.color;
    364 		XRenderColor bg = theme->cursor_bg.color;
    365 		/* FIXME: does just adding one really do the right thing? */
    366 		list = get_pango_attributes(vl->cursor_index, vl->cursor_index + 1, fg, bg);
    367 	}
    368 	if (list != NULL) {
    369 		pango_layout_set_attributes(layout, list);
    370 		pango_attr_list_unref(list);
    371 	} else {
    372 		pango_layout_set_attributes(layout, basic_attrs);
    373 	}
    374 	vl->highlight_dirty = 0;
    375 	vl->dirty = 1;
    376 }
    377 
    378 void
    379 view_set_line_cursor_attrs(ledit_view *view, size_t line, size_t index) {
    380 	ledit_view_line *ll = view_get_line(view, line);
    381 	ll->cursor_index = index;
    382 	ll->cursor_index_valid = 1;
    383 	ll->highlight_dirty = 1;
    384 	ll->dirty = 1;
    385 	view->redraw = 1;
    386 }
    387 
    388 void
    389 view_wipe_line_cursor_attrs(ledit_view *view, size_t line) {
    390 	ledit_view_line *vl = view_get_line(view, line);
    391 	vl->cursor_index = 0;
    392 	vl->cursor_index_valid = 0;
    393 	vl->highlight_dirty = 1;
    394 	vl->dirty = 1;
    395 	view->redraw = 1;
    396 }
    397 
    398 static int
    399 line_visible_callback(void *data, size_t line) {
    400 	return view_line_visible((ledit_view*)data, line);
    401 }
    402 
    403 /* FIXME: standardize variable names (line/line_index, etc.) */
    404 void
    405 render_line(ledit_view *view, size_t line_index) {
    406 	ledit_theme *theme = config_get_theme();
    407 	/* FIXME: check for <= 0 on size */
    408 	ledit_view_line *ll = view_get_line(view, line_index);
    409 	ledit_assert(!ll->h_dirty); /* FIXME */
    410 	PangoLayout *layout = get_pango_layout(view, line_index);
    411 	if (!ll->cache_pixmap_valid) {
    412 		cache_assign_pixmap_index(
    413 		    view->cache, line_index, view,
    414 		    &line_visible_callback, &set_pixmap_line_helper,
    415 		    &invalidate_pixmap_line_helper
    416 		);
    417 	}
    418 	cache_pixmap *pix = cache_get_pixmap(view->cache, ll->cache_pixmap_index);
    419 	/* FIXME: fail on too large pixmap size (e.g. way too long line) */
    420 	/* FIXME: sensible default pixmap sizes here */
    421 	/* FIXME: handle this in cache */
    422 	if (pix->pixmap == None || pix->draw == NULL) {
    423 		pix->pixmap = XCreatePixmap(
    424 		    view->buffer->common->dpy, view->window->drawable,
    425 		    ll->w + 10, ll->h + 10, view->buffer->common->depth
    426 		);
    427 		pix->w = ll->w + 10;
    428 		pix->h = ll->h + 10;
    429 		pix->draw = XftDrawCreate(
    430 		    view->buffer->common->dpy, pix->pixmap,
    431 		    view->buffer->common->vis, view->buffer->common->cm
    432 		);
    433 	} else if (pix->w < ll->w || pix->h < ll->h) {
    434 		int new_w = ll->w > pix->w ? ll->w + 10 : pix->w + 10;
    435 		int new_h = ll->h > pix->h ? ll->h + 10 : pix->h + 10;
    436 		XFreePixmap(view->buffer->common->dpy, pix->pixmap);
    437 		pix->pixmap = XCreatePixmap(
    438 		    view->buffer->common->dpy, view->window->drawable,
    439 		    new_w, new_h, view->buffer->common->depth
    440 		);
    441 		pix->w = new_w;
    442 		pix->h = new_h;
    443 		XftDrawChange(pix->draw, pix->pixmap);
    444 	}
    445 	XftDrawRect(pix->draw, &theme->text_bg, 0, 0, ll->w, ll->h);
    446 	pango_xft_render_layout(pix->draw, &theme->text_fg, layout, 0, 0);
    447 	ll->dirty = 0;
    448 }
    449 
    450 static void
    451 init_line(ledit_view *view, ledit_view_line *line) {
    452 	int text_w, text_h;
    453 	window_get_textview_size(view->window, &text_w, &text_h);
    454 	line->view = view;
    455 	line->w = text_w;
    456 	line->h = 0;
    457 	line->y_offset = 0;
    458 	line->cache_pixmap_index = 0;
    459 	line->cache_layout_index = 0;
    460 	line->softlines = 0;
    461 	line->cursor_index = 0;
    462 	line->cursor_index_valid = 0;
    463 	line->cache_pixmap_valid = 0;
    464 	line->cache_layout_valid = 0;
    465 	line->dirty = 1;
    466 	line->text_dirty = 1;
    467 	line->highlight_dirty = 1;
    468 	line->h_dirty = 1;
    469 }
    470 
    471 static void
    472 set_pixmap_line_helper(void *data, size_t line, size_t index) {
    473 	ledit_view_line *vl = view_get_line((ledit_view *)data, line);
    474 	vl->cache_pixmap_index = index;
    475 	vl->cache_pixmap_valid = 1;
    476 }
    477 
    478 static void
    479 invalidate_pixmap_line_helper(void *data, size_t line) {
    480 	ledit_view_line *vl = view_get_line((ledit_view *)data, line);
    481 	vl->cache_pixmap_valid = 0;
    482 }
    483 
    484 static void
    485 set_layout_line_helper(void *data, size_t line, size_t index) {
    486 	ledit_view_line *vl = view_get_line((ledit_view *)data, line);
    487 	vl->cache_layout_index = index;
    488 	vl->cache_layout_valid = 1;
    489 }
    490 
    491 static void
    492 invalidate_layout_line_helper(void *data, size_t line) {
    493 	ledit_view_line *vl = view_get_line((ledit_view *)data, line);
    494 	vl->cache_layout_valid = 0;
    495 }
    496 
    497 void
    498 view_recalc_line(ledit_view *view, size_t line) {
    499 	ledit_theme *theme = config_get_theme();
    500 	ledit_view_line *l = view_get_line(view, line);
    501 	if (l->text_dirty)
    502 		set_pango_text_and_highlight(view, line);
    503 
    504 	int text_w, text_h;
    505 	window_get_textview_size(view->window, &text_w, &text_h);
    506 	/* if height changed, set height of current line
    507 	 * and adjust offsets of all lines following it */
    508 	if (l->h_dirty) {
    509 		l->h_dirty = 0;
    510 		/* FIXME: maybe also check overflow for offset? */
    511 		long off = l->y_offset + l->h + theme->extra_line_spacing;
    512 		for (size_t i = line + 1; i < view->lines_num; i++) {
    513 			l = view_get_line(view, i);
    514 			l->y_offset = off;
    515 			off += l->h + theme->extra_line_spacing;
    516 		}
    517 		view->total_height = off - theme->extra_line_spacing;
    518 		if (l->y_offset < view->display_offset + text_h)
    519 			view->redraw = 1;
    520 	}
    521 	l = view_get_line(view, line);
    522 	if (l->y_offset < view->display_offset + text_h &&
    523 	    l->y_offset + l->h >= view->display_offset) {
    524 		view->redraw = 1;
    525 	}
    526 	window_set_scroll_max(view->window, view->total_height);
    527 	view_scroll(view, view->display_offset);
    528 }
    529 
    530 void
    531 view_recalc_from_line(ledit_view *view, size_t line) {
    532 	ledit_theme *theme = config_get_theme();
    533 	ledit_view_line *l = view_get_line(view, line);
    534 	/* force first line to offset 0 */
    535 	if (line == 0)
    536 		l->y_offset = 0;
    537 	int text_w, text_h;
    538 	window_get_textview_size(view->window, &text_w, &text_h);
    539 	long off = l->y_offset;
    540 	if (off < view->display_offset + text_h)
    541 		view->redraw = 1;
    542 	for (size_t i = line; i < view->lines_num; i++) {
    543 		l = view_get_line(view, i);
    544 		if (l->text_dirty)
    545 			set_pango_text_and_highlight(view, i);
    546 		l->h_dirty = 0;
    547 		l->y_offset = off;
    548 		off += l->h + theme->extra_line_spacing;
    549 	}
    550 	view->total_height = off - theme->extra_line_spacing;
    551 	window_set_scroll_max(view->window, view->total_height);
    552 	view_scroll(view, view->display_offset);
    553 }
    554 
    555 void
    556 view_recalc_all_lines(ledit_view *view) {
    557 	view_recalc_from_line(view, 0);
    558 }
    559 
    560 static int
    561 view_line_visible(ledit_view *view, size_t index) {
    562 	int text_w, text_h;
    563 	window_get_textview_size(view->window, &text_w, &text_h);
    564 	ledit_view_line *l = view_get_line(view, index);
    565 	return l->y_offset < view->display_offset + text_h &&
    566 	       l->y_offset + l->h > view->display_offset;
    567 }
    568 
    569 /* FIXME: these functions are only here because they need the PangoLayouts to
    570    determine grapheme boundaries. Maybe use a separate library for that? */
    571 void
    572 view_next_cursor_pos(
    573     ledit_view *view,
    574     size_t line, size_t byte,
    575     int num, int multiline,
    576     size_t *line_ret, size_t *byte_ret) {
    577 	int nattrs;
    578 	ledit_line *ll = buffer_get_line(view->buffer, line);
    579 	size_t c = line_byte_to_char(ll, byte);
    580 	size_t cur_byte = byte;
    581 	PangoLayout *layout = get_pango_layout(view, line);
    582 	const PangoLogAttr *attrs =
    583 	    pango_layout_get_log_attrs_readonly(layout, &nattrs);
    584 	for (int i = 0; i < num; i++) {
    585 		if (cur_byte >= ll->len) {
    586 			if (multiline && line < view->lines_num - 1) {
    587 				line++;
    588 				ll = buffer_get_line(view->buffer, line);
    589 				layout = get_pango_layout(view, line);
    590 				attrs = pango_layout_get_log_attrs_readonly(layout, &nattrs);
    591 				c = 0;
    592 				cur_byte = 0;
    593 				i++;
    594 				continue;
    595 			} else {
    596 				break;
    597 			}
    598 		}
    599 		cur_byte = line_next_utf8(ll, cur_byte);
    600 		for (c++; c < (size_t)nattrs; c++) {
    601 			if (attrs[c].is_cursor_position)
    602 				break;
    603 			cur_byte = line_next_utf8(ll, cur_byte);
    604 		}
    605 	}
    606 	if (line_ret)
    607 		*line_ret = line;
    608 	if (byte_ret)
    609 		*byte_ret = cur_byte <= ll->len ? cur_byte : ll->len;
    610 }
    611 
    612 void
    613 view_prev_cursor_pos(
    614     ledit_view *view,
    615     size_t line, size_t byte,
    616     int num, int multiline,
    617     size_t *line_ret, size_t *byte_ret) {
    618 	int nattrs;
    619 	ledit_line *ll = buffer_get_line(view->buffer, line);
    620 	size_t c = line_byte_to_char(ll, byte);
    621 	size_t cur_byte = byte;
    622 	PangoLayout *layout = get_pango_layout(view, line);
    623 	const PangoLogAttr *attrs =
    624 	    pango_layout_get_log_attrs_readonly(layout, &nattrs);
    625 	for (int i = 0; i < num; i++) {
    626 		if (cur_byte == 0) {
    627 			if (multiline && line > 0) {
    628 				line--;
    629 				ll = buffer_get_line(view->buffer, line);
    630 				layout = get_pango_layout(view, line);
    631 				attrs = pango_layout_get_log_attrs_readonly(layout, &nattrs);
    632 				c = (size_t)nattrs;
    633 				cur_byte = ll->len;
    634 				i++;
    635 				continue;
    636 			} else {
    637 				break;
    638 			}
    639 		}
    640 		cur_byte = line_prev_utf8(ll, cur_byte);
    641 		for (; c-- > 0;) {
    642 			if (attrs[c].is_cursor_position)
    643 				break;
    644 			cur_byte = line_prev_utf8(ll, cur_byte);
    645 		}
    646 	}
    647 	if (line_ret)
    648 		*line_ret = line;
    649 	if (byte_ret)
    650 		*byte_ret = cur_byte;
    651 }
    652 
    653 static int
    654 line_next_word(
    655     ledit_view *view,
    656     size_t line, size_t byte, size_t char_index, int wrapped_line,
    657     size_t *char_ret, size_t *byte_ret, size_t *real_byte_ret) {
    658 	int nattrs;
    659 	ledit_line *ll = buffer_get_line(view->buffer, line);
    660 	int cur_byte = wrapped_line ? byte : line_next_utf8(ll, byte);
    661 	PangoLayout *layout = get_pango_layout(view, line);
    662 	const PangoLogAttr *attrs =
    663 	    pango_layout_get_log_attrs_readonly(layout, &nattrs);
    664 	for (size_t i = wrapped_line ? char_index : char_index + 1; i < (size_t)nattrs; i++) {
    665 		if (attrs[i].is_word_start) {
    666 			*char_ret = i;
    667 			*real_byte_ret = cur_byte;
    668 			*byte_ret = cur_byte;
    669 			return 0;
    670 		}
    671 		cur_byte = line_next_utf8(ll, cur_byte);
    672 	}
    673 	return -1;
    674 }
    675 
    676 static int
    677 line_prev_word(
    678     ledit_view *view,
    679     size_t line, size_t byte, size_t char_index,
    680     size_t *char_ret, size_t *byte_ret) {
    681 	int nattrs;
    682 	ledit_line *ll = buffer_get_line(view->buffer, line);
    683 	size_t cur_byte = line_prev_utf8(ll, byte);
    684 	PangoLayout *layout = get_pango_layout(view, line);
    685 	const PangoLogAttr *attrs =
    686 	    pango_layout_get_log_attrs_readonly(layout, &nattrs);
    687 	if (char_index > (size_t)nattrs - 1)
    688 		char_index = (size_t)nattrs - 1;
    689 	/* this is a bit weird because size_t can't be negative */
    690 	for (size_t i = char_index; i > 0; i--) {
    691 		if (attrs[i-1].is_word_start) {
    692 			*char_ret = i-1;
    693 			*byte_ret = cur_byte;
    694 			return 0;
    695 		}
    696 		cur_byte = line_prev_utf8(ll, cur_byte);
    697 	}
    698 	return -1;
    699 }
    700 
    701 static int
    702 line_prev_bigword(
    703     ledit_view *view,
    704     size_t line, size_t byte, size_t char_index,
    705     size_t *char_ret, size_t *byte_ret) {
    706 	int nattrs;
    707 	ledit_line *ll = buffer_get_line(view->buffer, line);
    708 	size_t cur_byte = line_prev_utf8(ll, byte);
    709 	PangoLayout *layout = get_pango_layout(view, line);
    710 	const PangoLogAttr *attrs =
    711 	    pango_layout_get_log_attrs_readonly(layout, &nattrs);
    712 	size_t next_cursorb = byte;
    713 	size_t next_cursorc = char_index;
    714 	int found_word = 0;
    715 	if (char_index > (size_t)nattrs - 1)
    716 		char_index = (size_t)nattrs - 1;
    717 	/* FIXME: use for (size_t i = ...; i-- > 0;) everywhere */
    718 	/* this is a bit weird because size_t can't be negative */
    719 	for (size_t i = char_index; i > 0; i--) {
    720 		if (!found_word && !attrs[i-1].is_white) {
    721 			found_word = 1;
    722 		} else if (found_word && attrs[i-1].is_white) {
    723 			*char_ret = next_cursorc;
    724 			*byte_ret = next_cursorb;
    725 			return 0;
    726 		}
    727 		if (found_word && i-1 == 0) {
    728 			*char_ret = 0;
    729 			*byte_ret = 0;
    730 			return 0;
    731 		}
    732 		if (attrs[i-1].is_cursor_position) {
    733 			next_cursorc = i-1;
    734 			next_cursorb = cur_byte;
    735 		}
    736 		cur_byte = line_prev_utf8(ll, cur_byte);
    737 	}
    738 	return -1;
    739 }
    740 
    741 static int
    742 line_next_bigword_end(
    743     ledit_view *view,
    744     size_t line, size_t byte, size_t char_index, int wrapped_line,
    745     size_t *char_ret, size_t *byte_ret, size_t *real_byte_ret) {
    746 	int nattrs;
    747 	ledit_line *ll = buffer_get_line(view->buffer, line);
    748 	PangoLayout *layout = get_pango_layout(view, line);
    749 	const PangoLogAttr *attrs =
    750 	    pango_layout_get_log_attrs_readonly(layout, &nattrs);
    751 	size_t last_cursorb = 0, last_cursorc = 0;
    752 	int last_cursor_valid;
    753 	if (wrapped_line) {
    754 		last_cursorb = byte;
    755 		last_cursorc = char_index;
    756 		last_cursor_valid = 1;
    757 	} else {
    758 		last_cursor_valid = 0;
    759 	}
    760 	int found_word = 0;
    761 	size_t cur_byte = byte;
    762 	for (size_t i = char_index; i < (size_t)nattrs; i++) {
    763 		if (last_cursor_valid && !found_word && !attrs[i].is_white) {
    764 			found_word = 1;
    765 		} else if (found_word && attrs[i].is_white) {
    766 			*char_ret = last_cursorc;
    767 			*real_byte_ret = cur_byte;
    768 			*byte_ret = last_cursorb;
    769 			return 0;
    770 		}
    771 		if (attrs[i].is_cursor_position) {
    772 			last_cursorc = i;
    773 			last_cursorb = cur_byte;
    774 			last_cursor_valid = 1;
    775 		}
    776 		cur_byte = line_next_utf8(ll, cur_byte);
    777 	}
    778 	return -1;
    779 }
    780 
    781 static int
    782 line_next_word_end(
    783     ledit_view *view,
    784     size_t line, size_t byte, size_t char_index, int wrapped_line,
    785     size_t *char_ret, size_t *byte_ret, size_t *real_byte_ret) {
    786 	int nattrs;
    787 	ledit_line *ll = buffer_get_line(view->buffer, line);
    788 	size_t cur_byte = line_next_utf8(ll, byte);
    789 	PangoLayout *layout = get_pango_layout(view, line);
    790 	const PangoLogAttr *attrs =
    791 	    pango_layout_get_log_attrs_readonly(layout, &nattrs);
    792 	size_t last_cursorb = 0, last_cursorc = 0;
    793 	int last_cursor_valid;
    794 	if (wrapped_line) {
    795 		last_cursorb = byte;
    796 		last_cursorc = char_index;
    797 		last_cursor_valid = 1;
    798 	} else {
    799 		last_cursor_valid = 0;
    800 	}
    801 	for (size_t i = char_index + 1; i < (size_t)nattrs; i++) {
    802 		if (last_cursor_valid && attrs[i].is_word_end) {
    803 			*char_ret = last_cursorc;
    804 			*real_byte_ret = cur_byte;
    805 			*byte_ret = last_cursorb;
    806 			return 0;
    807 		}
    808 		if (attrs[i].is_cursor_position) {
    809 			last_cursorc = i;
    810 			last_cursorb = cur_byte;
    811 			last_cursor_valid = 1;
    812 		}
    813 		cur_byte = line_next_utf8(ll, cur_byte);
    814 	}
    815 	return -1;
    816 }
    817 
    818 static int
    819 line_next_bigword(
    820     ledit_view *view,
    821     size_t line, size_t byte, size_t char_index, int wrapped_line,
    822     size_t *char_ret, size_t *byte_ret, size_t *real_byte_ret) {
    823 	int nattrs;
    824 	ledit_line *ll = buffer_get_line(view->buffer, line);
    825 	size_t cur_byte = byte;
    826 	PangoLayout *layout = get_pango_layout(view, line);
    827 	const PangoLogAttr *attrs =
    828 	    pango_layout_get_log_attrs_readonly(layout, &nattrs);
    829 	int found_ws = wrapped_line;
    830 	for (size_t i = char_index; i < (size_t)nattrs; i++) {
    831 		if (!found_ws && attrs[i].is_white) {
    832 			found_ws = 1;
    833 		} else if (found_ws && !attrs[i].is_white) {
    834 			*char_ret = i;
    835 			*real_byte_ret = cur_byte;
    836 			*byte_ret = cur_byte;
    837 			return 0;
    838 		}
    839 		cur_byte = line_next_utf8(ll, cur_byte);
    840 	}
    841 	return -1;
    842 }
    843 
    844 size_t
    845 view_line_next_non_whitespace(ledit_view *view, size_t line, size_t byte) {
    846 	int nattrs;
    847 	ledit_line *ll = buffer_get_line(view->buffer, line);
    848 	size_t c = line_byte_to_char(ll, byte);
    849 	size_t cur_byte = byte;
    850 	PangoLayout *layout = get_pango_layout(view, line);
    851 	const PangoLogAttr *attrs =
    852 	    pango_layout_get_log_attrs_readonly(layout, &nattrs);
    853 	for (; c < (size_t)nattrs; c++) {
    854 		if (!attrs[c].is_white)
    855 			return cur_byte;
    856 		cur_byte = line_next_utf8(ll, cur_byte);
    857 	}
    858 	return ll->len;
    859 }
    860 
    861 /* FIXME: document that word and bigword are a bit weird because word uses unicode semantics */
    862 
    863 #define GEN_NEXT_WORD(name, func)                                                          \
    864 void                                                                                       \
    865 view_next_##name(                                                                          \
    866     ledit_view *view,                                                                      \
    867     size_t line, size_t byte, int num_repeat,                                              \
    868     size_t *line_ret, size_t *byte_ret, size_t *real_byte_ret) {                           \
    869 	size_t cur_line = line;                                                            \
    870 	size_t cur_byte = byte;                                                            \
    871 	ledit_line *ll = buffer_get_line(view->buffer, line);                              \
    872 	size_t cur_char = line_byte_to_char(ll, byte);                                     \
    873 	size_t real_byte = 0;                                                              \
    874 	int last_ret = -1;                                                                 \
    875 	int wrapped_line;                                                                  \
    876 	for (int i = 0; i < num_repeat; i++) {                                             \
    877 		wrapped_line = 0;                                                          \
    878 		while ((last_ret = func(view, cur_line, cur_byte, cur_char,                \
    879 		        wrapped_line, &cur_char, &cur_byte, &real_byte)) == -1 &&          \
    880 		       cur_line < view->lines_num - 1) {                                   \
    881 			cur_line++;                                                        \
    882 			cur_byte = 0;                                                      \
    883 			cur_char = 0;                                                      \
    884 			wrapped_line = 1;                                                  \
    885 		}                                                                          \
    886 		if (last_ret == -1 && cur_line == view->lines_num - 1)                     \
    887 			break;                                                             \
    888 	}                                                                                  \
    889 	if (last_ret == -1) {                                                              \
    890 		*line_ret = view->lines_num - 1;                                           \
    891 		ledit_line *ll = buffer_get_line(view->buffer, view->lines_num - 1);       \
    892 		*byte_ret = view_get_legal_normal_pos(view, view->lines_num - 1, ll->len); \
    893 		*real_byte_ret = ll->len;                                                  \
    894 	} else {                                                                           \
    895 		*line_ret = cur_line;                                                      \
    896 		*byte_ret = cur_byte;                                                      \
    897 		*real_byte_ret = real_byte;                                                \
    898 	}                                                                                  \
    899 }
    900 
    901 #define GEN_PREV_WORD(name, func)                                                          \
    902 void                                                                                       \
    903 view_prev_##name(                                                                          \
    904     ledit_view *view,                                                                      \
    905     size_t line, size_t byte, int num_repeat,                                              \
    906     size_t *line_ret, size_t *byte_ret, size_t *real_byte_ret) {                           \
    907 	size_t cur_line = line;                                                            \
    908 	size_t cur_byte = byte;                                                            \
    909 	ledit_line *ll = buffer_get_line(view->buffer, line);                              \
    910 	size_t cur_char = line_byte_to_char(ll, byte);                                     \
    911 	int last_ret = -1;                                                                 \
    912 	for (int i = 0; i < num_repeat; i++) {                                             \
    913 		while ((last_ret = func(view, cur_line, cur_byte, cur_char,                \
    914 		        &cur_char, &cur_byte)) == -1 && cur_line > 0) {                    \
    915 			cur_line--;                                                        \
    916 			ll = buffer_get_line(view->buffer, cur_line);                      \
    917 			cur_byte = ll->len;                                                \
    918 			cur_char = ll->len;                                                \
    919 		}                                                                          \
    920 		if (last_ret == -1 && cur_line == 0)                                       \
    921 			break;                                                             \
    922 	}                                                                                  \
    923 	if (last_ret == -1) {                                                              \
    924 		*line_ret = 0;                                                             \
    925 		*byte_ret = 0;                                                             \
    926 		*real_byte_ret = 0;                                                        \
    927 	} else {                                                                           \
    928 		*line_ret = cur_line;                                                      \
    929 		*byte_ret = cur_byte;                                                      \
    930 		*real_byte_ret = cur_byte;                                                 \
    931 	}                                                                                  \
    932 }
    933 
    934 GEN_NEXT_WORD(word, line_next_word)
    935 GEN_NEXT_WORD(word_end, line_next_word_end)
    936 GEN_NEXT_WORD(bigword, line_next_bigword)
    937 GEN_NEXT_WORD(bigword_end, line_next_bigword_end)
    938 GEN_PREV_WORD(word, line_prev_word)
    939 GEN_PREV_WORD(bigword, line_prev_bigword)
    940 
    941 void
    942 view_get_pos_softline_bounds(
    943     ledit_view *view, size_t line, size_t pos,
    944     size_t *start_byte_ret, size_t *end_byte_ret) {
    945 	ledit_assert(line < view->lines_num);
    946 	ledit_line *ll = buffer_get_line(view->buffer, line);
    947 	ledit_assert(pos <= ll->len);
    948 	PangoLayout *layout = get_pango_layout(view, line);
    949 	int x, sli;
    950 	if (pos > INT_MAX)
    951 		err_overflow();
    952 	pango_layout_index_to_line_x(layout, (int)pos, 0, &sli, &x);
    953 	PangoLayoutLine *pl = pango_layout_get_line_readonly(layout, sli);
    954 	*start_byte_ret = (size_t)pl->start_index;
    955 	*end_byte_ret = (size_t)(pl->start_index + pl->length);
    956 }
    957 
    958 void
    959 view_get_softline_bounds(
    960     ledit_view *view, size_t line, int softline,
    961     size_t *start_byte_ret, size_t *end_byte_ret) {
    962 	ledit_assert(line < view->lines_num);
    963 	ledit_view_line *vl = view_get_line(view, line);
    964 	PangoLayout *layout = get_pango_layout(view, line);
    965 	ledit_assert(softline < vl->softlines);
    966 	PangoLayoutLine *pl = pango_layout_get_line_readonly(layout, softline);
    967 	*start_byte_ret = (size_t)pl->start_index;
    968 	*end_byte_ret = (size_t)(pl->start_index + pl->length);
    969 }
    970 
    971 int
    972 view_get_softline_count(ledit_view *view, size_t line) {
    973 	ledit_assert(line < view->lines_num);
    974 	ledit_view_line *vl = view_get_line(view, line);
    975 	if (vl->text_dirty)
    976 		set_pango_text_and_highlight(view, line);
    977 	return vl->softlines;
    978 }
    979 
    980 int
    981 view_pos_to_softline(ledit_view *view, size_t line, size_t pos) {
    982 	ledit_assert(line < view->lines_num);
    983 	ledit_line *ll = buffer_get_line(view->buffer, line);
    984 	ledit_assert(pos <= ll->len);
    985 	PangoLayout *layout = get_pango_layout(view, line);
    986 	int x, sli;
    987 	if (pos > INT_MAX)
    988 		err_overflow();
    989 	pango_layout_index_to_line_x(layout, (int)pos, 0, &sli, &x);
    990 	return sli;
    991 }
    992 
    993 void
    994 view_get_cursor_pixel_pos(ledit_view *view, size_t line, size_t pos, int *x_ret, int *y_ret, int *h_ret) {
    995 	ledit_assert(line < view->lines_num);
    996 	ledit_line *ll = buffer_get_line(view->buffer, line);
    997 	ledit_assert(pos <= ll->len);
    998 	PangoLayout *layout = get_pango_layout(view, line);
    999 	PangoRectangle strong, weak;
   1000 	if (pos > INT_MAX)
   1001 		err_overflow();
   1002 	pango_layout_get_cursor_pos(layout, (int)pos, &strong, &weak);
   1003 	*x_ret = strong.x / PANGO_SCALE;
   1004 	*y_ret = strong.y / PANGO_SCALE;
   1005 	*h_ret = strong.height / PANGO_SCALE;
   1006 }
   1007 
   1008 /* prev_index_ret is used instead of just calling get_legal_normal_pos
   1009    because weird things happen otherwise
   1010    -> in certain cases, this is still weird because prev_index_ret sometimes
   1011       is not at the end of the line, but this is the best I could come up
   1012       with for now */
   1013 size_t
   1014 view_move_cursor_visually(ledit_view *view, size_t line, size_t pos, int movement, size_t *prev_index_ret) {
   1015 	if (pos > INT_MAX)
   1016 		err_overflow();
   1017 	/* FIXME: trailing */
   1018 	int trailing = 0;
   1019 	ledit_line *cur_line = buffer_get_line(view->buffer, line);
   1020 	PangoLayout *layout = get_pango_layout(view, line);
   1021 	int tmp_index;
   1022 	int new_index = (int)pos, last_index = (int)pos;
   1023 	int dir = 1;
   1024 	int num = movement;
   1025 	if (movement < 0) {
   1026 		dir = -1;
   1027 		num = -movement;
   1028 	}
   1029 	/* FIXME: This is stupid. Anything outside the range of int won't work
   1030 	   anyways because of pango (and because everything else would break
   1031 	   anyways with such long lines), so it's stupid to do all this weird
   1032 	   casting. */
   1033 	if (cur_line->len > INT_MAX)
   1034 		err_overflow();
   1035 	while (num > 0) {
   1036 		tmp_index = new_index;
   1037 		pango_layout_move_cursor_visually(
   1038 		    layout, TRUE,
   1039 		    new_index, trailing, dir,
   1040 		    &new_index, &trailing
   1041 		);
   1042 		/* for some reason, this is necessary */
   1043 		if (new_index < 0)
   1044 			new_index = 0;
   1045 		else if (new_index > (int)cur_line->len)
   1046 			new_index = (int)cur_line->len;
   1047 		num--;
   1048 		if (tmp_index != new_index)
   1049 			last_index = tmp_index;
   1050 	}
   1051 	/* FIXME: Allow cursor to be at end of soft line */
   1052 	/* we don't currently support a difference between the cursor being at
   1053 	   the end of a soft line and the beginning of the next line */
   1054 	/* FIXME: spaces at end of softlines are weird in normal mode */
   1055 	while (trailing > 0) {
   1056 		trailing--;
   1057 		new_index = line_next_utf8(cur_line, new_index);
   1058 	}
   1059 	if (new_index < 0)
   1060 		new_index = 0;
   1061 	if (prev_index_ret)
   1062 		*prev_index_ret = (size_t)last_index;
   1063 	return (size_t)new_index;
   1064 }
   1065 
   1066 /* FIXME: implement */
   1067 /*
   1068 int
   1069 ledit_line_nearest_cursor_pos(ledit_line *line, int byte) {
   1070 }
   1071 
   1072 void
   1073 ledit_line_word_boundaries(ledit_line *line, int byte, int *start_ret, int *end_ret) {
   1074 }
   1075 */
   1076 
   1077 static void
   1078 set_pango_text_and_highlight(ledit_view *view, size_t line) {
   1079 	cache_layout *cl;
   1080 	ledit_theme *theme = config_get_theme();
   1081 	ledit_line *ll = buffer_get_line(view->buffer, line);
   1082 	ledit_view_line *vl = view_get_line(view, line);
   1083 	char old_valid = vl->cache_layout_valid;
   1084 	if (!vl->cache_layout_valid) {
   1085 		cache_assign_layout_index(
   1086 		    view->cache, line,
   1087 		    view, &set_layout_line_helper, &invalidate_layout_line_helper
   1088 		);
   1089 		cl = cache_get_layout(view->cache, vl->cache_layout_index);
   1090 	} else {
   1091 		cl = cache_get_layout(view->cache, vl->cache_layout_index);
   1092 	}
   1093 	if (cl->layout == NULL) {
   1094 		cl->layout = pango_layout_new(view->window->context);
   1095 		pango_layout_set_font_description(cl->layout, view->window->font);
   1096 		pango_layout_set_wrap(cl->layout, PANGO_WRAP_WORD_CHAR);
   1097 		pango_layout_set_spacing(cl->layout, theme->extra_line_spacing * PANGO_SCALE);
   1098 	}
   1099 	if (vl->text_dirty || !old_valid) {
   1100 		buffer_normalize_line(ll);
   1101 		if (ll->len > INT_MAX)
   1102 			err_overflow();
   1103 		pango_layout_set_text(cl->layout, ll->text, (int)ll->len);
   1104 		set_line_layout_attrs(view, line, cl->layout);
   1105 		pango_layout_set_width(cl->layout, vl->w * PANGO_SCALE);
   1106 		vl->softlines = pango_layout_get_line_count(cl->layout);
   1107 		int w, h;
   1108 		pango_layout_get_pixel_size(cl->layout, &w, &h);
   1109 		if (h != vl->h) {
   1110 			vl->h = h;
   1111 			vl->h_dirty = 1;
   1112 		}
   1113 		vl->text_dirty = 0;
   1114 		vl->dirty = 1;
   1115 	} else if (vl->highlight_dirty) {
   1116 		set_line_layout_attrs(view, line, cl->layout);
   1117 	}
   1118 	vl->highlight_dirty = 0;
   1119 }
   1120 
   1121 static PangoLayout *
   1122 get_pango_layout(ledit_view *view, size_t line) {
   1123 	set_pango_text_and_highlight(view, line);
   1124 	ledit_view_line *vl = view_get_line(view, line);
   1125 	cache_layout *cl = cache_get_layout(
   1126 	    view->cache, vl->cache_layout_index
   1127 	);
   1128 	return cl->layout;
   1129 }
   1130 
   1131 void
   1132 view_pos_to_x_softline(ledit_view *view, size_t line, size_t pos, int *x_ret, int *softline_ret) {
   1133 	ledit_view_line *vl = view_get_line(view, line);
   1134 	PangoLayout *layout = get_pango_layout(view, line);
   1135 	if (pos > INT_MAX)
   1136 		err_overflow();
   1137 	pango_layout_index_to_line_x(layout, (int)pos, 0, softline_ret, x_ret);
   1138 	PangoLayoutLine *pango_line = pango_layout_get_line_readonly(layout, *softline_ret);
   1139 	/* add left margin to x position if line is aligned right */
   1140 	if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) {
   1141 		PangoRectangle rect;
   1142 		pango_layout_line_get_extents(pango_line, NULL, &rect);
   1143 		*x_ret += (vl->w * PANGO_SCALE - rect.width);
   1144 	}
   1145 	/* if in normal mode, change position to the middle of the
   1146 	   current rectangle so that moving around won't jump weirdly */
   1147 	/* FIXME: also in visual? */
   1148 	/* FIXME: this is too much magic for my taste */
   1149 	if (view->mode == NORMAL) {
   1150 		PangoRectangle rect;
   1151 		pango_layout_index_to_pos(layout, (int)pos, &rect);
   1152 		*x_ret += rect.width / 2;
   1153 	}
   1154 }
   1155 
   1156 size_t
   1157 view_x_softline_to_pos(ledit_view *view, size_t line, int x, int softline) {
   1158 	int trailing = 0;
   1159 	int x_relative = x;
   1160 	ledit_view_line *vl = view_get_line(view, line);
   1161 	PangoLayout *layout = get_pango_layout(view, line);
   1162 	PangoLayoutLine *pango_line =
   1163 	    pango_layout_get_line_readonly(layout, softline);
   1164 	/* x is absolute, so the margin at the left needs to be subtracted */
   1165 	if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) {
   1166 		PangoRectangle rect;
   1167 		pango_layout_line_get_extents(pango_line, NULL, &rect);
   1168 		x_relative -= (vl->w * PANGO_SCALE - rect.width);
   1169 	}
   1170 	int tmp_pos;
   1171 	pango_layout_line_x_to_index(
   1172 	    pango_line, x_relative, &tmp_pos, &trailing
   1173 	);
   1174 	size_t pos = (size_t)tmp_pos;
   1175 	/* if in insert or visual mode, snap to the nearest border between graphemes */
   1176 	/* FIXME: add parameter for this instead of checking mode */
   1177 	if (view->mode == INSERT || view->mode == VISUAL) {
   1178 		ledit_line *ll = buffer_get_line(view->buffer, line);
   1179 		while (trailing > 0) {
   1180 			trailing--;
   1181 			pos = line_next_utf8(ll, pos);
   1182 		}
   1183 	}
   1184 	return pos;
   1185 }
   1186 
   1187 size_t
   1188 view_get_legal_normal_pos(ledit_view *view, size_t line, size_t pos) {
   1189 	/* move back one grapheme if at end of line */
   1190 	size_t ret = pos;
   1191 	ledit_line *ll = buffer_get_line(view->buffer, line);
   1192 	if (pos == ll->len && pos > 0) {
   1193 		int nattrs;
   1194 		PangoLayout *layout = get_pango_layout(view, line);
   1195 		const PangoLogAttr *attrs =
   1196 		    pango_layout_get_log_attrs_readonly(layout, &nattrs);
   1197 		if (nattrs < 2)
   1198 			return 0;
   1199 		size_t cur = nattrs - 2;
   1200 		ret = line_prev_utf8(ll, ret);
   1201 		while (ret > 0 && cur > 0 && !attrs[cur].is_cursor_position) {
   1202 			cur--;
   1203 			ret = line_prev_utf8(ll, ret);
   1204 		}
   1205 	}
   1206 	return ret;
   1207 }
   1208 
   1209 void
   1210 view_delete_range(
   1211     ledit_view *view,
   1212     enum delete_mode delmode, int start_undo_group,
   1213     size_t line_index1, size_t byte_index1,
   1214     size_t line_index2, size_t byte_index2,
   1215     size_t *new_line_ret, size_t *new_byte_ret,
   1216     txtbuf *text_ret) {
   1217 	view_delete_range_base(
   1218 	    view,
   1219 	    delmode, start_undo_group,
   1220 	    line_index1, byte_index1,
   1221 	    line_index2, byte_index2,
   1222 	    new_line_ret, new_byte_ret,
   1223 	    text_ret
   1224 	);
   1225 	/* need to start recalculating one line before in case first
   1226 	   line was deleted and offset is now wrong */
   1227 	size_t min = line_index1 < line_index2 ? line_index1 : line_index2;
   1228 	buffer_recalc_all_views_from_line(
   1229 	    view->buffer, min > 0 ? min - 1 : min
   1230 	);
   1231 }
   1232 
   1233 /* Note: line_index* and byte_index* don't need to be sorted */
   1234 /* line_index1, byte_index1 are used as the cursor position in order
   1235    to determine the new cursor position */
   1236 /* FIXME: use at least somewhat sensible variable names */
   1237 void
   1238 view_delete_range_base(
   1239     ledit_view *view,
   1240     enum delete_mode delmode, int start_undo_group,
   1241     size_t line_index1, size_t byte_index1,
   1242     size_t line_index2, size_t byte_index2,
   1243     size_t *new_line_ret, size_t *new_byte_ret,
   1244     txtbuf *text_ret) {
   1245 	/* FIXME: Oh boy, this is nasty */
   1246 	/* range line x, range byte x */
   1247 	size_t rgl1 = 0, rgb1 = 0, rgl2 = 0, rgb2 = 0;
   1248 	/* line_index1 and byte_index1 are used as cursor start position
   1249 	   -> FIXME: why not just use view->cur_line, view->cur_index here? */
   1250 	size_t cur_line = line_index1;
   1251 	size_t cur_byte = byte_index1;
   1252 	sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2);
   1253 	size_t new_line = 0, new_byte = 0;
   1254 	ledit_assert(line_index1 < view->lines_num);
   1255 	ledit_assert(line_index2 < view->lines_num);
   1256 	ledit_range cur_range = {view->cur_line, view->cur_index, 0, 0};
   1257 	/* FIXME: could this be simplified by just calculating the range and then using
   1258 	   the non-line-based version? */
   1259 	if (delmode == DELETE_HARDLINE) {
   1260 		int x, sl_useless;
   1261 		size_t l1 = line_index1, l2 = line_index2;
   1262 		ledit_line *ll;
   1263 		view_pos_to_x_softline(view, cur_line, cur_byte, &x, &sl_useless);
   1264 		if (l1 > 0 && l2 < view->lines_num - 1) {
   1265 			rgl1 = l1;
   1266 			rgb1 = 0;
   1267 			rgl2 = l2 + 1;
   1268 			rgb2 = 0;
   1269 		} else if (l1 > 0) {
   1270 			rgl1 = l1 - 1;
   1271 			ll = buffer_get_line(view->buffer, rgl1);
   1272 			rgb1 = ll->len;
   1273 			rgl2 = l2;
   1274 			ll = buffer_get_line(view->buffer, rgl2);
   1275 			rgb2 = ll->len;
   1276 		} else if (l2 < view->lines_num - 1) {
   1277 			rgl1 = l1;
   1278 			rgb1 = 0;
   1279 			rgl2 = l2 + 1;
   1280 			rgb2 = 0;
   1281 		} else {
   1282 			rgl1 = l1;
   1283 			rgb1 = 0;
   1284 			rgl2 = l2;
   1285 			ll = buffer_get_line(view->buffer, rgl2);
   1286 			rgb2 = ll->len;
   1287 		}
   1288 		if (l2 < view->lines_num - 1) {
   1289 			new_line = l1;
   1290 			new_byte = view_x_softline_to_pos(
   1291 			    view, l2 + 1, x, 0
   1292 			);
   1293 		} else if (l1 > 0) {
   1294 			new_line = l1 - 1;
   1295 			new_byte = view_x_softline_to_pos(
   1296 			    view, l1 - 1, x, 0
   1297 			);
   1298 		} else {
   1299 			new_line = 0;
   1300 			new_byte = 0;
   1301 		}
   1302 		buffer_delete_with_undo_base(
   1303 		    view->buffer, cur_range,
   1304 		    start_undo_group, view->mode,
   1305 		    rgl1, rgb1, rgl2, rgb2, text_ret
   1306 		);
   1307 	} else if (delmode == DELETE_SOFTLINE) {
   1308 		int x, sl_useless;
   1309 		view_pos_to_x_softline(view, cur_line, cur_byte, &x, &sl_useless);
   1310 		if (line_index1 == line_index2) {
   1311 			int x_useless, l1, l2;
   1312 			ledit_line *line1 = buffer_get_line(view->buffer, line_index1);
   1313 			ledit_view_line *vline1 = view_get_line(view, line_index1);
   1314 			view_pos_to_x_softline(view, line_index1, byte_index1, &x_useless, &l1);
   1315 			view_pos_to_x_softline(view, line_index2, byte_index2, &x_useless, &l2);
   1316 			PangoLayout *layout = get_pango_layout(view, line_index1);
   1317 			PangoLayoutLine *pl1 = pango_layout_get_line_readonly(layout, l1);
   1318 			PangoLayoutLine *pl2 = pango_layout_get_line_readonly(layout, l2);
   1319 			/* don't delete entire line if it is the last one remaining */
   1320 			if (l1 == 0 && l2 == vline1->softlines - 1 && view->lines_num > 1) {
   1321 				if (line_index1 < view->lines_num - 1) {
   1322 					/* cursor can be moved to next hard line */
   1323 					new_line = line_index1;
   1324 					new_byte = view_x_softline_to_pos(
   1325 					    view, line_index1 + 1, x, 0
   1326 					);
   1327 					rgl1 = line_index1;
   1328 					rgb1 = 0;
   1329 					rgl2 = line_index1 + 1;
   1330 					rgb2 = 0;
   1331 				} else {
   1332 					/* cursor has to be be moved to previous hard line
   1333 					   because last line in buffer is deleted */
   1334 					/* note: logically, line_index1 - 1 must be >= 0 because
   1335 					   view->lines_num > 1 && line_index1 >= view->lines_num - 1 */
   1336 					new_line = line_index1 - 1;
   1337 					ledit_line *prevline = buffer_get_line(view->buffer, new_line);
   1338 					ledit_view_line *vprevline = view_get_line(view, new_line);
   1339 					if (vprevline->text_dirty)
   1340 						set_pango_text_and_highlight(view, new_line);
   1341 					new_byte = view_x_softline_to_pos(view, new_line, x, vprevline->softlines - 1);
   1342 					rgl1 = line_index1 - 1;
   1343 					rgb1 = prevline->len;
   1344 					rgl2 = line_index1;
   1345 					rgb2 = line1->len;
   1346 				}
   1347 				buffer_delete_with_undo_base(
   1348 				    view->buffer, cur_range,
   1349 				    start_undo_group, view->mode,
   1350 				    rgl1, rgb1, rgl2, rgb2, text_ret
   1351 				);
   1352 			} else {
   1353 				ledit_assert(pl2->start_index + pl2->length >= pl1->start_index);
   1354 				rgl1 = rgl2 = line_index1;
   1355 				rgb1 = (size_t)pl1->start_index;
   1356 				rgb2 = (size_t)(pl2->start_index + pl2->length);
   1357 				/* the deletion has to happen before calculating the new cursor
   1358 				   position because deleting softlines could change the line
   1359 				   wrapping as well */
   1360 				buffer_delete_with_undo_base(
   1361 				    view->buffer, cur_range,
   1362 				    start_undo_group, view->mode,
   1363 				    rgl1, rgb1, rgl2, rgb2, text_ret
   1364 				);
   1365 				set_pango_text_and_highlight(view, line_index1);
   1366 				vline1 = view_get_line(view, line_index1);
   1367 				if (l1 == vline1->softlines && line_index1 < view->lines_num - 1) {
   1368 					new_line = line_index1 + 1;
   1369 					new_byte = view_x_softline_to_pos(
   1370 					    view, line_index1 + 1, x, 0
   1371 					);
   1372 				} else if (l1 <= vline1->softlines - 1) {
   1373 					new_line = line_index1;
   1374 					new_byte = view_x_softline_to_pos(
   1375 					    view, line_index1, x, l1
   1376 					);
   1377 				} else if (l1 > 0) {
   1378 					new_line = line_index1;
   1379 					new_byte = view_x_softline_to_pos(
   1380 					    view, line_index1, x, l1 - 1
   1381 					);
   1382 				} else {
   1383 					/* the line has been emptied and is the last line remaining */
   1384 					new_line = 0;
   1385 					new_byte = 0;
   1386 				}
   1387 			}
   1388 		} else {
   1389 			int x_useless, sl1, sl2;
   1390 			size_t l1 = line_index1, b1 = byte_index1;
   1391 			size_t l2 = line_index2, b2 = byte_index2;
   1392 			ledit_line *ll2 = buffer_get_line(view->buffer, l2);
   1393 			ledit_view_line *vl2 = view_get_line(view, l2);
   1394 			PangoLayout *layout1 = get_pango_layout(view, l1);
   1395 			PangoLayout *layout2 = get_pango_layout(view, l2);
   1396 			pango_layout_index_to_line_x(layout1, b1, 0, &sl1, &x_useless);
   1397 			pango_layout_index_to_line_x(layout2, b2, 0, &sl2, &x_useless);
   1398 			PangoLayoutLine *pl1 = pango_layout_get_line_readonly(layout1, sl1);
   1399 			PangoLayoutLine *pl2 = pango_layout_get_line_readonly(layout2, sl2);
   1400 			if (sl1 == 0 && sl2 == vl2->softlines - 1) {
   1401 				if (l1 == 0 && l2 == view->lines_num - 1) {
   1402 					rgl1 = l1;
   1403 					rgl2 = l2;
   1404 					rgb1 = 0;
   1405 					rgb2 = ll2->len;
   1406 					new_line = 0;
   1407 					new_byte = 0;
   1408 				} else {
   1409 					if (l2 == view->lines_num - 1) {
   1410 						new_line = l1 - 1;
   1411 						ledit_line *new_lline = buffer_get_line(view->buffer, new_line);
   1412 						ledit_view_line *new_vline = view_get_line(view, new_line);
   1413 						if (new_vline->text_dirty)
   1414 							set_pango_text_and_highlight(view, new_line);
   1415 						new_byte = view_x_softline_to_pos(view, new_line, x, new_vline->softlines - 1);
   1416 						rgl1 = l1 - 1;
   1417 						rgb1 = new_lline->len;
   1418 						rgl2 = l2;
   1419 						rgb2 = ll2->len;
   1420 					} else {
   1421 						new_line = l1;
   1422 						new_byte = view_x_softline_to_pos(
   1423 						    view, l2 + 1, x, 0
   1424 						);
   1425 						rgl1 = l1;
   1426 						rgb1 = 0;
   1427 						rgl2 = l2 + 1;
   1428 						rgb2 = 0;
   1429 					}
   1430 				}
   1431 				buffer_delete_with_undo_base(
   1432 				    view->buffer, cur_range,
   1433 				    start_undo_group, view->mode,
   1434 				    rgl1, rgb1, rgl2, rgb2, text_ret
   1435 				);
   1436 			} else if (sl1 == 0) {
   1437 				rgl1 = l1;
   1438 				rgb1 = 0;
   1439 				rgl2 = l2;
   1440 				rgb2 = (size_t)(pl2->start_index + pl2->length);
   1441 				buffer_delete_with_undo_base(
   1442 				    view->buffer, cur_range,
   1443 				    start_undo_group, view->mode,
   1444 				    rgl1, rgb1, rgl2, rgb2, text_ret
   1445 				);
   1446 				new_line = l1;
   1447 				new_byte = view_x_softline_to_pos(view, l1, x, 0);
   1448 			} else if (sl2 == vl2->softlines - 1) {
   1449 				rgl1 = l1;
   1450 				rgb1 = (size_t)pl1->start_index;
   1451 				rgl2 = l2;
   1452 				rgb2 = ll2->len;
   1453 				if (l2 + 1 == view->lines_num) {
   1454 					new_line = l1;
   1455 					new_byte = view_x_softline_to_pos(view, l1, x, sl1 - 1);
   1456 				} else {
   1457 					new_line = l1 + 1;
   1458 					new_byte = view_x_softline_to_pos(
   1459 					    view, l2 + 1, x, 0
   1460 					);
   1461 				}
   1462 				buffer_delete_with_undo_base(
   1463 				    view->buffer, cur_range,
   1464 				    start_undo_group, view->mode,
   1465 				    rgl1, rgb1, rgl2, rgb2, text_ret
   1466 				);
   1467 			} else {
   1468 				rgl1 = l1;
   1469 				rgb1 = (size_t)pl1->start_index;
   1470 				rgl2 = l2;
   1471 				rgb2 = (size_t)(pl2->start_index + pl2->length);
   1472 				buffer_delete_with_undo_base(
   1473 				    view->buffer, cur_range,
   1474 				    start_undo_group, view->mode,
   1475 				    rgl1, rgb1, rgl2, rgb2, text_ret
   1476 				);
   1477 				new_line = l1;
   1478 				/* important so vl1->softlines is updated */
   1479 				set_pango_text_and_highlight(view, l1);
   1480 				ledit_view_line *vl1 = view_get_line(view, l1);
   1481 				/* it's technically possible that the remaining part of the
   1482 				   second line is so small that it doesn't generate a new
   1483 				   softline, so there needs to be a special case - this is
   1484 				   a bit weird because the cursor will seem to stay on the
   1485 				   same line, but it now includes the rest of the second line
   1486 				   (FIXME: this is probably not the best thing to do) */
   1487 				new_byte = view_x_softline_to_pos(
   1488 				    view, l1, x, sl1 + 1 < vl1->softlines ? sl1 + 1 : sl1
   1489 				);
   1490 			}
   1491 		}
   1492 	} else {
   1493 		rgl1 = line_index1;
   1494 		rgb1 = byte_index1;
   1495 		rgl2 = line_index2;
   1496 		rgb2 = byte_index2;
   1497 		new_line = rgl1;
   1498 		new_byte = rgb1;
   1499 		buffer_delete_with_undo_base(
   1500 		    view->buffer, cur_range,
   1501 		    start_undo_group, view->mode,
   1502 		    rgl1, rgb1, rgl2, rgb2, text_ret
   1503 		);
   1504 	}
   1505 	/* note: line1/byte1 need to be set at the top since deleting text
   1506 	   might change the current line/byte of the view through the notify
   1507 	   functions */
   1508 	cur_range.line2 = new_line;
   1509 	cur_range.byte2 = new_byte;
   1510 	undo_change_last_cur_range(view->buffer->undo, cur_range);
   1511 	/* FIXME: too much magic - maybe don't include this here */
   1512 	if (view->mode == NORMAL)
   1513 		new_byte = view_get_legal_normal_pos(view, new_line, new_byte);
   1514 	if (new_line_ret)
   1515 		*new_line_ret = new_line;
   1516 	if (new_byte_ret)
   1517 		*new_byte_ret = new_byte;
   1518 }
   1519 
   1520 /* FIXME: any way to make this more efficient? */
   1521 void
   1522 view_resize_textview(void *data) {
   1523 	ledit_theme *theme = config_get_theme();
   1524 	ledit_view *view = (ledit_view *)data;
   1525 	view->total_height = 0;
   1526 	int text_w, text_h;
   1527 	window_get_textview_size(view->window, &text_w, &text_h);
   1528 	for (size_t i = 0; i < view->lines_num; i++) {
   1529 		ledit_view_line *line = view_get_line(view, i);
   1530 		line->w = text_w;
   1531 		line->text_dirty = 1; /* it's a bit weird to set this, it should rather be something like 'w_dirty' */
   1532 		set_pango_text_and_highlight(view, i);
   1533 		line->y_offset = view->total_height;
   1534 		line->dirty = 1;
   1535 		line->h_dirty = 0;
   1536 		view->total_height += line->h + theme->extra_line_spacing;
   1537 	}
   1538 	view->total_height -= theme->extra_line_spacing;
   1539 	window_set_scroll_max(view->window, view->total_height);
   1540 	if (view->display_offset > 0 &&
   1541 	    view->display_offset + text_h > view->total_height) {
   1542 		view_scroll(view, view->total_height - text_h);
   1543 	}
   1544 }
   1545 
   1546 void
   1547 view_scroll(ledit_view *view, long new_offset) {
   1548 	int text_w, text_h;
   1549 	window_get_textview_size(view->window, &text_w, &text_h);
   1550 	if (new_offset + text_h > view->total_height)
   1551 		new_offset = view->total_height - text_h;
   1552 	if (new_offset < 0)
   1553 		new_offset = 0;
   1554 	view->display_offset = new_offset;
   1555 	window_set_scroll_pos(view->window, view->display_offset);
   1556 }
   1557 
   1558 /* FIXME: there's gotta be a better/more efficient way to do this... */
   1559 /* FIXME: make sure h_dirty is not set here */
   1560 /* FIXME: this might cause weird effects when used together with extra-line-spacing */
   1561 void
   1562 view_get_nearest_legal_pos(
   1563     ledit_view *view,
   1564     size_t line, size_t byte,
   1565     /*int snap_to_nearest, int snap_middle, FIXME: take these parameters */
   1566     size_t *line_ret, size_t *byte_ret) {
   1567 	PangoRectangle strong, weak;
   1568 	int text_w, text_h;
   1569 	int x, sl_useless;
   1570 	window_get_textview_size(view->window, &text_w, &text_h);
   1571 	ledit_view_line *vline = view_get_line(view, line);
   1572 	PangoLayout *layout = get_pango_layout(view, line);
   1573 	if (byte > INT_MAX)
   1574 		err_overflow();
   1575 	pango_layout_get_cursor_pos(layout, (int)byte, &strong, &weak);
   1576 	view_pos_to_x_softline(view, line, byte, &x, &sl_useless);
   1577 	long cursor_y = strong.y / PANGO_SCALE + vline->y_offset;
   1578 	PangoRectangle ink, log;
   1579 	if (cursor_y < view->display_offset) {
   1580 		/* search for the hard line covering the top of the screen */
   1581 		size_t hline = line;
   1582 		while (vline->y_offset + vline->h <= view->display_offset && hline < view->lines_num - 1) {
   1583 			vline = view_get_line(view, ++hline);
   1584 		}
   1585 		/* the current hard line is now the one at the very top of the screen*/
   1586 		layout = get_pango_layout(view, hline);
   1587 		int num_sl = vline->softlines;
   1588 		int cur_y_off = 0;
   1589 		int sl_index = -1;
   1590 		PangoLayoutLine *sl;
   1591 		/* search for first soft line completely on-screen */
   1592 		for (int i = 0; i < num_sl; i++) {
   1593 			sl = pango_layout_get_line_readonly(layout, i);
   1594 			if (cur_y_off + vline->y_offset >= view->display_offset) {
   1595 				sl_index = i;
   1596 				break;
   1597 			}
   1598 			pango_layout_line_get_pixel_extents(sl, &ink, &log);
   1599 			cur_y_off += log.height;
   1600 		}
   1601 		if (sl_index >= 0) {
   1602 			/* we found the correct soft line */
   1603 			*line_ret = hline;
   1604 			*byte_ret = view_x_softline_to_pos(view, hline, x, sl_index);
   1605 		} else if (hline < view->lines_num - 1) {
   1606 			/* need to move to next hard line */
   1607 			*line_ret = hline + 1;
   1608 			*byte_ret = view_x_softline_to_pos(view, hline + 1, x, 0);
   1609 		} else {
   1610 			/* no idea if this can happen, but just fail and use
   1611 			   the last soft line of the last hard line */
   1612 			*line_ret = hline;
   1613 			*byte_ret = view_x_softline_to_pos(view, hline, x, num_sl - 1);
   1614 		}
   1615 	} else if (cursor_y + strong.height / PANGO_SCALE >
   1616 	           view->display_offset + text_h) {
   1617 		/* search for the hard line covering the bottom of the screen */
   1618 		size_t hline = line;
   1619 		while (vline->y_offset > view->display_offset + text_h && hline > 0) {
   1620 			vline = view_get_line(view, --hline);
   1621 		}
   1622 		/* the current hard line is now the one at the very bottom of the screen*/
   1623 		layout = get_pango_layout(view, hline);
   1624 		int num_sl = vline->softlines;
   1625 		int cur_y_off = 0;
   1626 		int sl_index = -1;
   1627 		PangoLayoutLine *sl;
   1628 		/* search for last soft line completely on-screen */
   1629 		for (int i = num_sl - 1; i >= 0; i--) {
   1630 			sl = pango_layout_get_line_readonly(layout, i);
   1631 			if (vline->y_offset + vline->h - cur_y_off < view->display_offset + text_h) {
   1632 				sl_index = i;
   1633 				break;
   1634 			}
   1635 			pango_layout_line_get_pixel_extents(sl, &ink, &log);
   1636 			cur_y_off += log.height;
   1637 		}
   1638 		if (sl_index >= 0) {
   1639 			/* we found the correct soft line */
   1640 			*line_ret = hline;
   1641 			*byte_ret = view_x_softline_to_pos(view, hline, x, sl_index);
   1642 		} else if (hline > 0) {
   1643 			/* need to move to previous hard line */
   1644 			*line_ret = hline - 1;
   1645 			vline = view_get_line(view, hline - 1);
   1646 			num_sl = vline->softlines;
   1647 			*byte_ret = view_x_softline_to_pos(view, hline - 1, x, num_sl - 1);
   1648 		} else {
   1649 			/* no idea if this can happen, but just fail and use
   1650 			   the first soft line of the first hard line */
   1651 			*line_ret = hline;
   1652 			*byte_ret = view_x_softline_to_pos(view, hline, x, 0);
   1653 		}
   1654 	} else {
   1655 		*line_ret = line;
   1656 		*byte_ret = byte;
   1657 	}
   1658 }
   1659 
   1660 void
   1661 view_xy_to_line_byte(ledit_view *view, int x, int y, int snap_to_nearest, size_t *line_ret, size_t *byte_ret) {
   1662 	ledit_theme *theme = config_get_theme();
   1663 	long pos = view->display_offset + y;
   1664 	for (size_t i = 0; i < view->lines_num; i++) {
   1665 		ledit_view_line *vline = view_get_line(view, i);
   1666 		long y1 = vline->y_offset - (i == 0 ? 0 : theme->extra_line_spacing / 2);
   1667 		long y2 = vline->y_offset + vline->h + theme->extra_line_spacing / 2 + theme->extra_line_spacing % 2;
   1668 		if ((y1 <= pos && y2 > pos) || i == view->lines_num - 1) {
   1669 			int index, trailing;
   1670 			PangoLayout *layout = get_pango_layout(view, i);
   1671 			/* FIXME: what if i == view->lines_num - 1 but pos - h < 0? */
   1672 			pango_layout_xy_to_index(
   1673 			    layout,
   1674 			    x * PANGO_SCALE, (int)(pos - y1) * PANGO_SCALE,
   1675 			    &index, &trailing
   1676 			);
   1677 			*byte_ret = (size_t)index;
   1678 			if (snap_to_nearest) {
   1679 				ledit_line *ll = buffer_get_line(view->buffer, i);
   1680 				while (trailing > 0) {
   1681 					trailing--;
   1682 					*byte_ret = line_next_utf8(ll, *byte_ret);
   1683 				}
   1684 			}
   1685 			*line_ret = i;
   1686 			return;
   1687 		}
   1688 	}
   1689 	*line_ret = 0;
   1690 	*byte_ret = 0;
   1691 }
   1692 
   1693 static void
   1694 scroll_to_pos(ledit_view *view, size_t line, size_t byte, int top) {
   1695 	PangoRectangle strong, weak;
   1696 	int text_w, text_h;
   1697 	window_get_textview_size(view->window, &text_w, &text_h);
   1698 	ledit_view_line *vl = view_get_line(view, line);
   1699 	PangoLayout *layout = get_pango_layout(view, line);
   1700 	if (byte > INT_MAX)
   1701 		err_overflow();
   1702 	pango_layout_get_cursor_pos(layout, (int)byte, &strong, &weak);
   1703 	long cursor_y = strong.y / PANGO_SCALE + vl->y_offset;
   1704 	if (top) {
   1705 		view_scroll(view, cursor_y);
   1706 	} else {
   1707 		view_scroll(view, cursor_y - text_h + strong.height / PANGO_SCALE);
   1708 	}
   1709 }
   1710 
   1711 void
   1712 view_scroll_to_pos_top(ledit_view *view, size_t line, size_t byte) {
   1713 	scroll_to_pos(view, line, byte, 1);
   1714 }
   1715 
   1716 void
   1717 view_scroll_to_pos_bottom(ledit_view *view, size_t line, size_t byte) {
   1718 	scroll_to_pos(view, line, byte, 0);
   1719 }
   1720 
   1721 void
   1722 view_ensure_cursor_shown(ledit_view *view) {
   1723 	PangoRectangle strong, weak;
   1724 	int text_w, text_h;
   1725 	window_get_textview_size(view->window, &text_w, &text_h);
   1726 	ledit_view_line *vline = view_get_line(view, view->cur_line);
   1727 	PangoLayout *layout = get_pango_layout(view, view->cur_line);
   1728 	if (view->cur_index > INT_MAX)
   1729 		err_overflow();
   1730 	pango_layout_get_cursor_pos(
   1731 	    layout, (int)view->cur_index, &strong, &weak
   1732 	);
   1733 	long cursor_y = strong.y / PANGO_SCALE + vline->y_offset;
   1734 	if (cursor_y < view->display_offset) {
   1735 		view_scroll(view, cursor_y);
   1736 	} else if (cursor_y + strong.height / PANGO_SCALE >
   1737 	           view->display_offset + text_h) {
   1738 		view_scroll(view, cursor_y - text_h + strong.height / PANGO_SCALE);
   1739 	}
   1740 }
   1741 
   1742 /* FIXME: don't reset selection when selection is clicked away */
   1743 /* FIXME: when selecting with mouse, only call this when button is released */
   1744 /* lines and bytes need to be sorted already! */
   1745 static void
   1746 copy_selection_to_x_primary(ledit_view *view, size_t line1, size_t byte1, size_t line2, size_t byte2) {
   1747 	txtbuf *primary = clipboard_get_primary_buffer(view->buffer->clipboard);
   1748 	buffer_copy_text_to_txtbuf(view->buffer, primary, line1, byte1, line2, byte2);
   1749 	clipboard_set_primary_selection_owner(view->buffer->clipboard);
   1750 	/*
   1751 	FIXME
   1752 	if (XGetSelectionOwner(state.dpy, XA_PRIMARY) != state.win)
   1753 		selclear();
   1754 	*/
   1755 }
   1756 
   1757 void
   1758 view_wipe_selection(ledit_view *view) {
   1759 	if (view->sel_valid) {
   1760 		if (view->sel.line1 > view->sel.line2)
   1761 			swap_sz(&view->sel.line1, &view->sel.line2);
   1762 		for (size_t i = view->sel.line1; i <= view->sel.line2; i++) {
   1763 			view_wipe_line_cursor_attrs(view, i);
   1764 		}
   1765 	}
   1766 	view->sel_valid = 0;
   1767 	view->sel.line1 = view->sel.line2 = 0;
   1768 	view->sel.byte1 = view->sel.byte2 = 0;
   1769 }
   1770 
   1771 void
   1772 view_set_selection(ledit_view *view, size_t line1, size_t byte1, size_t line2, size_t byte2) {
   1773 	if (view->sel_valid &&
   1774 	    line1 == view->sel.line1 && line2 == view->sel.line2 &&
   1775 	    byte1 == view->sel.byte1 && byte2 == view->sel.byte2) {
   1776 		return;
   1777 	}
   1778 	size_t l1_new = line1, l2_new = line2;
   1779 	size_t b1_new = byte1, b2_new = byte2;
   1780 	sort_range(&l1_new, &b1_new, &l2_new, &b2_new);
   1781 	sort_range(&view->sel.line1, &view->sel.byte1, &view->sel.line2, &view->sel.byte2);
   1782 	/* FIXME: make this a bit nicer and optimize it */
   1783 	/* FIXME: always reset view->sel so bugs like this can't happen where
   1784 	   a function forgets to check sel_valid */
   1785 	if (view->sel_valid) {
   1786 		if (view->sel.line1 > l2_new || view->sel.line2 < l1_new) {
   1787 			for (size_t i = view->sel.line1; i <= view->sel.line2; i++) {
   1788 				view_wipe_line_cursor_attrs(view, i);
   1789 			}
   1790 		} else {
   1791 			for (size_t i = view->sel.line1; i < l1_new; i++) {
   1792 				view_wipe_line_cursor_attrs(view, i);
   1793 			}
   1794 			for (size_t i = view->sel.line2; i > l2_new; i--) {
   1795 				view_wipe_line_cursor_attrs(view, i);
   1796 			}
   1797 		}
   1798 	}
   1799 	for (size_t i = l1_new; i <= l2_new; i++) {
   1800 		/* only change the ones that were not already selected */
   1801 		if (!view->sel_valid || i <= view->sel.line1 || i >= view->sel.line2) {
   1802 			ledit_view_line *vl = view_get_line(view, i);
   1803 			vl->highlight_dirty = 1;
   1804 		}
   1805 	}
   1806 	/* force current line to recalculate in case it wasn't covered above */
   1807 	ledit_view_line *vl = view_get_line(view, line2);
   1808 	vl->highlight_dirty = 1;
   1809 	if (l1_new != l2_new || b1_new != b2_new)
   1810 		copy_selection_to_x_primary(view, l1_new, b1_new, l2_new, b2_new);
   1811 	view->sel.line1 = line1;
   1812 	view->sel.byte1 = byte1;
   1813 	view->sel.line2 = line2;
   1814 	view->sel.byte2 = byte2;
   1815 	view->sel_valid = 1;
   1816 	view->redraw = 1;
   1817 }
   1818 
   1819 static void
   1820 view_scroll_handler(void *view, long pos) {
   1821 	/* FIXME: check for invalid pos? */
   1822 	((ledit_view *)view)->display_offset = pos;
   1823 }
   1824 
   1825 static void
   1826 view_button_handler(void *data, XEvent *event) {
   1827 	size_t l, b;
   1828 	ledit_view *view= (ledit_view *)data;
   1829 	int x, y;
   1830 	int snap;
   1831 	switch (event->type) {
   1832 	case ButtonPress:
   1833 		if (event->xbutton.button == Button1) {
   1834 			x = event->xbutton.x;
   1835 			y = event->xbutton.y;
   1836 			snap = view->mode == NORMAL ? 0 : 1;
   1837 			view_xy_to_line_byte(view, x, y, snap,  &l, &b);
   1838 			view->selecting = 1;
   1839 			view_wipe_line_cursor_attrs(view, view->cur_line);
   1840 			view->cur_line = l;
   1841 			view->cur_index = b;
   1842 			/* don't set selection yet because the mouse may not be
   1843 			   dragged, so we don't want to switch to visual (this
   1844 			   allows setting just the cursor position in normal mode
   1845 			   without always switching to visual) */
   1846 			if (view->mode == NORMAL)
   1847 				view_set_line_cursor_attrs(view, l, b);
   1848 			else if (view->mode == VISUAL)
   1849 				view_set_selection(view, l, b, l, b);
   1850 		} else if (event->xbutton.button == Button2) {
   1851 			view->button2_pressed = 1;
   1852 		}
   1853 		break;
   1854 	case ButtonRelease:
   1855 		/* FIXME: view->button2_pressed should be set to 0 even when
   1856 		   the event does not occur inside the text area */
   1857 		if (event->xbutton.button == Button1) {
   1858 			view->selecting = 0;
   1859 		} else if (event->xbutton.button == Button2) {
   1860 			if (view->button2_pressed && view->mode == INSERT)
   1861 				view_paste_primary(view);
   1862 			view->button2_pressed = 0;
   1863 		}
   1864 		break;
   1865 	case MotionNotify:
   1866 		x = event->xmotion.x;
   1867 		y = event->xmotion.y;
   1868 		if (view->selecting) {
   1869 			y = y >= 0 ? y : 0;
   1870 			view_xy_to_line_byte(view, x, y, 1, &l, &b);
   1871 			if (view->mode == NORMAL) {
   1872 				view_wipe_line_cursor_attrs(view, view->cur_line);
   1873 				/* FIXME: return to old mode afterwards? */
   1874 				/* should change_mode_group even be called here? */
   1875 			}
   1876 			view_set_mode(view, VISUAL);
   1877 			if (!view->sel_valid) {
   1878 				/* the selection has just started, so the current
   1879 				   position is already set to the beginning of the
   1880 				   selection (see case ButtonPress above) */
   1881 				view_set_selection(
   1882 				    view,
   1883 				    view->cur_line, view->cur_index, l, b
   1884 				);
   1885 			} else {
   1886 				view_set_selection(
   1887 				    view,
   1888 				    view->sel.line1, view->sel.byte1, l, b
   1889 				);
   1890 			}
   1891 			view->cur_line = l;
   1892 			view->cur_index = b;
   1893 		}
   1894 		break;
   1895 	}
   1896 }
   1897 
   1898 static void
   1899 view_redraw_text(ledit_view *view) {
   1900 	ledit_theme *theme = config_get_theme();
   1901 	int cur_line_y = 0;
   1902 	int cursor_displayed = 0;
   1903 	int text_w, text_h;
   1904 	window_get_textview_size(view->window, &text_w, &text_h);
   1905 	/* FIXME: use binary search here */
   1906 	/* FIXME: draw extra highlight when extra-line-spacing set
   1907 	   (also between soft lines because pango doesn't do that) */
   1908 	for (size_t i = 0; i < view->lines_num; i++) {
   1909 		ledit_view_line *vline = view_get_line(view, i);
   1910 		if (vline->y_offset + vline->h > view->display_offset) {
   1911 			/* FIXME: vline->text_dirty should not happen here */
   1912 			if (vline->text_dirty || vline->highlight_dirty)
   1913 				set_pango_text_and_highlight(view, i);
   1914 			if (vline->dirty || !vline->cache_pixmap_valid) {
   1915 				render_line(view, i);
   1916 			}
   1917 			int final_y = 0;
   1918 			int dest_y = vline->y_offset - view->display_offset;
   1919 			int final_h = vline->h;
   1920 			if (vline->y_offset < view->display_offset) {
   1921 				dest_y = 0;
   1922 				final_y = view->display_offset - vline->y_offset;
   1923 				final_h -= view->display_offset - vline->y_offset;
   1924 			}
   1925 			if (dest_y + final_h > text_h) {
   1926 				final_h -= final_y + final_h -
   1927 				           view->display_offset - text_h;
   1928 			}
   1929 			cache_pixmap *pix = cache_get_pixmap(
   1930 			    view->cache, vline->cache_pixmap_index
   1931 			);
   1932 			XCopyArea(
   1933 			    view->buffer->common->dpy, pix->pixmap,
   1934 			    view->window->drawable, view->window->gc,
   1935 			    0, final_y, vline->w, final_h, 0, dest_y
   1936 			);
   1937 			if (i == view->cur_line) {
   1938 				cur_line_y = vline->y_offset - view->display_offset;
   1939 				cursor_displayed = 1;
   1940 			}
   1941 			if (vline->y_offset + vline->h >= view->display_offset + text_h)
   1942 				break;
   1943 		}
   1944 	}
   1945 
   1946 	XSetForeground(view->buffer->common->dpy, view->window->gc, theme->cursor_bg.pixel);
   1947 	PangoRectangle strong, weak;
   1948 	ledit_line *cur_line = buffer_get_line(view->buffer, view->cur_line);
   1949 	PangoLayout *layout = get_pango_layout(view, view->cur_line);
   1950 	pango_layout_get_cursor_pos(
   1951 	    layout, (int)view->cur_index, &strong, &weak
   1952 	);
   1953 	/* FIXME: long, int, etc. */
   1954 	int cursor_y = strong.y / PANGO_SCALE + cur_line_y;
   1955 	if (cursor_displayed && cursor_y >= 0) {
   1956 		if (view->mode == NORMAL) {
   1957 			/* FIXME: figure out if there's a better way to do this */
   1958 			/* Seriously, which of the pango folks though it would be a good idea to
   1959 			   not highlight spaces at the end of soft lines? That is an utterly
   1960 			   horrible idea. Or am I just too stupid to use it properly? */
   1961 			/* FIXME: properly document what is happening here */
   1962 
   1963 			int box_x = strong.x / PANGO_SCALE;
   1964 			int box_w = 10;
   1965 			/* determine where the box should be drawn */
   1966 			PangoDirection dir = PANGO_DIRECTION_LTR;
   1967 			size_t tmp_index = view->cur_index;
   1968 			if (view->cur_index >= cur_line->len && cur_line->len > 0)
   1969 				tmp_index = cur_line->len - 1;
   1970 			else if (view->cur_index >= cur_line->len)
   1971 				tmp_index = 0;
   1972 			dir = ledit_pango_layout_get_direction(layout, (int)tmp_index);
   1973 
   1974 			int x, sli;
   1975 			pango_layout_index_to_line_x(layout, (int)view->cur_index, 0, &sli, &x);
   1976 			PangoLayoutLine *sl = pango_layout_get_line_readonly(layout, sli);
   1977 			if (dir != sl->resolved_dir) {
   1978 				box_w = 3;
   1979 			}
   1980 			if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL) {
   1981 				box_x = box_x - box_w;
   1982 			}
   1983 
   1984 			if (view->cur_index == cur_line->len ||
   1985 			    (cur_line->text[view->cur_index] == ' ' &&
   1986 			     view->cur_index == (size_t)(sl->start_index + sl->length - 1))) {
   1987 				XFillRectangle(
   1988 				    view->buffer->common->dpy, view->window->drawable, view->window->gc,
   1989 				    box_x, cursor_y,
   1990 				    box_w, strong.height / PANGO_SCALE
   1991 				);
   1992 			}
   1993 		} else if (view->mode == INSERT || view->mode == VISUAL) {
   1994 			XDrawLine(
   1995 			    view->buffer->common->dpy, view->window->drawable, view->window->gc,
   1996 			    strong.x / PANGO_SCALE, cursor_y,
   1997 			    strong.x / PANGO_SCALE,
   1998 			    (strong.y + strong.height) / PANGO_SCALE + cur_line_y
   1999 			);
   2000 		}
   2001 	}
   2002 	/* move input method position */
   2003 	if (!ledit_window_bottom_bar_text_shown(view->window)) {
   2004 		xximspot(
   2005 		    view->window,
   2006 		    strong.x / PANGO_SCALE,
   2007 		    (strong.y + strong.height) / PANGO_SCALE + cur_line_y
   2008 		);
   2009 	}
   2010 	view->redraw = 0;
   2011 }
   2012 
   2013 void
   2014 view_redraw(ledit_view *view, size_t lang_index) {
   2015 	window_set_format_args(
   2016 	    view->window, view->mode, view->buffer->hard_line_based,
   2017 	    view->cur_line + 1, view->cur_index + 1, lang_index
   2018 	);
   2019 	if (view->redraw || view->window->redraw) {
   2020 		window_clear(view->window);
   2021 		view_redraw_text(view);
   2022 		window_redraw(view->window);
   2023 	}
   2024 }
   2025 
   2026 void
   2027 view_undo(ledit_view *view, int num) {
   2028 	/* FIXME: maybe wipe selection (although I guess this
   2029 	   currently isn't possible in visual mode anyways) */
   2030 	view_wipe_line_cursor_attrs(view, view->cur_line);
   2031 	for (int i = 0; i < num; i++) {
   2032 		undo_status s = buffer_undo(view->buffer, view->mode, &view->cur_line, &view->cur_index);
   2033 		if (view->mode == NORMAL) {
   2034 			view->cur_index = view_get_legal_normal_pos(
   2035 			    view, view->cur_line, view->cur_index
   2036 			);
   2037 		}
   2038 		if (s != UNDO_NORMAL) {
   2039 			window_show_message(view->window, undo_state_to_str(s), -1);
   2040 			break;
   2041 		}
   2042 	}
   2043 	if (view->mode == NORMAL)
   2044 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   2045 	else
   2046 		view_wipe_line_cursor_attrs(view, view->cur_line);
   2047 }
   2048 
   2049 void
   2050 view_redo(ledit_view *view, int num) {
   2051 	view_wipe_line_cursor_attrs(view, view->cur_line);
   2052 	for (int i = 0; i < num; i++) {
   2053 		undo_status s = buffer_redo(view->buffer, view->mode, &view->cur_line, &view->cur_index);
   2054 		if (view->mode == NORMAL) {
   2055 			view->cur_index = view_get_legal_normal_pos(
   2056 			    view, view->cur_line, view->cur_index
   2057 			);
   2058 		}
   2059 		if (s != UNDO_NORMAL) {
   2060 			window_show_message(view->window, undo_state_to_str(s), -1);
   2061 			break;
   2062 		}
   2063 	}
   2064 	if (view->mode == NORMAL)
   2065 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   2066 	else
   2067 		view_wipe_line_cursor_attrs(view, view->cur_line);
   2068 }
   2069 
   2070 static void
   2071 paste_txtbuf(ledit_view *view, txtbuf *buf) {
   2072 	if (view->mode == NORMAL)
   2073 		view_wipe_line_cursor_attrs(view, view->cur_line);
   2074 	ledit_range cur_range;
   2075 	cur_range.line1 = view->cur_line;
   2076 	cur_range.byte1 = view->cur_index;
   2077 	/* just to avoid false positives during static analysis */
   2078 	cur_range.line2 = cur_range.byte2 = 0;
   2079 	buffer_insert_with_undo(
   2080 	     view->buffer, cur_range, 1, 1, view->mode,
   2081 	     view->cur_line, view->cur_index, buf->text, buf->len,
   2082 	     &view->cur_line, &view->cur_index
   2083 	);
   2084 	view_ensure_cursor_shown(view);
   2085 	if (view->mode == NORMAL)
   2086 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   2087 }
   2088 
   2089 void
   2090 view_paste_clipboard(ledit_view *view) {
   2091 	txtbuf *buf = clipboard_get_clipboard_text(view->buffer->clipboard);
   2092 	if (!buf)
   2093 		return; /* FIXME: warning? */
   2094 	paste_txtbuf(view, buf);
   2095 }
   2096 
   2097 void
   2098 view_paste_primary(ledit_view *view) {
   2099 	txtbuf *buf = clipboard_get_primary_text(view->buffer->clipboard);
   2100 	if (!buf)
   2101 		return; /* FIXME: warning? */
   2102 	paste_txtbuf(view, buf);
   2103 }