URI: 
       view.c - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ledit.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR 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 }