ledit

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

commit 6c98800389dc02e20954a157675ac67fb02b7f8c
parent 60385928394de59d1104da2c69241745cbba4784
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 21 Nov 2021 14:30:26 +0100

Don't keep a PangoLayout for every line

Diffstat:
MLICENSE | 3++-
Mbuffer.c | 632+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mbuffer.h | 37++++++++++++++++++++-----------------
Mcache.c | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mcache.h | 128++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mkeys_basic.c | 26++++++++++++--------------
Mkeys_command.c | 2--
Mmemory.c | 23+++++++++++++++++++++++
Mmemory.h | 1+
9 files changed, 675 insertions(+), 346 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -1,4 +1,5 @@ -Note: Some stuff is stolen from st (https://st.suckless.org) +Note 1: Some stuff is stolen from st (https://st.suckless.org) +Note 2: Some stuff is stolen from OpenBSD (https://openbsd.org) ISC License diff --git a/buffer.c b/buffer.c @@ -36,16 +36,44 @@ static PangoAttrList *basic_attrs = NULL; -static void err_text_dirty(ledit_buffer *buffer, int line); +/*static void err_text_dirty(ledit_buffer *buffer, int line);*/ static void move_text_gap(ledit_line *line, int index); static void resize_and_move_text_gap(ledit_line *line, int min_size, int index); static char *strchr_len(char *text, char c, long len); static void init_line(ledit_buffer *buffer, ledit_line *line); static void delete_line_section_base(ledit_buffer *buffer, int line, int start, int length); -static void normalize_and_set_pango_text(ledit_line *line); static void swap(int *a, int *b); static void copy_selection_to_x_primary(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2); +/* + * Assign a cache index to line and set text and highlight of the pango layout. + */ +static void set_pango_text_and_highlight(ledit_buffer *buffer, int line); + +/* + * Get the pango layout for line. + * This first assigns a cache index (by calling set_pango_text_and_highlight). + */ +static PangoLayout *get_pango_layout(ledit_buffer *buffer, int line); + +/* + * Get an attribute list for a text highlight between the given range. + */ +static PangoAttrList *get_pango_attributes(ledit_buffer *buffer, int start_byte, int end_byte); + +/* + * Set the attributes for a PangoLayout belonging to the given line index. + * If the line is part of the buffer's selection, the selection is set. + * If that is not the case but cursor_index is set for the line, the character + * at that position is highlighted (this is used for the normal mode cursor). + * Otherwise, the default attributes (basic_attrs) are set. + */ +static void set_line_layout_attrs(ledit_buffer *buffer, int line, PangoLayout *layout); + +static int line_visible_callback(void *data, int line); +static void set_pixmap_line_helper(void *data, int line, int index); +static void set_layout_line_helper(void *data, int line, int index); + void ledit_buffer_set_mode(ledit_buffer *buffer, enum ledit_mode mode) { buffer->common->mode = mode; @@ -105,6 +133,13 @@ ledit_buffer_create(ledit_common *common, ledit_theme *theme, ledit_window *wind } ledit_buffer *buffer = ledit_malloc(sizeof(ledit_buffer)); + buffer->cache = cache_create(common->dpy); + buffer->undo = ledit_undo_stack_create(); + buffer->theme = theme; + buffer->marklist = marklist_create(); + ledit_window_set_scroll_callback(window, &ledit_buffer_scroll_handler, buffer); + ledit_window_set_button_callback(window, &ledit_buffer_button_handler, buffer); + buffer->common = common; buffer->window = window; buffer->lines = NULL; @@ -122,14 +157,9 @@ ledit_buffer_create(ledit_common *common, ledit_theme *theme, ledit_window *wind buffer->display_offset = 0; buffer->sel.line1 = buffer->sel.byte1 = -1; buffer->sel.line2 = buffer->sel.byte2 = -1; + ledit_buffer_append_line_base(buffer, -1, -1); ledit_buffer_recalc_all_lines(buffer); - buffer->cache = ledit_cache_create(common); - buffer->undo = ledit_undo_stack_create(); - buffer->theme = theme; - buffer->marklist = marklist_create(); - ledit_window_set_scroll_callback(window, &ledit_buffer_scroll_handler, buffer); - ledit_window_set_button_callback(window, &ledit_buffer_button_handler, buffer); return buffer; } @@ -217,10 +247,9 @@ ledit_buffer_destroy(ledit_buffer *buffer) { ledit_line *l; for (int i = 0; i < buffer->lines_num; i++) { l = ledit_buffer_get_line(buffer, i); - g_object_unref(l->layout); free(l->text); } - ledit_cache_destroy(buffer->cache); + cache_destroy(buffer->cache); ledit_undo_stack_destroy(buffer->undo); free(buffer->lines); if (buffer->filename) @@ -249,6 +278,7 @@ ledit_buffer_normalize_line(ledit_line *line) { line->text[line->len] = '\0'; } +#if 0 static void err_text_dirty(ledit_buffer *buffer, int line) { fprintf( @@ -258,9 +288,11 @@ err_text_dirty(ledit_buffer *buffer, int line) { ); ledit_buffer_recalc_from_line(buffer, line); } +#endif -void -ledit_buffer_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte) { +#if 0 +static void +set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte) { ledit_line *l = ledit_buffer_get_line(buffer, line); if (l->text_dirty) err_text_dirty(buffer, line); @@ -284,41 +316,72 @@ ledit_buffer_set_line_selection(ledit_buffer *buffer, int line, int start_byte, pango_attr_list_unref(list); l->dirty = 1; } +#endif -void -ledit_buffer_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index) { - ledit_line *l = ledit_buffer_get_line(buffer, line); - if (l->text_dirty) - err_text_dirty(buffer, line); - if (buffer->common->mode == NORMAL) { - XRenderColor fg = buffer->theme->text_fg.color; - XRenderColor bg = buffer->theme->text_bg.color; - PangoAttribute *attr0 = pango_attr_background_new(fg.red, fg.green, fg.blue); - PangoAttribute *attr1 = pango_attr_foreground_new(bg.red, bg.green, bg.blue); - attr0->start_index = index; - attr0->end_index = index + 1; - attr1->start_index = index; - attr1->end_index = index + 1; - PangoAttrList *list = pango_attr_list_new(); - pango_attr_list_insert(list, attr0); - pango_attr_list_insert(list, attr1); - #if PANGO_VERSION_CHECK(1, 44, 0) - PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE); - pango_attr_list_insert(list, attr2); - #endif - pango_layout_set_attributes(l->layout, list); +static PangoAttrList * +get_pango_attributes(ledit_buffer *buffer, int start_byte, int end_byte) { + XRenderColor fg = buffer->theme->text_fg.color; + XRenderColor bg = buffer->theme->text_bg.color; + PangoAttribute *attr0 = pango_attr_background_new(fg.red, fg.green, fg.blue); + PangoAttribute *attr1 = pango_attr_foreground_new(bg.red, bg.green, bg.blue); + attr0->start_index = start_byte; + attr0->end_index = end_byte; + attr1->start_index = start_byte; + attr1->end_index = end_byte; + PangoAttrList *list = pango_attr_list_new(); + pango_attr_list_insert(list, attr0); + pango_attr_list_insert(list, attr1); + #if PANGO_VERSION_CHECK(1, 44, 0) + PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE); + pango_attr_list_insert(list, attr2); + #endif + return list; +} + +/* this takes layout directly to possibly avoid infinite recursion */ +static void +set_line_layout_attrs(ledit_buffer *buffer, int line, PangoLayout *layout) { + ledit_line *ll = ledit_buffer_get_line(buffer, line); + ledit_range sel = ll->parent_buffer->sel; + PangoAttrList *list = NULL; + if (sel.line1 < line && sel.line2 > line) { + list = get_pango_attributes(buffer, 0, ll->len); + } else if (sel.line1 == line && sel.line2 == line) { + int start = sel.byte1, end = sel.byte2; + if (start > end) + swap(&start, &end); + list = get_pango_attributes(buffer, start, end); + } else if (sel.line1 == line && sel.line2 > line) { + list = get_pango_attributes(buffer, sel.byte1, ll->len); + } else if (sel.line1 < line && sel.line2 == line) { + list = get_pango_attributes(buffer, 0, sel.byte2); + } else if (ll->cursor_index >= 0 && ll->cursor_index < ll->len) { + /* FIXME: does just adding one really do the right thing? */ + list = get_pango_attributes(buffer, ll->cursor_index, ll->cursor_index + 1); + } + if (list != NULL) { + pango_layout_set_attributes(layout, list); pango_attr_list_unref(list); } else { - pango_layout_set_attributes(l->layout, basic_attrs); + pango_layout_set_attributes(layout, basic_attrs); } - l->dirty = 1; + ll->highlight_dirty = 0; +} + +void +ledit_buffer_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index) { + ledit_line *ll = ledit_buffer_get_line(buffer, line); + ll->cursor_index = index; + ll->highlight_dirty = 1; + ll->dirty = 1; } void ledit_buffer_wipe_line_cursor_attrs(ledit_buffer *buffer, int line) { - ledit_line *l = ledit_buffer_get_line(buffer, line); - pango_layout_set_attributes(l->layout, basic_attrs); - l->dirty = 1; + ledit_line *ll = ledit_buffer_get_line(buffer, line); + ll->cursor_index = -1; + ll->highlight_dirty = 1; + ll->dirty = 1; } /* FIXME: To simplify this a bit, maybe just copy text to txtbuf first and @@ -589,36 +652,31 @@ line_visible_callback(void *data, int line) { void ledit_buffer_render_line(ledit_buffer *buffer, int line_index) { /* FIXME: check for <= 0 on size */ - ledit_line *line = ledit_buffer_get_line(buffer, line_index); - /* this shouldn't happen if the functions here are used correctly */ - if (line->text_dirty || line->h_dirty) - err_text_dirty(buffer, line_index); - if (line->cache_index == -1) { - int new_index = ledit_get_unneeded_cache_index(buffer->cache, buffer, &line_visible_callback); - ledit_cache_pixmap *tmp_pix = ledit_get_cache_pixmap(buffer->cache, new_index); - if (tmp_pix->line >= 0) { - ledit_line *old_line = ledit_buffer_get_line(buffer, tmp_pix->line); - old_line->cache_index = -1; - } - tmp_pix->line = line_index; - line->cache_index = new_index; + ledit_line *ll = ledit_buffer_get_line(buffer, line_index); + assert(!ll->h_dirty); /* FIXME */ + PangoLayout *layout = get_pango_layout(buffer, line_index); + if (ll->cache_pixmap_index == -1) { + cache_assign_pixmap_index( + buffer->cache, line_index, buffer, + &line_visible_callback, &set_pixmap_line_helper + ); } - ledit_cache_pixmap *pix = ledit_get_cache_pixmap(buffer->cache, line->cache_index); + cache_pixmap *pix = cache_get_pixmap(buffer->cache, ll->cache_pixmap_index); /* FIXME: sensible default pixmap sizes here */ if (pix->pixmap == None || pix->draw == NULL) { pix->pixmap = XCreatePixmap( buffer->common->dpy, buffer->window->drawable, - line->w + 10, line->h + 10, buffer->common->depth + ll->w + 10, ll->h + 10, buffer->common->depth ); - pix->w = line->w + 10; - pix->h = line->h + 10; + pix->w = ll->w + 10; + pix->h = ll->h + 10; pix->draw = XftDrawCreate( buffer->common->dpy, pix->pixmap, buffer->common->vis, buffer->common->cm ); - } else if (pix->w < line->w || pix->h < line->h) { - int new_w = line->w > pix->w ? line->w + 10 : pix->w + 10; - int new_h = line->h > pix->h ? line->h + 10 : pix->h + 10; + } else if (pix->w < ll->w || pix->h < ll->h) { + int new_w = ll->w > pix->w ? ll->w + 10 : pix->w + 10; + int new_h = ll->h > pix->h ? ll->h + 10 : pix->h + 10; XFreePixmap(buffer->common->dpy, pix->pixmap); pix->pixmap = XCreatePixmap( buffer->common->dpy, buffer->window->drawable, @@ -628,35 +686,32 @@ ledit_buffer_render_line(ledit_buffer *buffer, int line_index) { pix->h = new_h; XftDrawChange(pix->draw, pix->pixmap); } - XftDrawRect(pix->draw, &buffer->theme->text_bg, 0, 0, line->w, line->h); - pango_xft_render_layout(pix->draw, &buffer->theme->text_fg, line->layout, 0, 0); - line->dirty = 0; + XftDrawRect(pix->draw, &buffer->theme->text_bg, 0, 0, ll->w, ll->h); + pango_xft_render_layout(pix->draw, &buffer->theme->text_fg, layout, 0, 0); + ll->dirty = 0; } static void init_line(ledit_buffer *buffer, ledit_line *line) { int text_w, text_h; line->parent_buffer = buffer; - line->layout = pango_layout_new(buffer->window->context); ledit_window_get_textview_size(buffer->window, &text_w, &text_h); - pango_layout_set_width(line->layout, text_w * PANGO_SCALE); - pango_layout_set_font_description(line->layout, buffer->window->font); - pango_layout_set_wrap(line->layout, PANGO_WRAP_WORD_CHAR); - pango_layout_set_attributes(line->layout, basic_attrs); line->gap = 0; line->cap = 2; /* arbitrary */ line->text = ledit_malloc(line->cap); line->text[0] = '\0'; line->len = 0; - line->cache_index = -1; + line->cache_pixmap_index = -1; + line->cache_layout_index = -1; line->dirty = 1; line->text_dirty = 1; + line->highlight_dirty = 1; line->h_dirty = 1; - /* FIXME: Is line->w needed? I don't think so. */ line->w = line->h = 0; - /* FIXME: does this set line height reasonably when no text yet? */ - pango_layout_get_pixel_size(line->layout, &line->w, &line->h); + line->cursor_index = -1; + line->softlines = 1; line->w = text_w; + line->h = 0; line->y_offset = 0; } @@ -682,9 +737,9 @@ ledit_buffer_append_line_base(ledit_buffer *buffer, int line_index, int text_ind buffer->lines + line_index + 1, (buffer->lines_num - (line_index + 1)) * sizeof(ledit_line) ); + buffer->lines_num++; ledit_line *new_l = ledit_buffer_get_line(buffer, line_index + 1); init_line(buffer, new_l); - buffer->lines_num++; if (text_index != -1) { ledit_line *l = ledit_buffer_get_line(buffer, line_index); ledit_buffer_insert_text_from_line_base( @@ -705,15 +760,31 @@ ledit_buffer_delete_line_entries(ledit_buffer *buffer, int index1, int index2) { ledit_buffer_recalc_from_line(buffer, index1 > 0 ? index1 - 1 : 0); } +static void +set_pixmap_line_helper(void *data, int line, int index) { + ledit_line *ll = ledit_buffer_get_line((ledit_buffer *)data, line); + ll->cache_pixmap_index = index; +} + +static void +set_layout_line_helper(void *data, int line, int index) { + ledit_line *ll = ledit_buffer_get_line((ledit_buffer *)data, line); + ll->cache_layout_index = index; +} + /* IMPORTANT: ledit_buffer_recalc_from_line needs to be called sometime after this! */ void ledit_buffer_delete_line_entries_base(ledit_buffer *buffer, int index1, int index2) { ledit_line *l; /* FIXME: make sure this is always true */ + /* FIXME: Ummm... what is that assert supposed to do? */ assert(index2 - index1 != buffer->lines_num); + cache_invalidate_from_line( + buffer->cache, index1, buffer, + &set_pixmap_line_helper, &set_layout_line_helper + ); for (int i = index1; i <= index2; i++) { l = ledit_buffer_get_line(buffer, i); - g_object_unref(l->layout); free(l->text); } /* FIXME: gap buffer */ @@ -747,9 +818,9 @@ ledit_buffer_delete_line_entry_base(ledit_buffer *buffer, int index) { /* FIXME: use some sort of gap buffer (that would make this function slightly more useful...) */ -/* FIXME: error checking, assert? */ ledit_line * ledit_buffer_get_line(ledit_buffer *buffer, int index) { + assert(index >= 0 && index < buffer->lines_num); return &buffer->lines[index]; } @@ -758,20 +829,15 @@ ledit_buffer_get_line(ledit_buffer *buffer, int index) { * - if height has changed, offset of all following lines is changed */ void ledit_buffer_recalc_line(ledit_buffer *buffer, int line) { - int w, h; ledit_line *l = ledit_buffer_get_line(buffer, line); - if (l->text_dirty) { - ledit_buffer_normalize_line(l); - pango_layout_set_text(l->layout, l->text, l->len); - l->text_dirty = 0; - } - l->h_dirty = 0; - pango_layout_get_pixel_size(l->layout, &w, &h); + if (l->text_dirty) + set_pango_text_and_highlight(buffer, line); + /* if height changed, set height of current line * and adjust offsets of all lines following it */ - if (l->h != h) { - long off = l->y_offset + h; - l->h = h; + if (l->h_dirty) { + l->h_dirty = 0; + long off = l->y_offset + l->h; for (int i = line + 1; i < buffer->lines_num; i++) { l = ledit_buffer_get_line(buffer, i); l->y_offset = off; @@ -785,21 +851,12 @@ ledit_buffer_recalc_line(ledit_buffer *buffer, int line) { * and offset for all lines starting at 'line' */ void ledit_buffer_recalc_from_line(ledit_buffer *buffer, int line) { - int w, h; ledit_line *l = ledit_buffer_get_line(buffer, line); long off = l->y_offset; for (int i = line; i < buffer->lines_num; i++) { l = ledit_buffer_get_line(buffer, i); - if (l->text_dirty) { - ledit_buffer_normalize_line(l); - pango_layout_set_text(l->layout, l->text, l->len); - l->text_dirty = 0; - pango_layout_get_pixel_size(l->layout, &w, &h); - l->h = h; - } else if (l->h_dirty) { - pango_layout_get_pixel_size(l->layout, &w, &h); - l->h = h; - } + if (l->text_dirty) + set_pango_text_and_highlight(buffer, i); l->h_dirty = 0; l->y_offset = off; off += l->h; @@ -951,8 +1008,9 @@ ledit_buffer_next_cursor_pos(ledit_buffer *buffer, int line, int byte, int num) ledit_line *ll = ledit_buffer_get_line(buffer, line); int c = line_byte_to_char(ll, byte); int cur_byte = byte; + PangoLayout *layout = get_pango_layout(buffer, line); const PangoLogAttr *attrs = - pango_layout_get_log_attrs_readonly(ll->layout, &nattrs); + pango_layout_get_log_attrs_readonly(layout, &nattrs); for (int i = 0; i < num; i++) { cur_byte = ledit_line_next_utf8(ll, byte); for (c++; c < nattrs; c++) { @@ -972,8 +1030,9 @@ ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int line, int byte, int num) ledit_line *ll = ledit_buffer_get_line(buffer, line); int c = line_byte_to_char(ll, byte); int cur_byte = byte; + PangoLayout *layout = get_pango_layout(buffer, line); const PangoLogAttr *attrs = - pango_layout_get_log_attrs_readonly(ll->layout, &nattrs); + pango_layout_get_log_attrs_readonly(layout, &nattrs); for (int i = 0; i < num; i++) { cur_byte = ledit_line_prev_utf8(ll, cur_byte); for (c--; c >= 0; c--) { @@ -988,15 +1047,17 @@ ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int line, int byte, int num) } static int -line_next_word(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) { +line_next_word(ledit_buffer *buffer, int line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) { int c, nattrs; + ledit_line *ll = ledit_buffer_get_line(buffer, line); if (char_index >= 0) c = char_index; else - c = line_byte_to_char(line, byte); - int cur_byte = wrapped_line ? byte : ledit_line_next_utf8(line, byte); + c = line_byte_to_char(ll, byte); + int cur_byte = wrapped_line ? byte : ledit_line_next_utf8(ll, byte); + PangoLayout *layout = get_pango_layout(buffer, line); const PangoLogAttr *attrs = - pango_layout_get_log_attrs_readonly(line->layout, &nattrs); + pango_layout_get_log_attrs_readonly(layout, &nattrs); for (int i = wrapped_line ? c : c + 1; i < nattrs; i++) { if (attrs[i].is_word_start) { if (char_ret) @@ -1005,21 +1066,23 @@ line_next_word(ledit_line *line, int byte, int char_index, int wrapped_line, int *real_byte_ret = cur_byte; return cur_byte; } - cur_byte = ledit_line_next_utf8(line, cur_byte); + cur_byte = ledit_line_next_utf8(ll, cur_byte); } return -1; } static int -line_prev_word(ledit_line *line, int byte, int char_index, int *char_ret) { +line_prev_word(ledit_buffer *buffer, int line, int byte, int char_index, int *char_ret) { int c, nattrs; + ledit_line *ll = ledit_buffer_get_line(buffer, line); if (char_index >= 0) c = char_index; else - c = line_byte_to_char(line, byte); - int cur_byte = ledit_line_prev_utf8(line, byte); + c = line_byte_to_char(ll, byte); + int cur_byte = ledit_line_prev_utf8(ll, byte); + PangoLayout *layout = get_pango_layout(buffer, line); const PangoLogAttr *attrs = - pango_layout_get_log_attrs_readonly(line->layout, &nattrs); + pango_layout_get_log_attrs_readonly(layout, &nattrs); if (c > nattrs) return -1; for (int i = c - 1; i >= 0; i--) { @@ -1028,21 +1091,23 @@ line_prev_word(ledit_line *line, int byte, int char_index, int *char_ret) { *char_ret = i; return cur_byte; } - cur_byte = ledit_line_prev_utf8(line, cur_byte); + cur_byte = ledit_line_prev_utf8(ll, cur_byte); } return -1; } static int -line_prev_bigword(ledit_line *line, int byte, int char_index, int *char_ret) { +line_prev_bigword(ledit_buffer *buffer, int line, int byte, int char_index, int *char_ret) { int c, nattrs; + ledit_line *ll = ledit_buffer_get_line(buffer, line); if (char_index >= 0) c = char_index; else - c = line_byte_to_char(line, byte); - int cur_byte = ledit_line_prev_utf8(line, byte); + c = line_byte_to_char(ll, byte); + int cur_byte = ledit_line_prev_utf8(ll, byte); + PangoLayout *layout = get_pango_layout(buffer, line); const PangoLogAttr *attrs = - pango_layout_get_log_attrs_readonly(line->layout, &nattrs); + pango_layout_get_log_attrs_readonly(layout, &nattrs); int next_cursorb = byte; int next_cursorc = c; int found_word = 0; @@ -1063,20 +1128,22 @@ line_prev_bigword(ledit_line *line, int byte, int char_index, int *char_ret) { next_cursorc = i; next_cursorb = cur_byte; } - cur_byte = ledit_line_prev_utf8(line, cur_byte); + cur_byte = ledit_line_prev_utf8(ll, cur_byte); } return -1; } int -line_next_bigword_end(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) { +line_next_bigword_end(ledit_buffer *buffer, int line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) { int c, nattrs; + ledit_line *ll = ledit_buffer_get_line(buffer, line); if (char_index >= 0) c = char_index; else - c = line_byte_to_char(line, byte); + c = line_byte_to_char(ll, byte); + PangoLayout *layout = get_pango_layout(buffer, line); const PangoLogAttr *attrs = - pango_layout_get_log_attrs_readonly(line->layout, &nattrs); + pango_layout_get_log_attrs_readonly(layout, &nattrs); int last_cursorb, last_cursorc; if (wrapped_line) { last_cursorb = byte; @@ -1101,21 +1168,23 @@ line_next_bigword_end(ledit_line *line, int byte, int char_index, int wrapped_li last_cursorc = i; last_cursorb = cur_byte; } - cur_byte = ledit_line_next_utf8(line, cur_byte); + cur_byte = ledit_line_next_utf8(ll, cur_byte); } return -1; } static int -line_next_word_end(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) { +line_next_word_end(ledit_buffer *buffer, int line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) { int c, nattrs; + ledit_line *ll = ledit_buffer_get_line(buffer, line); if (char_index >= 0) c = char_index; else - c = line_byte_to_char(line, byte); - int cur_byte = ledit_line_next_utf8(line, byte); + c = line_byte_to_char(ll, byte); + int cur_byte = ledit_line_next_utf8(ll, byte); + PangoLayout *layout = get_pango_layout(buffer, line); const PangoLogAttr *attrs = - pango_layout_get_log_attrs_readonly(line->layout, &nattrs); + pango_layout_get_log_attrs_readonly(layout, &nattrs); int last_cursorb, last_cursorc; if (wrapped_line) { last_cursorb = byte; @@ -1136,21 +1205,23 @@ line_next_word_end(ledit_line *line, int byte, int char_index, int wrapped_line, last_cursorc = i; last_cursorb = cur_byte; } - cur_byte = ledit_line_next_utf8(line, cur_byte); + cur_byte = ledit_line_next_utf8(ll, cur_byte); } return -1; } static int -line_next_bigword(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) { +line_next_bigword(ledit_buffer *buffer, int line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) { int c, nattrs; + ledit_line *ll = ledit_buffer_get_line(buffer, line); if (char_index >= 0) c = char_index; else - c = line_byte_to_char(line, byte); + c = line_byte_to_char(ll, byte); int cur_byte = byte; + PangoLayout *layout = get_pango_layout(buffer, line); const PangoLogAttr *attrs = - pango_layout_get_log_attrs_readonly(line->layout, &nattrs); + pango_layout_get_log_attrs_readonly(layout, &nattrs); int found_ws = wrapped_line; for (int i = c; i < nattrs; i++) { if (!found_ws && attrs[i].is_white) { @@ -1162,24 +1233,26 @@ line_next_bigword(ledit_line *line, int byte, int char_index, int wrapped_line, *real_byte_ret = cur_byte; return cur_byte; } - cur_byte = ledit_line_next_utf8(line, cur_byte); + cur_byte = ledit_line_next_utf8(ll, cur_byte); } return -1; } int -ledit_line_next_non_whitespace(ledit_line *line, int byte) { +ledit_line_next_non_whitespace(ledit_buffer *buffer, int line, int byte) { int c, nattrs; - c = line_byte_to_char(line, byte); + ledit_line *ll = ledit_buffer_get_line(buffer, line); + c = line_byte_to_char(ll, byte); int cur_byte = byte; + PangoLayout *layout = get_pango_layout(buffer, line); const PangoLogAttr *attrs = - pango_layout_get_log_attrs_readonly(line->layout, &nattrs); + pango_layout_get_log_attrs_readonly(layout, &nattrs); for (; c < nattrs; c++) { if (!attrs[c].is_white) return cur_byte; - cur_byte = ledit_line_next_utf8(line, cur_byte); + cur_byte = ledit_line_next_utf8(ll, cur_byte); } - return line->len; + return ll->len; } /* FIXME: document that word and bigword are a bit weird because word uses unicode semantics */ @@ -1195,13 +1268,12 @@ ledit_buffer_next_##name( int cur_char = -1; \ int real_byte = -1; \ int wrapped_line; \ - ledit_line *ll = ledit_buffer_get_line(buffer, cur_line); \ for (int i = 0; i < num_repeat; i++) { \ wrapped_line = 0; \ - while ((cur_byte = func(ll, cur_byte, cur_char, wrapped_line, &cur_char, &real_byte)) == -1 && \ + while ((cur_byte = func(buffer, cur_line, cur_byte, cur_char, \ + wrapped_line, &cur_char, &real_byte)) == -1 && \ cur_line < buffer->lines_num - 1) { \ cur_line++; \ - ll = ledit_buffer_get_line(buffer, cur_line); \ cur_byte = 0; \ wrapped_line = 1; \ } \ @@ -1210,6 +1282,7 @@ ledit_buffer_next_##name( } \ if (cur_byte == -1) { \ *line_ret = buffer->lines_num - 1; \ + ledit_line *ll = ledit_buffer_get_line(buffer, buffer->lines_num - 1); \ *byte_ret = ledit_buffer_get_legal_normal_pos(buffer, buffer->lines_num - 1, ll->len); \ *real_byte_ret = ll->len; \ } else { \ @@ -1230,7 +1303,8 @@ ledit_buffer_prev_##name( int cur_char = -1; \ ledit_line *ll = ledit_buffer_get_line(buffer, cur_line); \ for (int i = 0; i < num_repeat; i++) { \ - while ((cur_byte = func(ll, cur_byte, cur_char, &cur_char)) == -1 && cur_line > 0) { \ + while ((cur_byte = func(buffer, cur_line, cur_byte, cur_char, \ + &cur_char)) == -1 && cur_line > 0) { \ cur_line--; \ ll = ledit_buffer_get_line(buffer, cur_line); \ cur_byte = ll->len; \ @@ -1262,11 +1336,11 @@ ledit_buffer_get_pos_softline_bounds( int *start_byte_ret, int *end_byte_ret) { assert(line >= 0 && line < buffer->lines_num); ledit_line *ll = ledit_buffer_get_line(buffer, line); - normalize_and_set_pango_text(ll); assert(pos >= 0 && pos <= ll->len); + PangoLayout *layout = get_pango_layout(buffer, line); int x, sli; - pango_layout_index_to_line_x(ll->layout, pos, 0, &sli, &x); - PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, sli); + pango_layout_index_to_line_x(layout, pos, 0, &sli, &x); + PangoLayoutLine *pl = pango_layout_get_line_readonly(layout, sli); *start_byte_ret = pl->start_index; *end_byte_ret = pl->start_index + pl->length; } @@ -1277,9 +1351,9 @@ ledit_buffer_get_softline_bounds( int *start_byte_ret, int *end_byte_ret) { assert(line >= 0 && line < buffer->lines_num); ledit_line *ll = ledit_buffer_get_line(buffer, line); - normalize_and_set_pango_text(ll); - assert(softline < pango_layout_get_line_count(ll->layout)); - PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, softline); + PangoLayout *layout = get_pango_layout(buffer, line); + assert(softline < ll->softlines); + PangoLayoutLine *pl = pango_layout_get_line_readonly(layout, softline); *start_byte_ret = pl->start_index; *end_byte_ret = pl->start_index + pl->length; } @@ -1288,7 +1362,9 @@ int ledit_buffer_get_softline_count(ledit_buffer *buffer, int line) { assert(line >= 0 && line < buffer->lines_num); ledit_line *ll = ledit_buffer_get_line(buffer, line); - return pango_layout_get_line_count(ll->layout); + if (ll->text_dirty) + set_pango_text_and_highlight(buffer, line); + return ll->softlines; } int @@ -1296,8 +1372,9 @@ ledit_buffer_pos_to_softline(ledit_buffer *buffer, int line, int pos) { assert(line >= 0 && line < buffer->lines_num); ledit_line *ll = ledit_buffer_get_line(buffer, line); assert(pos >= 0 && pos <= ll->len); + PangoLayout *layout = get_pango_layout(buffer, line); int x, sli; - pango_layout_index_to_line_x(ll->layout, pos, 0, &sli, &x); + pango_layout_index_to_line_x(layout, pos, 0, &sli, &x); return sli; } @@ -1306,8 +1383,9 @@ ledit_buffer_get_cursor_pixel_pos(ledit_buffer *buffer, int line, int pos, int * assert(line >= 0 && line < buffer->lines_num); ledit_line *ll = ledit_buffer_get_line(buffer, line); assert(pos >= 0 && pos <= ll->len); + PangoLayout *layout = get_pango_layout(buffer, line); PangoRectangle strong, weak; - pango_layout_get_cursor_pos(ll->layout, 0, &strong, &weak); + pango_layout_get_cursor_pos(layout, 0, &strong, &weak); *x_ret = strong.x / PANGO_SCALE; *y_ret = strong.y / PANGO_SCALE; *h_ret = strong.height / PANGO_SCALE; @@ -1323,6 +1401,7 @@ ledit_buffer_move_cursor_visually(ledit_buffer *buffer, int line, int pos, int m /* FIXME: trailing */ int trailing = 0, tmp_index; ledit_line *cur_line = ledit_buffer_get_line(buffer, line); + PangoLayout *layout = get_pango_layout(buffer, line); int new_index = pos, last_index = pos; int dir = 1; int num = movement; @@ -1333,7 +1412,7 @@ ledit_buffer_move_cursor_visually(ledit_buffer *buffer, int line, int pos, int m while (num > 0) { tmp_index = new_index; pango_layout_move_cursor_visually( - cur_line->layout, TRUE, + layout, TRUE, new_index, trailing, dir, &new_index, &trailing ); @@ -1403,7 +1482,6 @@ delete_line_section_base(ledit_buffer *buffer, int line, int start, int length) l->len -= length; l->dirty = 1; l->text_dirty = 1; - l->h_dirty = 1; } int @@ -1428,85 +1506,124 @@ ledit_buffer_delete_unicode_char_base(ledit_buffer *buffer, int line_index, int return new_index; } -/* FIXME: normalize_line will generally be called whenever it is not normalized - since text_dirty should be set then, but the naming of this function technically - isn't very good */ static void -normalize_and_set_pango_text(ledit_line *line) { - if (line->text_dirty) { - ledit_buffer_normalize_line(line); - pango_layout_set_text(line->layout, line->text, line->len); - line->text_dirty = 0; - line->h_dirty = 1; +set_pango_text_and_highlight(ledit_buffer *buffer, int line) { + cache_layout *cl; + ledit_line *ll = ledit_buffer_get_line(buffer, line); + int old_index = ll->cache_layout_index; + if (ll->cache_layout_index < 0) { + cache_assign_layout_index( + ll->parent_buffer->cache, line, + ll->parent_buffer, &set_layout_line_helper + ); + assert(ll->cache_layout_index >= 0); + cl = cache_get_layout(ll->parent_buffer->cache, ll->cache_layout_index); + if (cl->layout == NULL) { + cl->layout = pango_layout_new(ll->parent_buffer->window->context); + pango_layout_set_font_description(cl->layout, buffer->window->font); + pango_layout_set_wrap(cl->layout, PANGO_WRAP_WORD_CHAR); + } + } else { + cl = cache_get_layout(ll->parent_buffer->cache, ll->cache_layout_index); + } + if (ll->text_dirty || old_index < 0) { + ledit_buffer_normalize_line(ll); + pango_layout_set_text(cl->layout, ll->text, ll->len); + set_line_layout_attrs(buffer, line, cl->layout); + /* FIXME: is this guard necessary? */ + ll->softlines = ll->len > 0 ? pango_layout_get_line_count(cl->layout) : 1; + pango_layout_set_width(cl->layout, ll->w * PANGO_SCALE); + int w, h; + pango_layout_get_pixel_size(cl->layout, &w, &h); + if (h != ll->h) { + ll->h = h; + ll->h_dirty = 1; + } + ll->text_dirty = 0; + ll->dirty = 1; + } else if (ll->highlight_dirty) { + set_line_layout_attrs(buffer, line, cl->layout); } + ll->highlight_dirty = 0; +} + +static PangoLayout * +get_pango_layout(ledit_buffer *buffer, int line) { + set_pango_text_and_highlight(buffer, line); + ledit_line *ll = ledit_buffer_get_line(buffer, line); + cache_layout *cl = cache_get_layout( + ll->parent_buffer->cache, ll->cache_layout_index + ); + return cl->layout; } void -ledit_pos_to_x_softline(ledit_line *line, int pos, int *x_ret, int *softline_ret) { - normalize_and_set_pango_text(line); - pango_layout_index_to_line_x(line->layout, pos, 0, softline_ret, x_ret); +ledit_pos_to_x_softline(ledit_buffer *buffer, int line, int pos, int *x_ret, int *softline_ret) { + ledit_line *ll = ledit_buffer_get_line(buffer, line); + PangoLayout *layout = get_pango_layout(buffer, line); + pango_layout_index_to_line_x(layout, pos, 0, softline_ret, x_ret); /* FIXME: do these lines need to be unref'd? */ - PangoLayoutLine *pango_line = pango_layout_get_line_readonly(line->layout, *softline_ret); + PangoLayoutLine *pango_line = pango_layout_get_line_readonly(layout, *softline_ret); /* add left margin to x position if line is aligned right */ if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) { PangoRectangle rect; pango_layout_line_get_extents(pango_line, NULL, &rect); - *x_ret += (line->w * PANGO_SCALE - rect.width); + *x_ret += (ll->w * PANGO_SCALE - rect.width); } /* if in normal mode, change position to the middle of the current rectangle so that moving around won't jump weirdly */ /* FIXME: also in visual? */ /* FIXME: this is too much magic for my taste */ - if (line->parent_buffer->common->mode == NORMAL) { + if (ll->parent_buffer->common->mode == NORMAL) { PangoRectangle rect; - pango_layout_index_to_pos(line->layout, pos, &rect); + pango_layout_index_to_pos(layout, pos, &rect); *x_ret += rect.width / 2; } } /* FIXME: change this to return int */ void -ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret) { +ledit_x_softline_to_pos(ledit_buffer *buffer, int line, int x, int softline, int *pos_ret) { int trailing = 0; int x_relative = x; - normalize_and_set_pango_text(line); + ledit_line *ll = ledit_buffer_get_line(buffer, line); + PangoLayout *layout = get_pango_layout(buffer, line); PangoLayoutLine *pango_line = - pango_layout_get_line_readonly(line->layout, softline); + pango_layout_get_line_readonly(layout, softline); /* x is absolute, so the margin at the left needs to be subtracted */ if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) { PangoRectangle rect; pango_layout_line_get_extents(pango_line, NULL, &rect); - x_relative -= (line->w * PANGO_SCALE - rect.width); + x_relative -= (ll->w * PANGO_SCALE - rect.width); } pango_layout_line_x_to_index( pango_line, x_relative, pos_ret, &trailing ); /* if in insert mode, snap to the nearest border between graphemes */ /* FIXME: add parameter for this instead of checking mode */ - if (line->parent_buffer->common->mode == INSERT) { + if (ll->parent_buffer->common->mode == INSERT) { while (trailing > 0) { trailing--; - *pos_ret = ledit_line_next_utf8(line, *pos_ret); + *pos_ret = ledit_line_next_utf8(ll, *pos_ret); } } } -/* FIXME: make sure PangoLayout has newest text already when these functions are called */ int ledit_buffer_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos) { /* move back one grapheme if at end of line */ int ret = pos; - ledit_line *final_line = ledit_buffer_get_line(buffer, line); - normalize_and_set_pango_text(final_line); - if (pos == final_line->len && pos > 0) { + ledit_line *ll = ledit_buffer_get_line(buffer, line); + if (pos == ll->len && pos > 0) { int nattrs; + PangoLayout *layout = get_pango_layout(buffer, line); const PangoLogAttr *attrs = - pango_layout_get_log_attrs_readonly(final_line->layout, &nattrs); + pango_layout_get_log_attrs_readonly(layout, &nattrs); int cur = nattrs - 2; - ret = ledit_line_prev_utf8(final_line, ret); + ret = ledit_line_prev_utf8(ll, ret); while (ret > 0 && cur > 0 && !attrs[cur].is_cursor_position) { cur--; - ret = ledit_line_prev_utf8(final_line, ret); + ret = ledit_line_prev_utf8(ll, ret); } } return ret; @@ -1565,7 +1682,7 @@ ledit_buffer_delete_range_base( } int dell1 = l1, dell2 = l2; ledit_line *ll = ledit_buffer_get_line(buffer, line_index1); - ledit_pos_to_x_softline(ll, byte_index1, &x, &sl_useless); + ledit_pos_to_x_softline(buffer, line_index1, byte_index1, &x, &sl_useless); if (l1 > 0 && l2 < buffer->lines_num - 1) { rgl1 = l1; rgb1 = 0; @@ -1600,13 +1717,13 @@ ledit_buffer_delete_range_base( if (l2 < buffer->lines_num - 1) { new_line = l1; ledit_x_softline_to_pos( - ledit_buffer_get_line(buffer, l2 + 1), + buffer, l2 + 1, x, 0, &new_byte ); } else if (l1 > 0) { new_line = l1 - 1; ledit_x_softline_to_pos( - ledit_buffer_get_line(buffer, l1 - 1), + buffer, l1 - 1, x, 0, &new_byte ); } else { @@ -1626,23 +1743,22 @@ ledit_buffer_delete_range_base( } else if (delmode == DELETE_SOFTLINE) { int x, softline1, softline2; ledit_line *line1 = ledit_buffer_get_line(buffer, line_index1); - normalize_and_set_pango_text(line1); - ledit_pos_to_x_softline(line1, byte_index1, &x, &softline1); + ledit_pos_to_x_softline(buffer, line_index1, byte_index1, &x, &softline1); if (line_index1 == line_index2) { int x_useless; - pango_layout_index_to_line_x(line1->layout, byte_index2, 0, &softline2, &x_useless); + PangoLayout *layout = get_pango_layout(buffer, line_index1); + pango_layout_index_to_line_x(layout, byte_index2, 0, &softline2, &x_useless); int l1 = softline1 < softline2 ? softline1 : softline2; int l2 = softline1 < softline2 ? softline2 : softline1; - int softlines = pango_layout_get_line_count(line1->layout); - PangoLayoutLine *pl1 = pango_layout_get_line_readonly(line1->layout, l1); - PangoLayoutLine *pl2 = pango_layout_get_line_readonly(line1->layout, l2); + PangoLayoutLine *pl1 = pango_layout_get_line_readonly(layout, l1); + PangoLayoutLine *pl2 = pango_layout_get_line_readonly(layout, l2); /* don't delete entire line if it is the last one remaining */ - if (l1 == 0 && l2 == softlines - 1 && buffer->lines_num > 1) { + if (l1 == 0 && l2 == line1->softlines - 1 && buffer->lines_num > 1) { if (line_index1 < buffer->lines_num - 1) { /* cursor can be moved to next hard line */ new_line = line_index1; ledit_x_softline_to_pos( - ledit_buffer_get_line(buffer, line_index1 + 1), + buffer, line_index1 + 1, x, 0, &new_byte ); rgl1 = line_index1; @@ -1655,9 +1771,10 @@ ledit_buffer_delete_range_base( /* note: logically, line_index1 - 1 must be >= 0 because buffer->lines_num > 1 && line_index1 >= buffer->lines_num - 1 */ new_line = line_index1 - 1; - ledit_line *prevline = ledit_buffer_get_line(buffer, line_index1 - 1); - softlines = pango_layout_get_line_count(prevline->layout); - ledit_x_softline_to_pos(prevline, x, softlines - 1, &new_byte); + ledit_line *prevline = ledit_buffer_get_line(buffer, new_line); + if (prevline->text_dirty) + set_pango_text_and_highlight(buffer, new_line); + ledit_x_softline_to_pos(buffer, new_line, x, prevline->softlines - 1, &new_byte); rgl1 = line_index1 - 1; rgb1 = prevline->len; rgl2 = line_index1; @@ -1686,22 +1803,22 @@ ledit_buffer_delete_range_base( delete_line_section_base( buffer, line_index1, rgb1, rgb2 - rgb1 ); - if (l2 == softlines - 1 && line_index1 < buffer->lines_num - 1) { + if (l2 == line1->softlines - 1 && line_index1 < buffer->lines_num - 1) { new_line = line_index1 + 1; ledit_x_softline_to_pos( - ledit_buffer_get_line(buffer, line_index1 + 1), + buffer, line_index1 + 1, x, 0, &new_byte ); - } else if (l2 < softlines - 1) { + } else if (l2 < line1->softlines - 1) { new_line = line_index1; ledit_x_softline_to_pos( - ledit_buffer_get_line(buffer, line_index1), + buffer, line_index1, x, l1, &new_byte ); } else if (l1 > 0) { new_line = line_index1; ledit_x_softline_to_pos( - ledit_buffer_get_line(buffer, line_index1), + buffer, line_index1, x, l1 - 1, &new_byte ); } else { @@ -1726,14 +1843,13 @@ ledit_buffer_delete_range_base( } ledit_line *ll1 = ledit_buffer_get_line(buffer, l1); ledit_line *ll2 = ledit_buffer_get_line(buffer, l2); - normalize_and_set_pango_text(ll1); - normalize_and_set_pango_text(ll2); - pango_layout_index_to_line_x(ll1->layout, b1, 0, &sl1, &x_useless); - pango_layout_index_to_line_x(ll2->layout, b2, 0, &sl2, &x_useless); - PangoLayoutLine *pl1 = pango_layout_get_line_readonly(ll1->layout, sl1); - PangoLayoutLine *pl2 = pango_layout_get_line_readonly(ll2->layout, sl2); - int softlines = pango_layout_get_line_count(ll2->layout); - if (sl1 == 0 && sl2 == softlines - 1) { + PangoLayout *layout1 = get_pango_layout(buffer, l1); + PangoLayout *layout2 = get_pango_layout(buffer, l2); + pango_layout_index_to_line_x(layout1, b1, 0, &sl1, &x_useless); + pango_layout_index_to_line_x(layout2, b2, 0, &sl2, &x_useless); + PangoLayoutLine *pl1 = pango_layout_get_line_readonly(layout1, sl1); + PangoLayoutLine *pl2 = pango_layout_get_line_readonly(layout2, sl2); + if (sl1 == 0 && sl2 == ll2->softlines - 1) { if (l1 == 0 && l2 == buffer->lines_num - 1) { rgl1 = l1; rgl2 = l2; @@ -1754,17 +1870,17 @@ ledit_buffer_delete_range_base( if (l2 == buffer->lines_num - 1) { new_line = l1 - 1; ledit_line *new_lline = ledit_buffer_get_line(buffer, new_line); - int new_softlines = pango_layout_get_line_count(new_lline->layout); - ledit_x_softline_to_pos(new_lline, x, new_softlines - 1, &new_byte); + if (new_lline->text_dirty) + set_pango_text_and_highlight(buffer, new_line); + ledit_x_softline_to_pos(buffer, new_line, x, new_lline->softlines - 1, &new_byte); rgl1 = l1 - 1; rgb1 = new_lline->len; rgl2 = l2; rgb2 = ll2->len; } else { new_line = l1; - ledit_line *nextline = ledit_buffer_get_line(buffer, l2 + 1); ledit_x_softline_to_pos( - nextline, x, 0, &new_byte + buffer, l2 + 1, x, 0, &new_byte ); rgl1 = l1; rgb1 = 0; @@ -1794,20 +1910,20 @@ ledit_buffer_delete_range_base( } delete_line_section_base(buffer, l2, 0, pl2->start_index + pl2->length); new_line = l1; - ledit_x_softline_to_pos(ll2, x, 0, &new_byte); + ledit_x_softline_to_pos(buffer, l2, x, 0, &new_byte); ledit_buffer_delete_line_entries_base(buffer, l1, l2 - 1); - } else if (sl2 == softlines - 1) { + } else if (sl2 == ll2->softlines - 1) { rgl1 = l1; rgb1 = pl1->start_index; rgl2 = l2; rgb2 = ll2->len; if (l2 + 1 == buffer->lines_num) { new_line = l1; - ledit_x_softline_to_pos(ll1, x, sl1 - 1, &new_byte); + ledit_x_softline_to_pos(buffer, l1, x, sl1 - 1, &new_byte); } else { new_line = l1 + 1; ledit_x_softline_to_pos( - ledit_buffer_get_line(buffer, l2 + 1), + buffer, l2 + 1, x, 0, &new_byte ); } @@ -1843,7 +1959,7 @@ ledit_buffer_delete_range_base( ); ledit_buffer_delete_line_entries_base(buffer, l1 + 1, l2); new_line = l1; - int new_softlines = pango_layout_get_line_count(ll1->layout); + set_pango_text_and_highlight(buffer, l1); /* it's technically possible that the remaining part of the second line is so small that it doesn't generate a new softline, so there needs to be a special case - this is @@ -1851,7 +1967,7 @@ ledit_buffer_delete_range_base( same line, but it now includes the rest of the second line (FIXME: this is probably not the best thing to do) */ ledit_x_softline_to_pos( - ll1, x, sl1 + 1 < new_softlines ? sl1 + 1 : sl1, &new_byte + buffer, l1, x, sl1 + 1 < ll1->softlines ? sl1 + 1 : sl1, &new_byte ); } } @@ -1919,22 +2035,21 @@ ledit_buffer_delete_range_base( *new_byte_ret = new_byte; } -/* FIXME: always normalize lines */ +/* FIXME: any way to make this more efficient? */ void ledit_buffer_resize_textview(ledit_buffer *buffer) { buffer->total_height = 0; - int tmp_w, tmp_h; int text_w, text_h; ledit_window_get_textview_size(buffer->window, &text_w, &text_h); for (int i = 0; i < buffer->lines_num; i++) { ledit_line *line = ledit_buffer_get_line(buffer, i); - pango_layout_set_width(line->layout, text_w * PANGO_SCALE); - pango_layout_get_pixel_size(line->layout, &tmp_w, &tmp_h); - line->h = tmp_h; line->w = text_w; + line->text_dirty = 1; + set_pango_text_and_highlight(buffer, i); line->y_offset = buffer->total_height; line->dirty = 1; - buffer->total_height += tmp_h; + line->h_dirty = 0; + buffer->total_height += line->h; } ledit_window_set_scroll_max(buffer->window, buffer->total_height); if (buffer->display_offset > 0 && @@ -1956,6 +2071,7 @@ ledit_buffer_scroll(ledit_buffer *buffer, long new_offset) { } /* FIXME: there's gotta be a better/more efficient way to do this... */ +/* FIXME: make sure h_dirty is not set here */ void ledit_buffer_get_nearest_legal_pos( ledit_buffer *buffer, @@ -1967,10 +2083,9 @@ ledit_buffer_get_nearest_legal_pos( int x, sl_useless; ledit_window_get_textview_size(buffer->window, &text_w, &text_h); ledit_line *lline = ledit_buffer_get_line(buffer, line); - pango_layout_get_cursor_pos( - lline->layout, byte, &strong, &weak - ); - ledit_pos_to_x_softline(lline, byte, &x, &sl_useless); + PangoLayout *layout = get_pango_layout(buffer, line); + pango_layout_get_cursor_pos(layout, byte, &strong, &weak); + ledit_pos_to_x_softline(buffer, line, byte, &x, &sl_useless); long cursor_y = strong.y / PANGO_SCALE + lline->y_offset; PangoRectangle ink, log; if (cursor_y < buffer->display_offset) { @@ -1980,13 +2095,14 @@ ledit_buffer_get_nearest_legal_pos( lline = ledit_buffer_get_line(buffer, ++hline); } /* the current hard line is now the one at the very top of the screen*/ - int num_sl = pango_layout_get_line_count(lline->layout); + layout = get_pango_layout(buffer, hline); + int num_sl = lline->softlines; int cur_y_off = 0; int sl_index = -1; PangoLayoutLine *sl; /* search for first soft line completely on-screen */ for (int i = 0; i < num_sl; i++) { - sl = pango_layout_get_line_readonly(lline->layout, i); + sl = pango_layout_get_line_readonly(layout, i); if (cur_y_off + lline->y_offset >= buffer->display_offset) { sl_index = i; break; @@ -1997,17 +2113,16 @@ ledit_buffer_get_nearest_legal_pos( if (sl_index >= 0) { /* we found the correct soft line */ *line_ret = hline; - ledit_x_softline_to_pos(lline, x, sl_index, byte_ret); + ledit_x_softline_to_pos(buffer, hline, x, sl_index, byte_ret); } else if (hline < buffer->lines_num - 1) { /* need to move to next hard line */ *line_ret = hline + 1; - lline = ledit_buffer_get_line(buffer, hline + 1); - ledit_x_softline_to_pos(lline, x, 0, byte_ret); + ledit_x_softline_to_pos(buffer, hline + 1, x, 0, byte_ret); } else { /* no idea if this can happen, but just fail and use the last soft line of the last hard line */ *line_ret = hline; - ledit_x_softline_to_pos(lline, x, num_sl - 1, byte_ret); + ledit_x_softline_to_pos(buffer, hline, x, num_sl - 1, byte_ret); } } else if (cursor_y + strong.height / PANGO_SCALE > buffer->display_offset + text_h) { @@ -2017,13 +2132,14 @@ ledit_buffer_get_nearest_legal_pos( lline = ledit_buffer_get_line(buffer, --hline); } /* the current hard line is now the one at the very bottom of the screen*/ - int num_sl = pango_layout_get_line_count(lline->layout); + layout = get_pango_layout(buffer, hline); + int num_sl = lline->softlines; int cur_y_off = 0; int sl_index = -1; PangoLayoutLine *sl; /* search for last soft line completely on-screen */ for (int i = num_sl - 1; i >= 0; i--) { - sl = pango_layout_get_line_readonly(lline->layout, i); + sl = pango_layout_get_line_readonly(layout, i); if (lline->y_offset + lline->h - cur_y_off < buffer->display_offset + text_h) { sl_index = i; break; @@ -2034,18 +2150,18 @@ ledit_buffer_get_nearest_legal_pos( if (sl_index >= 0) { /* we found the correct soft line */ *line_ret = hline; - ledit_x_softline_to_pos(lline, x, sl_index, byte_ret); + ledit_x_softline_to_pos(buffer, hline, x, sl_index, byte_ret); } else if (hline > 0) { /* need to move to previous hard line */ *line_ret = hline - 1; lline = ledit_buffer_get_line(buffer, hline - 1); - num_sl = pango_layout_get_line_count(lline->layout); - ledit_x_softline_to_pos(lline, x, num_sl - 1, byte_ret); + num_sl = lline->softlines; + ledit_x_softline_to_pos(buffer, hline - 1, x, num_sl - 1, byte_ret); } else { /* no idea if this can happen, but just fail and use the first soft line of the first hard line */ *line_ret = hline; - ledit_x_softline_to_pos(lline, x, 0, byte_ret); + ledit_x_softline_to_pos(buffer, hline, x, 0, byte_ret); } } } @@ -2060,9 +2176,10 @@ ledit_xy_to_line_byte(ledit_buffer *buffer, int x, int y, int snap_to_nearest, i ledit_line *line = ledit_buffer_get_line(buffer, i); if ((h <= pos && h + line->h > pos) || i == buffer->lines_num - 1) { int index, trailing; + PangoLayout *layout = get_pango_layout(buffer, i); /* FIXME: what if i == buffer->lines_num - 1 but pos - h < 0? */ pango_layout_xy_to_index( - line->layout, + layout, x * PANGO_SCALE, (int)(pos - h) * PANGO_SCALE, &index, &trailing ); @@ -2086,7 +2203,8 @@ scroll_to_pos(ledit_buffer *buffer, int line, int byte, int top) { int text_w, text_h; ledit_window_get_textview_size(buffer->window, &text_w, &text_h); ledit_line *ll = ledit_buffer_get_line(buffer, line); - pango_layout_get_cursor_pos(ll->layout, byte, &strong, &weak); + PangoLayout *layout = get_pango_layout(buffer, line); + pango_layout_get_cursor_pos(layout, byte, &strong, &weak); long cursor_y = strong.y / PANGO_SCALE + ll->y_offset; if (top) { ledit_buffer_scroll(buffer, cursor_y); @@ -2111,8 +2229,9 @@ ledit_buffer_ensure_cursor_shown(ledit_buffer *buffer) { int text_w, text_h; ledit_window_get_textview_size(buffer->window, &text_w, &text_h); ledit_line *line = ledit_buffer_get_line(buffer, buffer->cur_line); + PangoLayout *layout = get_pango_layout(buffer, buffer->cur_line); pango_layout_get_cursor_pos( - line->layout, buffer->cur_index, &strong, &weak + layout, buffer->cur_index, &strong, &weak ); long cursor_y = strong.y / PANGO_SCALE + line->y_offset; if (cursor_y < buffer->display_offset) { @@ -2184,18 +2303,11 @@ ledit_buffer_set_selection(ledit_buffer *buffer, int line1, int byte1, int line2 } } if (l1_new >= 0 && l2_new >= 0) { - if (l1_new == l2_new) { - ledit_buffer_set_line_selection(buffer, l1_new, b1_new, b2_new); - } else { - ledit_line *ll1 = ledit_buffer_get_line(buffer, l1_new); - ledit_buffer_set_line_selection(buffer, l1_new, b1_new, ll1->len); - ledit_buffer_set_line_selection(buffer, l2_new, 0, b2_new); - /* FIXME: optimize this */ - for (int i = l1_new + 1; i < l2_new; i++) { - if (i <= buffer->sel.line1 || i >= buffer->sel.line2) { - ledit_line *llx = ledit_buffer_get_line(buffer, i); - ledit_buffer_set_line_selection(buffer, i, 0, llx->len); - } + for (int i = l1_new; i <= l2_new; i++) { + /* only change the ones that were not already selected */ + if (i <= buffer->sel.line1 || i >= buffer->sel.line2) { + ledit_line *ll = ledit_buffer_get_line(buffer, i); + ll->highlight_dirty = 1; } } if (l1_new != l2_new || b1_new != b2_new) @@ -2281,7 +2393,10 @@ ledit_buffer_redraw(ledit_buffer *buffer) { for (int i = 0; i < buffer->lines_num; i++) { ledit_line *line = ledit_buffer_get_line(buffer, i); if (h + line->h > buffer->display_offset) { - if (line->dirty || line->cache_index == -1) { + /* FIXME: line->text_dirty should not happen here */ + if (line->text_dirty || line->highlight_dirty) + set_pango_text_and_highlight(buffer, i); + if (line->dirty || line->cache_pixmap_index == -1) { ledit_buffer_render_line(buffer, i); } int final_y = 0; @@ -2296,8 +2411,8 @@ ledit_buffer_redraw(ledit_buffer *buffer) { final_h -= final_y + final_h - buffer->display_offset - text_h; } - ledit_cache_pixmap *pix = ledit_get_cache_pixmap( - buffer->cache, line->cache_index + cache_pixmap *pix = cache_get_pixmap( + buffer->cache, line->cache_pixmap_index ); XCopyArea( buffer->common->dpy, pix->pixmap, @@ -2308,17 +2423,18 @@ ledit_buffer_redraw(ledit_buffer *buffer) { cur_line_y = h - buffer->display_offset; cursor_displayed = 1; } + if (h + line->h >= buffer->display_offset + text_h) + break; } - if (h + line->h >= buffer->display_offset + text_h) - break; h += line->h; } XSetForeground(buffer->common->dpy, buffer->window->gc, buffer->theme->text_fg.pixel); PangoRectangle strong, weak; ledit_line *cur_line = ledit_buffer_get_line(buffer, buffer->cur_line); + PangoLayout *layout = get_pango_layout(buffer, buffer->cur_line); pango_layout_get_cursor_pos( - cur_line->layout, buffer->cur_index, &strong, &weak + layout, buffer->cur_index, &strong, &weak ); /* FIXME: long, int, etc. */ int cursor_y = strong.y / PANGO_SCALE + cur_line_y; @@ -2338,11 +2454,11 @@ ledit_buffer_redraw(ledit_buffer *buffer) { if (buffer->cur_index >= cur_line->len) tmp_index = cur_line->len - 1; if (tmp_index >= 0) - dir = pango_layout_get_direction(cur_line->layout, tmp_index); + dir = pango_layout_get_direction(layout, tmp_index); int x, sli; - pango_layout_index_to_line_x(cur_line->layout, buffer->cur_index, 0, &sli, &x); - PangoLayoutLine *sl = pango_layout_get_line_readonly(cur_line->layout, sli); + pango_layout_index_to_line_x(layout, buffer->cur_index, 0, &sli, &x); + PangoLayoutLine *sl = pango_layout_get_line_readonly(layout, sli); if (dir != sl->resolved_dir) { box_w = 3; } diff --git a/buffer.h b/buffer.h @@ -2,20 +2,24 @@ typedef struct ledit_buffer ledit_buffer; /* FIXME: size_t for len, etc. */ typedef struct { - PangoLayout *layout; - char *text; ledit_buffer *parent_buffer; - int gap; /* position of gap for gap buffer */ - int cap; /* allocated space for text */ - int len; /* actual length of text */ - int w; - int h; - long y_offset; /* pixel offset starting at the top of the file */ - int cache_index; /* index of pixmap in cache, or -1 if not assigned */ - char dirty; /* whether line needs to be rendered before being drawn */ - char text_dirty; /* whether the text in the PangoLayout needs to be - updated before the layout is rendered */ - char h_dirty; /* whether height needs to be recalculated still */ + char *text; /* text, stored as gap buffer */ + int gap; /* position of gap for gap buffer */ + int cap; /* allocated space for text */ + int len; /* actual length of text */ + int w; /* width in pixels */ + int h; /* height in pixels */ + long y_offset; /* pixel offset starting at the top of the file */ + int cache_pixmap_index; /* index of pixmap in cache, or -1 if not assigned */ + int cache_layout_index; /* index of pango layout in cache, or -1 if not assigned */ + int cursor_index; /* cursor index if it should be highlighted, -1 else */ + int softlines; /* number of softlines - cached from PangoLayout */ + char dirty; /* whether line needs to be rendered before being drawn */ + char text_dirty; /* whether the text in the PangoLayout needs to be + * updated before the layout is rendered */ + char highlight_dirty; /* whether highlight (cursor or selection) needs to be + * updated still in the PangoLayout before rendering */ + char h_dirty; /* whether height needs to be recalculated */ } ledit_line; typedef struct { @@ -64,15 +68,14 @@ int ledit_buffer_load_file(ledit_buffer *buffer, char *filename, int line, char int ledit_buffer_write_to_file(ledit_buffer *buffer, char *filename, char **errstr); void ledit_buffer_destroy(ledit_buffer *buffer); void ledit_buffer_normalize_line(ledit_line *line); -void ledit_buffer_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte); void ledit_buffer_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index); void ledit_buffer_wipe_line_cursor_attrs(ledit_buffer *buffer, int line); void ledit_buffer_render_line(ledit_buffer *buffer, int line_index); ledit_line *ledit_buffer_get_line(ledit_buffer *buffer, int index); int ledit_buffer_line_visible(ledit_buffer *buffer, int index); int ledit_buffer_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos); -void ledit_pos_to_x_softline(ledit_line *line, int pos, int *x_ret, int *softline_ret); -void ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret); +void ledit_pos_to_x_softline(ledit_buffer *buffer, int line, int pos, int *x_ret, int *softline_ret); +void ledit_x_softline_to_pos(ledit_buffer *buffer, int line, int x, int softline, int *pos_ret); int ledit_line_next_utf8(ledit_line *line, int index); int ledit_line_prev_utf8(ledit_line *line, int index); int ledit_buffer_next_cursor_pos(ledit_buffer *buffer, int line, int byte, int num); @@ -182,5 +185,5 @@ void ledit_buffer_scroll_to_pos_top(ledit_buffer *buffer, int line, int byte); void ledit_buffer_scroll_to_pos_bottom(ledit_buffer *buffer, int line, int byte); /* FIXME: just make generic sort range */ void ledit_buffer_sort_selection(int *line1, int *byte1, int *line2, int *byte2); -int ledit_line_next_non_whitespace(ledit_line *line, int byte); +int ledit_line_next_non_whitespace(ledit_buffer *buffer, int line, int byte); void ledit_buffer_insert_mark(ledit_buffer *buffer, char *mark, int len, int line, int byte); diff --git a/cache.c b/cache.c @@ -1,3 +1,6 @@ +#include <stdio.h> +#include <assert.h> +#include <limits.h> #include <stdlib.h> #include <X11/Xlib.h> #include <X11/Xutil.h> @@ -9,73 +12,153 @@ #include "cache.h" ledit_cache * -ledit_cache_create(ledit_common *common) { - /* FIXME: prevent overflow */ +cache_create(Display *dpy) { ledit_cache *cache = ledit_malloc(sizeof(ledit_cache)); - cache->dpy = common->dpy; - cache->entries = ledit_malloc(20 * sizeof(ledit_cache_pixmap)); - for (int i = 0; i < 20; i++) { - cache->entries[i].pixmap = None; - cache->entries[i].draw = NULL; - cache->entries[i].line = -1; + cache->dpy = dpy; + cache->pixmaps = ledit_reallocarray(NULL, PIXMAP_CACHE_INITIAL_SIZE, sizeof(cache_pixmap)); + cache->layouts = ledit_reallocarray(NULL, LAYOUT_CACHE_SIZE, sizeof(cache_layout)); + for (size_t i = 0; i < PIXMAP_CACHE_INITIAL_SIZE; i++) { + cache->pixmaps[i].pixmap = None; + cache->pixmaps[i].draw = NULL; + cache->pixmaps[i].line = -1; } - cache->entries_num = 20; - cache->cur_replace_index = -1; + for (size_t i = 0; i < LAYOUT_CACHE_SIZE; i++) { + cache->layouts[i].layout = NULL; + cache->layouts[i].line = -1; + } + cache->num_pixmaps = PIXMAP_CACHE_INITIAL_SIZE; + cache->num_layouts = LAYOUT_CACHE_SIZE; + cache->cur_pixmap_index = cache->cur_layout_index = 0; return cache; } void -ledit_cache_flush(ledit_cache *cache) { - for (int i = 0; i < cache->entries_num; i++) { - cache->entries[i].line = -1; +cache_flush( + ledit_cache *cache, void *callback_data, + void (*set_pixmap_line)(void *, int, int), + void (*set_layout_line)(void *, int, int)) { + cache_invalidate_from_line( + cache, 0, callback_data, set_pixmap_line, set_layout_line + ); +} + +void +cache_invalidate_from_line( + ledit_cache *cache, int start, void *callback_data, + void (*set_pixmap_line)(void *, int, int), + void (*set_layout_line)(void *, int, int)) { + for (size_t i = 0; i < cache->num_pixmaps; i++) { + if (cache->pixmaps[i].line >= start) { + set_pixmap_line(callback_data, cache->pixmaps[i].line, -1); + cache->pixmaps[i].line = -1; + } + } + for (size_t i = 0; i < cache->num_layouts; i++) { + if (cache->layouts[i].line >= start) { + set_layout_line(callback_data, cache->layouts[i].line, -1); + cache->layouts[i].line = -1; + } } } void -ledit_cache_destroy(ledit_cache *cache) { - for (int i = 0; i < cache->entries_num; i++) { - if (cache->entries[i].pixmap != None) - XFreePixmap(cache->dpy, cache->entries[i].pixmap); - if (cache->entries[i].draw != NULL) - XftDrawDestroy(cache->entries[i].draw); +cache_destroy(ledit_cache *cache) { + for (size_t i = 0; i < cache->num_pixmaps; i++) { + if (cache->pixmaps[i].pixmap != None) + XFreePixmap(cache->dpy, cache->pixmaps[i].pixmap); + if (cache->pixmaps[i].draw != NULL) + XftDrawDestroy(cache->pixmaps[i].draw); + } + for (size_t i = 0; i < cache->num_layouts; i++) { + if (cache->layouts[i].layout != NULL) + g_object_unref(cache->layouts[i].layout); } - free(cache->entries); + free(cache->pixmaps); + free(cache->layouts); free(cache); } -/* returns a cache index that is currently not needed (if needed, the cache size is increased) - whether it is needed or not is checked with line_needed, to which - callback_data is always passed as the first argument */ -int -ledit_get_unneeded_cache_index(ledit_cache *cache, void *callback_data, int (*line_needed)(void *, int)) { - int entry_index; +cache_pixmap * +cache_get_pixmap(ledit_cache *cache, int index) { + assert(index >= 0 && (size_t)index < cache->num_pixmaps); + return &cache->pixmaps[index]; +} + +cache_layout * +cache_get_layout(ledit_cache *cache, int index) { + assert(index >= 0 && (size_t)index < cache->num_layouts); + return &cache->layouts[index]; +} + +/* FIXME: standardize overflow checking */ +static void +err_overflow(void) { + fprintf(stderr, "ERROR: Integer overflow in cache handling.\n"); + exit(1); +} + +/* FIXME: decide on int or size_t, but not both */ +/* or maybe ssize_t */ +void +cache_assign_pixmap_index( + ledit_cache *cache, int line, + void *callback_data, + int (*line_needed)(void *, int), + void (*set_pixmap_line)(void *, int, int)) { int line_index; - /* start at 1 because the cache->cur_replace_index is actually the last entry that was replaced */ - for (int i = 1; i <= cache->entries_num; i++) { - entry_index = (i + cache->cur_replace_index) % cache->entries_num; - line_index = cache->entries[entry_index].line; + size_t entry_index; + for (size_t i = 0; i <= cache->num_pixmaps; i++) { + entry_index = (i + cache->cur_pixmap_index) % cache->num_pixmaps; + line_index = cache->pixmaps[entry_index].line; /* replace line when entry isn't assigned or currently assigned line is not visible */ if (line_index == -1 || (line_index >= 0 && !line_needed(callback_data, line_index))) { - cache->cur_replace_index = entry_index; - return entry_index; + cache->cur_pixmap_index = (entry_index + 1) % cache->num_pixmaps; + if (entry_index > INT_MAX) + err_overflow(); + cache_pixmap *pix = &cache->pixmaps[entry_index]; + if (pix->line >= 0) + set_pixmap_line(callback_data, pix->line, -1); + pix->line = line; + set_pixmap_line(callback_data, line, (int)entry_index); + return; } } /* no free entry found, increase cache size */ - cache->entries = ledit_realloc(cache->entries, cache->entries_num * 2 * sizeof(ledit_cache_pixmap)); - entry_index = cache->entries_num; - for (int i = cache->entries_num; i < cache->entries_num * 2; i++) { - cache->entries[i].line = -1; - cache->entries[i].pixmap = None; - cache->entries[i].draw = NULL; + /* FIXME: what is the ideal size to resize to? */ + /* FIXME: overflow */ + /* FIXME: maybe have maximum cache size */ + cache->pixmaps = ledit_reallocarray(cache->pixmaps, cache->num_pixmaps * 2, sizeof(cache_pixmap)); + entry_index = cache->num_pixmaps; + for (size_t i = cache->num_pixmaps; i < cache->num_pixmaps * 2; i++) { + cache->pixmaps[i].line = -1; + cache->pixmaps[i].pixmap = None; + cache->pixmaps[i].draw = NULL; } - cache->entries_num *= 2; - return entry_index; + cache->num_pixmaps *= 2; + if (entry_index > INT_MAX) + err_overflow(); + cache_pixmap *pix = &cache->pixmaps[entry_index]; + pix->line = line; + set_pixmap_line(callback_data, line, (int)entry_index); } -ledit_cache_pixmap * -ledit_get_cache_pixmap(ledit_cache *cache, int index) { - return &cache->entries[index]; +/* FIXME: perhaps use "real" clock cache management, i.e. set a bit on a cache entry + when it is used so it isn't invalidated yet. */ +void +cache_assign_layout_index( + ledit_cache *cache, int line, + void *callback_data, + void (*set_layout_line)(void *, int, int)) { + size_t old = cache->cur_layout_index; + cache->cur_layout_index = (cache->cur_layout_index + 1) % cache->num_layouts; + if (old > INT_MAX) + err_overflow(); + cache_layout *layout = &cache->layouts[old]; + if (layout->line >= 0) + set_layout_line(callback_data, layout->line, -1); + layout->line = line; + set_layout_line(callback_data, line, (int)old); } diff --git a/cache.h b/cache.h @@ -1,19 +1,125 @@ +/* + *The maximum number of layouts in the cache. + */ +#define LAYOUT_CACHE_SIZE 40 + +/* + * The initial number of pixmas in the cache. + * The size is increased when more pixmaps are visible + * at the same time than there are entries in the cache. + */ +#define PIXMAP_CACHE_INITIAL_SIZE 20 + typedef struct { Pixmap pixmap; XftDraw *draw; - int w, h; - int line; -} ledit_cache_pixmap; + int w, h; /* width and height of the pixmap */ + int line;/* the line associated with this entry, or -1 if unassigned */ +} cache_pixmap; + +typedef struct { + PangoLayout *layout; + int line; /* the line associated with this entry, or -1 if unassigned */ +} cache_layout; typedef struct { Display *dpy; - ledit_cache_pixmap *entries; - int entries_num; - int cur_replace_index; + cache_pixmap *pixmaps; + cache_layout *layouts; + size_t num_pixmaps; + size_t num_layouts; + size_t cur_pixmap_index; /* current replacement index for pixmaps */ + size_t cur_layout_index; /* current replacement index for layouts */ } ledit_cache; -ledit_cache *ledit_cache_create(ledit_common *common); -void ledit_cache_flush(ledit_cache *cache); -void ledit_cache_destroy(ledit_cache *cache); -ledit_cache_pixmap *ledit_get_cache_pixmap(ledit_cache *cache, int index); -int ledit_get_unneeded_cache_index(ledit_cache *cache, void *callback_data, int (*line_needed)(void *, int)); +/* FIXME: maybe handle pixmap creation and resizing here */ + +/* + * Create cache using X Display dpy (this is + * needed to destroy the pixmaps in the end). + */ +ledit_cache *cache_create(Display *dpy); + +/* + * Reset line index of every cache entry (pixmaps and layouts). + * set_pixmap_line is called with callback_data as its first argument, + * a line index which has been removed from the pixmap cache as the + * second argument, and '-1' as the third argument. + * set_layout_line is the same, but for the layout cache. + */ +void cache_flush( + ledit_cache *cache, + void *callback_data, + void (*set_pixmap_line)(void *, int, int), + void (*set_layout_line)(void *, int, int) +); + +/* + * Like cache_flush, but only line numbers >= start are invalidated. + */ +void cache_invalidate_from_line( + ledit_cache *cache, int start, + void *callback_data, + void (*set_pixmap_line)(void *, int, int), + void (*set_layout_line)(void *, int, int) +); + +/* + * Destroy cache. + */ +void cache_destroy(ledit_cache *cache); + +/* + * Get the cache_pixmap at index. + */ +cache_pixmap *cache_get_pixmap(ledit_cache *cache, int index); + +/* + * Get the cache_layout at index. + */ +cache_layout *cache_get_layout(ledit_cache *cache, int index); + +/* + * The following two functions have a somewhat cumbersome interface + * because set_pixmap_line and set_layout_line are required as helper + * functions instead of just returning the cache index and letting the + * caller handle the updating. However, this has led to horrible bugs + * in the past, when one of the updating steps was forgotten, so this + * interface was designed instead to avoid some of those more basic bugs. + */ + +/* + * Assign an unneeded pixmap cache index to the line with index 'line'. + * line_needed is used to check if a line is needed. + * It is called with callback_data as the first argument and the + * line to be checked as the second argument. + * The line of the cache entry is set to 'line' and if a line was + * set before, it is reset by calling 'reset_pixmap_line' with + * 'callback_data' as the first argument, the old line as the second + * argument, and '-1' as the third argument. + * Similarly, the cache index of the new line is changed by calling + * 'set_pixmap_line' with the new cache index as the third argument. + */ +void cache_assign_pixmap_index( + ledit_cache *cache, int line, + void *callback_data, + int (*line_needed)(void *, int), + void (*set_pixmap_line)(void *, int, int) +); + +/* + * Assign a layout cache index to the line with index 'line'. + * Since it is not clear which layouts are needed more, this just + * uses the next index in a clock fashion. + * The line of the cache entry is set to 'line' and if a line was + * set before, it is reset by calling 'set_layout_line' with + * 'callback_data' as the first argument, the old line as the second + * argument, and '-1' as the third argument. + * Similarly, the cache index of the new line is changed by calling + * 'set_layout_line' with the new cache index as the third argument. + */ +void cache_assign_layout_index( + ledit_cache *cache, int line, + void *callback_data, + void (*set_layout_line)(void *, int, int) +); diff --git a/keys_basic.c b/keys_basic.c @@ -654,7 +654,7 @@ scroll_lines(ledit_buffer *buffer, int lines, int dir) { ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); ledit_buffer_get_cursor_pixel_pos(buffer, buffer->cur_line, buffer->cur_index, &x, &y, &h); /* get the middle position of char */ - ledit_pos_to_x_softline(ll, buffer->cur_index, &x, &sli); + ledit_pos_to_x_softline(buffer, buffer->cur_line, buffer->cur_index, &x, &sli); long abs_pos = ll->y_offset + y; ledit_window_get_textview_size(buffer->window, &text_w, &text_h); if (lines > 0) @@ -670,7 +670,7 @@ scroll_lines(ledit_buffer *buffer, int lines, int dir) { int start, end; ledit_buffer_get_softline_bounds(buffer, buffer->cur_line, sli, &start, &end); ll = ledit_buffer_get_line(buffer, buffer->cur_line); - ledit_x_softline_to_pos(ll, x, sli, &buffer->cur_index); + ledit_x_softline_to_pos(buffer, buffer->cur_line, x, sli, &buffer->cur_index); ledit_buffer_get_cursor_pixel_pos(buffer, buffer->cur_line, buffer->cur_index, &x, &y, &h); long new_abs_pos = ll->y_offset + y; ledit_buffer_scroll(buffer, buffer->display_offset + (new_abs_pos - abs_pos)); @@ -806,7 +806,7 @@ move_half_screen(ledit_buffer *buffer, int movement) { /* try to keep current x position of cursor */ int x, softline; /* FIXME: properly document what uses PANGO_SCALE and what not */ - ledit_pos_to_x_softline(ll, buffer->cur_index, &x, &softline); + ledit_pos_to_x_softline(buffer, buffer->cur_line, buffer->cur_index, &x, &softline); ledit_xy_to_line_byte( buffer, x / PANGO_SCALE, y, 0, &buffer->cur_line, &buffer->cur_index @@ -1355,7 +1355,8 @@ backspace(ledit_buffer *buffer, char *text, int len) { int i = ledit_line_prev_utf8(l, buffer->cur_index); delete_range(buffer, 0, 0, buffer->cur_line, buffer->cur_index, buffer->cur_line, i, 0); } - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + /* FIXME: This was probably a mistake earlier, right? + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);*/ return (struct action){ACTION_NONE, NULL}; } @@ -1374,7 +1375,8 @@ delete_key(ledit_buffer *buffer, char *text, int len) { int i = ledit_line_next_utf8(cur_line, buffer->cur_index); delete_range(buffer, 0, 0, buffer->cur_line, buffer->cur_index, buffer->cur_line, i, 0); } - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + /* FIXME: This was probably a mistake earlier, right? + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);*/ return (struct action){ACTION_NONE, NULL}; } @@ -1419,8 +1421,8 @@ move_to_eol(ledit_buffer *buffer, char *text, int len) { buffer, buffer->cur_line, buffer->cur_index ); } + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); } - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); return (struct action){ACTION_NONE, NULL}; } @@ -1594,7 +1596,6 @@ escape_key(ledit_buffer *buffer, char *text, int len) { } buffer->sel.line1 = buffer->sel.line2 = -1; buffer->sel.byte1 = buffer->sel.byte2 = -1; - /* FIXME: optimize this to avoid first wiping and then setting the attrs */ ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); } return (struct action){ACTION_NONE, NULL}; @@ -1629,16 +1630,14 @@ move_cursor_up_down(ledit_buffer *buffer, int dir) { num, &new_line, &new_softline ); - ledit_line *cur_lline = ledit_buffer_get_line(buffer, buffer->cur_line); - ledit_line *new_lline = ledit_buffer_get_line(buffer, new_line); if (cb != NULL) { int start, end; ledit_buffer_get_softline_bounds(buffer, new_line, new_softline, &start, &end); cb(buffer, new_line, start, KEY_MOTION_LINE); } else { int lineno, x; - ledit_pos_to_x_softline(cur_lline, buffer->cur_index, &x, &lineno); - ledit_x_softline_to_pos(new_lline, x, new_softline, &buffer->cur_index); + ledit_pos_to_x_softline(buffer, buffer->cur_line, buffer->cur_index, &x, &lineno); + ledit_x_softline_to_pos(buffer, new_line, x, new_softline, &buffer->cur_index); if (buffer->cur_line != new_line) ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); buffer->cur_line = new_line; @@ -1746,13 +1745,12 @@ cursor_to_first_non_ws(ledit_buffer *buffer, char *text, int len) { if (num != 0) return err_invalid_key(buffer); int new_index = 0; - ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); if (hard_line_based) { - new_index = ledit_line_next_non_whitespace(ll, 0); + new_index = ledit_line_next_non_whitespace(buffer, buffer->cur_line, 0); } else { int start, end; ledit_buffer_get_pos_softline_bounds(buffer, buffer->cur_line, buffer->cur_index, &start, &end); - new_index = ledit_line_next_non_whitespace(ll, start); + new_index = ledit_line_next_non_whitespace(buffer, buffer->cur_line, start); /* next non-whitespace might be on next softline */ if (new_index >= end) { new_index = ledit_buffer_prev_cursor_pos( diff --git a/keys_command.c b/keys_command.c @@ -305,7 +305,6 @@ edit_submit(ledit_buffer *buffer, char *key_text, int len) { /* FIXME: support visual mode, i.e. change selection to new place? */ void search_next(ledit_buffer *buffer) { - /* FIXME: avoid this when line doesn't change */ ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); enum ledit_search_state ret = ledit_search_next(buffer, &buffer->cur_line, &buffer->cur_index); ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); @@ -315,7 +314,6 @@ search_next(ledit_buffer *buffer) { void search_prev(ledit_buffer *buffer) { - /* FIXME: avoid this when line doesn't change */ ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); enum ledit_search_state ret = ledit_search_prev(buffer, &buffer->cur_line, &buffer->cur_index); ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); diff --git a/memory.c b/memory.c @@ -1,7 +1,9 @@ #include <stdio.h> +#include <stdint.h> #include <stdlib.h> #include <string.h> +/* FIXME: clean up on exit */ static void fatal_err(const char *msg) { fprintf(stderr, "%s", msg); @@ -64,3 +66,24 @@ ledit_strcat(const char *str1, const char *str2) { return ret; } + +/* + * This is from OpenBSD (adapted to exit on error): + * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net> + */ + +/* + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) + +void * +ledit_reallocarray(void *optr, size_t nmemb, size_t size) +{ + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) { + fatal_err("Out of memory.\n"); + } + return realloc(optr, size * nmemb); +} diff --git a/memory.h b/memory.h @@ -4,3 +4,4 @@ void *ledit_malloc(size_t size); void *ledit_calloc(size_t nmemb, size_t size); void *ledit_realloc(void *ptr, size_t size); char *ledit_strcat(const char *str1, const char *str2); +void *ledit_reallocarray(void *optr, size_t nmemb, size_t size);