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 (71581B)


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