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 9bdc877acf81ae270c68cacad2e70b9860f0832e
parent 6c98800389dc02e20954a157675ac67fb02b7f8c
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 28 Nov 2021 21:06:33 +0100

Add support for multiple views

Warning: There are still a lot of bugs.

Diffstat:
MIDEAS | 6+++---
MMakefile | 6++++--
AQUIRKS | 9+++++++++
ATODO | 2++
Mbuffer.c | 2267+++++++++----------------------------------------------------------------------
Mbuffer.h | 418+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mcache.c | 94+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mcache.h | 66++++++++++++++++++++++++++++++++++++------------------------------
Acleanup.h | 3+++
Mcommon.h | 9++++-----
Mkeys_basic.c | 1483+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mkeys_basic.h | 3+--
Mkeys_basic_config.h | 160++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mkeys_command.c | 232++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mkeys_command.h | 17+++--------------
Mkeys_command_config.h | 32++++++++++++++++----------------
Mledit.c | 196++++++++++++++++++++++++-------------------------------------------------------
Amacros.h | 10++++++++++
Mmemory.c | 114++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mmemory.h | 32++++++++++++++++++++++++++++++++
Mpango-compat.c | 8++++++--
Mpango-compat.h | 5+++--
Msearch.c | 74+++++++++++++++++++++++++++++++++++++-------------------------------------
Msearch.h | 4++--
Mundo.c | 32++++++++++++++++++++++++--------
Mundo.h | 8++++----
Mutil.h | 2++
Aview.c | 2096+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aview.h | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mwindow.c | 179++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mwindow.h | 37++++++++++++++++++++++++++++++-------
31 files changed, 4305 insertions(+), 3457 deletions(-)

diff --git a/IDEAS b/IDEAS @@ -1,5 +1,5 @@ -* Important: use less memory (maybe don't keep all pango layouts around - all the time) -* allow editing same file in multiple places at same time (like in acme) +* draw cursor as outline rectangle when window not active +* implement '!' command by just opening a separate terminal +* basic macros * add different (more basic) text backend * https://drewdevault.com/2021/06/27/You-cant-capture-the-nuance.html diff --git a/Makefile b/Makefile @@ -11,6 +11,7 @@ MAN1 = ${BIN:=.1} OBJ = \ buffer.o \ + view.o \ cache.o \ keys.o \ keys_basic.o \ @@ -26,8 +27,8 @@ OBJ = \ pango-compat.o HDR = \ - action.h \ buffer.h \ + view.h \ cache.h \ common.h \ keys.h \ @@ -40,6 +41,7 @@ HDR = \ undo.h \ util.h \ window.h \ + cleanup.h \ pango-compat.h CFLAGS_LEDIT = -g -Wall -Wextra -D_POSIX_C_SOURCE=200809L `pkg-config --cflags x11 xkbfile pangoxft xext` @@ -47,7 +49,7 @@ LDFLAGS_LEDIT = ${LDFLAGS} `pkg-config --libs x11 xkbfile pangoxft xext` -lm all: ${BIN} -ledit.o : config.h +ledit.o window.o : config.h theme.o : theme_config.h keys_basic.o : keys_basic_config.h keys_command.o : keys_command_config.h diff --git a/QUIRKS b/QUIRKS @@ -0,0 +1,9 @@ +* Undo with multiple views: + Since a new mode group is started each time insert is entered, when text + is typed in one view in insert, then in another view, and then again in + the first one, the last two inserts will be undone in one go since both + views were in insert already. I'm not sure how to make this more logical, + though. + Maybe it could be "improved" by also saving view in undo stack, but that + would cause problems because views can be added and removed, and it would + maybe not even be more logical. diff --git a/TODO b/TODO @@ -0,0 +1,2 @@ +* Load file in background so text is already shown while still + loading the rest of a big file. diff --git a/buffer.c b/buffer.c @@ -1,12 +1,14 @@ /* FIXME: shrink buffers when text length less than a fourth of the size */ -/* FIXME: also cache PangoLayouts since keeping them around isn't really of much use? */ /* FIXME: handle all undo within buffer to keep it consistent */ +/* FIXME: maybe use separate unicode grapheme library so all functions + that need grapheme boundaries can be included here instead of in the views */ #include <stdio.h> #include <errno.h> #include <string.h> #include <assert.h> #include <limits.h> +#include <stdlib.h> #include <X11/Xlib.h> #include <X11/Xutil.h> @@ -34,52 +36,45 @@ * normalized. This is a bit ugly, but oh well. */ -static PangoAttrList *basic_attrs = NULL; +/* + * Note: "line gap buffer" refers to the gap buffer containing + * all lines, not to the gap buffer of the text in one 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 swap(int *a, int *b); -static void copy_selection_to_x_primary(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2); +/* + * Move the gap of a line so it is at byte position 'index' + */ +static void move_text_gap(ledit_line *line, size_t index); /* - * Assign a cache index to line and set text and highlight of the pango layout. + * Move the gap of the line gap buffer to 'index'. */ -static void set_pango_text_and_highlight(ledit_buffer *buffer, int line); +static void move_line_gap(ledit_buffer *buffer, size_t index); /* - * Get the pango layout for line. - * This first assigns a cache index (by calling set_pango_text_and_highlight). + * Resize the line so it can hold at least 'min_size' bytes and move the gap + * to byte position 'index'. */ -static PangoLayout *get_pango_layout(ledit_buffer *buffer, int line); +static void resize_and_move_text_gap(ledit_line *line, size_t min_size, size_t index); /* - * Get an attribute list for a text highlight between the given range. + * Resize the line gap buffer so it can hold at least 'min_size' lines and + * move the gap to line at position 'index'. */ -static PangoAttrList *get_pango_attributes(ledit_buffer *buffer, int start_byte, int end_byte); +static void resize_and_move_line_gap(ledit_buffer *buffer, size_t min_size, size_t index); /* - * 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. + * Similar to strchr, but the length of the text is given separately */ -static void set_line_layout_attrs(ledit_buffer *buffer, int line, PangoLayout *layout); +static char *strchr_len(char *text, char c, size_t len); -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); +/* + * Initialize a line with default values for its struct members. + */ +static void init_line(ledit_buffer *buffer, ledit_line *line); -void -ledit_buffer_set_mode(ledit_buffer *buffer, enum ledit_mode mode) { - buffer->common->mode = mode; - ledit_window_set_mode(buffer->window, mode); - ledit_change_mode_group(buffer->undo); -} +static void marklist_destroy(ledit_buffer_marklist *marklist); +static ledit_buffer_marklist *marklist_create(void); static void marklist_destroy(ledit_buffer_marklist *marklist) { @@ -91,7 +86,7 @@ marklist_destroy(ledit_buffer_marklist *marklist) { } void -ledit_buffer_insert_mark(ledit_buffer *buffer, char *mark, int len, int line, int byte) { +ledit_buffer_insert_mark(ledit_buffer *buffer, char *mark, size_t len, size_t line, size_t byte) { ledit_buffer_marklist *marklist = buffer->marklist; for (size_t i = 0; i < marklist->len; i++) { if (!strncmp(mark, marklist->marks[i].text, len)) { @@ -123,51 +118,89 @@ marklist_create(void) { } ledit_buffer * -ledit_buffer_create(ledit_common *common, ledit_theme *theme, ledit_window *window) { - if (basic_attrs == NULL) { - basic_attrs = pango_attr_list_new(); - #if PANGO_VERSION_CHECK(1, 44, 0) - PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE); - pango_attr_list_insert(basic_attrs, no_hyphens); - #endif - } - +ledit_buffer_create(ledit_common *common) { ledit_buffer *buffer = ledit_malloc(sizeof(ledit_buffer)); - buffer->cache = cache_create(common->dpy); + buffer->common = 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); - buffer->common = common; - buffer->window = window; - buffer->lines = NULL; buffer->filename = NULL; + buffer->lines = NULL; buffer->lines_num = 0; buffer->lines_cap = 0; - buffer->selecting = 0; - buffer->cur_line = 0; - buffer->cur_index = 0; - /* FIXME: trailing currently not used */ - buffer->trailing = 0; - buffer->trailing_bytes = 0; - buffer->end_of_soft_line = 0; - buffer->total_height = 0; - 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->lines_gap = 0; + buffer->views = NULL; + buffer->views_num = 0; + buffer->hard_line_based = 1; + + /* add one empty line to buffer */ + resize_and_move_line_gap(buffer, 1, 0); + buffer->lines_num++; + buffer->lines_gap++; + ledit_line *ll = ledit_buffer_get_line(buffer, 0); + init_line(buffer, ll); return buffer; } +static void +set_view_hard_line_text(ledit_buffer *buffer, ledit_view *view) { + char *text = buffer->hard_line_based ? "|HL" : "|SL"; + ledit_window_set_mode_extra_text(view->window, text); +} + +void +ledit_buffer_set_hard_line_based(ledit_buffer *buffer, int hl) { + buffer->hard_line_based = hl; + for (size_t i = 0; i < buffer->views_num; i++) { + set_view_hard_line_text(buffer, buffer->views[i]); + } +} + +void +ledit_buffer_add_view(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size_t line, size_t pos) { + size_t new_num = buffer->views_num + 1; + if (new_num <= buffer->views_num) + err_overflow(); + buffer->views = ledit_reallocarray(buffer->views, new_num, sizeof(ledit_view *)); + buffer->views[buffer->views_num] = view_create(buffer, theme, mode, line, pos); + set_view_hard_line_text(buffer, buffer->views[buffer->views_num]); + buffer->views_num = new_num; +} + +/* FIXME: error checking */ +void +ledit_buffer_remove_view(ledit_buffer *buffer, ledit_view *view) { + size_t i = 0; + int found = 0; + for (; i < buffer->views_num; i++) { + if (buffer->views[i] == view) { + found = 1; + break; + } + } + if (found) { + view_destroy(buffer->views[i]); + memmove( + buffer->views + i, + buffer->views + i + 1, + (buffer->views_num - i - 1) * sizeof(ledit_view *) + ); + ledit_reallocarray(buffer->views, --buffer->views_num, sizeof(ledit_view *)); + } +} + +void +ledit_buffer_recalc_all_views_from_line(ledit_buffer *buffer, size_t line) { + for (size_t i = 0; i < buffer->views_num; i++) { + view_recalc_from_line(buffer->views[i], line); + } +} + /* FIXME: don't generate extra blank line at end! */ /* WARNING: errstr must be copied as soon as possible! */ int -ledit_buffer_load_file(ledit_buffer *buffer, char *filename, int line, char **errstr) { +ledit_buffer_load_file(ledit_buffer *buffer, char *filename, size_t line, char **errstr) { long len; int off = 0; ledit_line *ll; @@ -224,7 +257,7 @@ ledit_buffer_write_to_file(ledit_buffer *buffer, char *filename, char **errstr) file = fopen(filename, "w"); if (!file) goto error; clearerr(file); - for (int i = 0; i < buffer->lines_num; i++) { + for (size_t i = 0; i < buffer->lines_num; i++) { ll = ledit_buffer_get_line(buffer, i); ledit_buffer_normalize_line(ll); if (fprintf(file, "%s\n", ll->text) < 0) goto errorclose; @@ -245,11 +278,10 @@ errorclose: void ledit_buffer_destroy(ledit_buffer *buffer) { ledit_line *l; - for (int i = 0; i < buffer->lines_num; i++) { + for (size_t i = 0; i < buffer->lines_num; i++) { l = ledit_buffer_get_line(buffer, i); free(l->text); } - cache_destroy(buffer->cache); ledit_undo_stack_destroy(buffer->undo); free(buffer->lines); if (buffer->filename) @@ -259,12 +291,6 @@ ledit_buffer_destroy(ledit_buffer *buffer) { } void -ledit_buffer_cleanup(void) { - if (basic_attrs) - pango_attr_list_unref(basic_attrs); -} - -void ledit_buffer_normalize_line(ledit_line *line) { if (line->gap < line->len) { memmove( @@ -275,122 +301,17 @@ ledit_buffer_normalize_line(ledit_line *line) { line->gap = line->len; } /* FIXME: check if enough space, just to be sure */ + assert(line->len < line->cap); line->text[line->len] = '\0'; } -#if 0 -static void -err_text_dirty(ledit_buffer *buffer, int line) { - fprintf( - stderr, - "WARNING: Line had text_dirty or h_dirty attribute " - "set when rendering. Fix your code!\n" - ); - ledit_buffer_recalc_from_line(buffer, line); -} -#endif - -#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); - /* FIXME: Are these guaranteed to be range 0-65535? */ - 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 - pango_layout_set_attributes(l->layout, list); - pango_attr_list_unref(list); - l->dirty = 1; -} -#endif - -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(layout, basic_attrs); - } - 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 *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 then insert it in one go instead of having this complex logic */ void ledit_buffer_insert_text_from_line( ledit_buffer *buffer, - int dst_line, int dst_index, - int src_line, int src_index, int src_len, + size_t dst_line, size_t dst_index, + size_t src_line, size_t src_index, size_t src_len, txtbuf *text_ret) { ledit_buffer_insert_text_from_line_base( buffer, dst_line, dst_index, src_line, src_index, src_len, text_ret @@ -398,19 +319,17 @@ ledit_buffer_insert_text_from_line( ledit_buffer_recalc_line(buffer, dst_line); } -/* FIXME: check if there can be bugs when a newline is inserted in some way other +/* FIXME: check if there can be bugs when a newline is inserted in some way other than pasting or pressing enter */ void ledit_buffer_insert_text_from_line_base( ledit_buffer *buffer, - int dst_line, int dst_index, - int src_line, int src_index, int src_len, + size_t dst_line, size_t dst_index, + size_t src_line, size_t src_index, size_t src_len, txtbuf *text_ret) { assert(dst_line != src_line); ledit_line *ll = ledit_buffer_get_line(buffer, src_line); - if (src_len == -1) - src_len = ll->len - src_index; if (text_ret != NULL) { txtbuf_grow(text_ret, src_len); text_ret->len = src_len; @@ -467,91 +386,66 @@ ledit_buffer_insert_text_from_line_base( } } + for (size_t i = 0; i < buffer->views_num; i++) { + view_notify_insert_text(buffer->views[i], dst_line, dst_index, src_len); + } } static void -move_text_gap(ledit_line *line, int index) { - if (index > line->gap) { - /* move piece between end of original gap and - index to beginning of original gap */ - memmove( - line->text + line->gap, - line->text + line->gap + line->cap - line->len, - index - line->gap - ); - } else if (index < line->gap) { - /* move piece between index and original gap to - end of original gap */ - memmove( - line->text + index + line->cap - line->len, - line->text + index, - line->gap - index - ); - } - line->gap = index; +move_text_gap(ledit_line *line, size_t index) { + /* yes, I know sizeof(char) == 1 anyways */ + move_gap( + line->text, sizeof(char), index, + line->gap, line->cap, line->len, + &line->gap + ); +} + +static void +move_line_gap(ledit_buffer *buffer, size_t index) { + move_gap( + buffer->lines, sizeof(ledit_line), index, + buffer->lines_gap, buffer->lines_cap, buffer->lines_num, + &buffer->lines_gap + ); } -/* This is almost certainly premature optimization and maybe - not optimization at all. */ /* FIXME: add "final" versions of the functions that include the normalization, i.e. if they have to move the gap anyways, they just move it to the end */ + static void -resize_and_move_text_gap(ledit_line *line, int min_size, int index) { - int gap_size = line->cap - line->len; - /* FIXME: read up on what the best values are here */ - line->cap = line->cap * 2 > min_size ? line->cap * 2 : min_size; - line->text = ledit_realloc(line->text, line->cap); - if (index > line->gap) { - /* move piece between end of original gap and index to - beginning of original gap */ - memmove( - line->text + line->gap, - line->text + line->gap + gap_size, - index - line->gap - ); - /* move piece after index to end of buffer */ - memmove( - line->text + line->cap - (line->len - index), - line->text + index + gap_size, - line->len - index - ); - } else if (index < line->gap) { - /* move piece after original gap to end of buffer */ - memmove( - line->text + line->cap - (line->len - line->gap), - line->text + line->gap + gap_size, - line->len - line->gap - ); - /* move piece between index and original gap to end */ - memmove( - line->text + line->cap - line->len + index, - line->text + index, - line->gap - index - ); - } else { - /* move piece after original gap to end of buffer */ - memmove( - line->text + line->cap - (line->len - line->gap), - line->text + line->gap + gap_size, - line->len - line->gap - ); - } - line->gap = index; +resize_and_move_text_gap(ledit_line *line, size_t min_size, size_t index) { + /* yes, I know sizeof(char) == 1 anyways */ + line->text = resize_and_move_gap( + line->text, sizeof(char), + line->gap, line->cap, line->len, + min_size, index, + &line->gap, &line->cap + ); +} + +static void +resize_and_move_line_gap(ledit_buffer *buffer, size_t min_size, size_t index) { + buffer->lines = resize_and_move_gap( + buffer->lines, sizeof(ledit_line), + buffer->lines_gap, buffer->lines_cap, buffer->lines_num, + min_size, index, + &buffer->lines_gap, &buffer->lines_cap + ); } void -ledit_buffer_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len) { +ledit_buffer_insert_text(ledit_buffer *buffer, size_t line_index, size_t index, char *text, size_t len) { ledit_buffer_insert_text_base(buffer, line_index, index, text, len); ledit_buffer_recalc_line(buffer, line_index); } void -ledit_buffer_insert_text_base(ledit_buffer *buffer, int line_index, int index, char *text, int len) { - ledit_line *line = &buffer->lines[line_index]; - if (len == -1) - len = strlen(text); +ledit_buffer_insert_text_base(ledit_buffer *buffer, size_t line_index, size_t index, char *text, size_t len) { + ledit_line *line = ledit_buffer_get_line(buffer, line_index); /* \0 is not included in line->len */ + /* FIXME: this if should be redundant now because resize_and_move... includes a check */ if (line->len + len + 1 > line->cap || line->text == NULL) resize_and_move_text_gap(line, line->len + len + 1, index); else @@ -560,24 +454,15 @@ ledit_buffer_insert_text_base(ledit_buffer *buffer, int line_index, int index, c memcpy(line->text + index, text, len); line->gap += len; line->len += len; - line->dirty = 1; - line->text_dirty = 1; - line->h_dirty = 1; -} - -/* FIXME: implement this - if it is known that this is the last insertion, - move gap to end immediately if the gap needs to be enlarged anyways to - avoid another copy before rendering */ -/* -void -ledit_buffer_insert_text_final(ledit_buffer *buffer, int line_index, int index, char *text, int len) { + for (size_t i = 0; i < buffer->views_num; i++) { + view_notify_insert_text(buffer->views[i], line_index, index, len); + } } -*/ /* FIXME: this isn't optimized like the standard version, but whatever */ static char * -strchr_len(char *text, char c, long len) { - for (long i = 0; i < len; i++) { +strchr_len(char *text, char c, size_t len) { + for (size_t i = 0; i < len; i++) { if (text[i] == c) return text + i; } @@ -585,23 +470,16 @@ strchr_len(char *text, char c, long len) { } /* FIXME: make these functions that call recalc* also be final as described above */ -/* FIXME: Sort out integer types. - -> len is long here mainly because that's what ftell(3) returns and it sort of - makes sense since a file can be very long (although ledit probably won't work - with such long files anyways). The individual lines have to use int anyways - because of pango. - Maybe len isn't needed anyways? It might be possible to enforce that text - just always has to be null-terminated. */ void ledit_buffer_insert_text_with_newlines( ledit_buffer *buffer, - int line_index, int index, - char *text, long len, - int *end_line_ret, int *end_char_ret) { - int end; + size_t line_index, size_t index, + char *text, size_t len, + size_t *end_line_ret, size_t *end_byte_ret) { + size_t end; ledit_buffer_insert_text_with_newlines_base( buffer, line_index, index, text, len, - &end, end_char_ret + &end, end_byte_ret ); if (end_line_ret) *end_line_ret = end; @@ -611,23 +489,22 @@ ledit_buffer_insert_text_with_newlines( ledit_buffer_recalc_from_line(buffer, line_index); } -/* FIXME: Check for integer overflow when casting to int */ +/* FIXME: also look for \r */ void ledit_buffer_insert_text_with_newlines_base( ledit_buffer *buffer, - int line_index, int index, - char *text, long len, - int *end_line_ret, int *end_char_ret) { - if (len == -1) - len = strlen(text); - long rem_len = len; + size_t line_index, size_t index, + char *text, size_t len, + size_t *end_line_ret, size_t *end_byte_ret) { + size_t rem_len = len; char *cur, *last = text; - int cur_line = line_index; - int cur_index = index; + size_t cur_line = line_index; + size_t cur_index = index; /* FIXME: strchr_len isn't really needed when the lines are normalized anyways */ while ((cur = strchr_len(last, '\n', rem_len)) != NULL) { - /* FIXME: inefficient because there's no gap buffer yet */ - ledit_buffer_append_line_base(buffer, cur_line, cur_index); + /* FIXME: this is probably inefficient, but I don't have time to + think about it right now */ + ledit_buffer_append_line_base(buffer, cur_line, cur_index, 1); ledit_buffer_insert_text_base(buffer, cur_line, cur_index, last, cur - last); cur_index = 0; cur_line++; @@ -638,256 +515,122 @@ ledit_buffer_insert_text_with_newlines_base( ledit_buffer_insert_text_base(buffer, cur_line, cur_index, last, text + len - last); if (end_line_ret) *end_line_ret = cur_line; - if (end_char_ret) - *end_char_ret = cur_index + text + len - last; -} - -/* FIXME: is this even needed? */ -static int -line_visible_callback(void *data, int line) { - return ledit_buffer_line_visible((ledit_buffer*)data, line); -} - -/* FIXME: standardize variable names (line/line_index, etc.) */ -void -ledit_buffer_render_line(ledit_buffer *buffer, int line_index) { - /* FIXME: check for <= 0 on size */ - 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 - ); - } - 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, - ll->w + 10, ll->h + 10, buffer->common->depth - ); - 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 < 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, - new_w, new_h, buffer->common->depth - ); - pix->w = new_w; - pix->h = new_h; - XftDrawChange(pix->draw, pix->pixmap); - } - 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; + if (end_byte_ret) + *end_byte_ret = cur_index + text + len - last; } static void init_line(ledit_buffer *buffer, ledit_line *line) { - int text_w, text_h; line->parent_buffer = buffer; - ledit_window_get_textview_size(buffer->window, &text_w, &text_h); line->gap = 0; line->cap = 2; /* arbitrary */ line->text = ledit_malloc(line->cap); line->text[0] = '\0'; line->len = 0; - 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; - line->w = line->h = 0; - line->cursor_index = -1; - line->softlines = 1; - line->w = text_w; - line->h = 0; - line->y_offset = 0; } void -ledit_buffer_append_line(ledit_buffer *buffer, int line_index, int text_index) { - ledit_buffer_append_line_base(buffer, line_index, text_index); +ledit_buffer_append_line(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text) { + ledit_buffer_append_line_base(buffer, line_index, text_index, break_text); ledit_buffer_recalc_from_line(buffer, line_index); } /* FIXME: error checking (index out of bounds, etc.) */ void -ledit_buffer_append_line_base(ledit_buffer *buffer, int line_index, int text_index) { - if (buffer->lines_num >= buffer->lines_cap) { - buffer->lines_cap *= 2; - if (buffer->lines_cap == 0) - buffer->lines_cap = 2; - buffer->lines = ledit_realloc( - buffer->lines, buffer->lines_cap * sizeof(ledit_line) - ); - } - memmove( - buffer->lines + line_index + 2, - buffer->lines + line_index + 1, - (buffer->lines_num - (line_index + 1)) * sizeof(ledit_line) - ); +ledit_buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text) { + size_t new_len = buffer->lines_num + 1; + if (new_len <= buffer->lines_num) + err_overflow(); + size_t insert_index = line_index + 1; + if (insert_index <= line_index) + err_overflow(); + resize_and_move_line_gap(buffer, new_len, insert_index); buffer->lines_num++; + buffer->lines_gap++; ledit_line *new_l = ledit_buffer_get_line(buffer, line_index + 1); init_line(buffer, new_l); - if (text_index != -1) { + for (size_t i = 0; i < buffer->views_num; i++) { + view_notify_append_line(buffer->views[i], line_index); + } + if (break_text) { ledit_line *l = ledit_buffer_get_line(buffer, line_index); ledit_buffer_insert_text_from_line_base( buffer, line_index + 1, 0, - line_index, text_index, -1, NULL + line_index, text_index, l->len - text_index, NULL ); - delete_line_section_base( + ledit_buffer_delete_line_section_base( buffer, line_index, text_index, l->len - text_index ); } } -/* this is very weird and ugly with the recalc */ +/* FIXME: set offset to 0 when recalculating first line? */ void -ledit_buffer_delete_line_entries(ledit_buffer *buffer, int index1, int index2) { +ledit_buffer_delete_line_entries(ledit_buffer *buffer, size_t index1, size_t index2) { ledit_buffer_delete_line_entries_base(buffer, index1, 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_buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2) { ledit_line *l; - /* FIXME: make sure this is always true */ - /* FIXME: Ummm... what is that assert supposed to do? */ + assert (index2 >= index1); + /* it isn't allowed to delete all lines */ 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++) { + for (size_t i = index1; i <= index2; i++) { l = ledit_buffer_get_line(buffer, i); free(l->text); } - /* FIXME: gap buffer */ - if (index2 < buffer->lines_num - 1) { - memmove( - buffer->lines + index1, buffer->lines + index2 + 1, - (buffer->lines_num - index2 - 1) * sizeof(ledit_line) - ); - } + move_line_gap(buffer, index1); buffer->lines_num -= index2 - index1 + 1; - /* force a recalc of the lines */ - if (index1 == 0) { - l = ledit_buffer_get_line(buffer, 0); - l->y_offset = 0; - l->h_dirty = 1; - } else { - l = ledit_buffer_get_line(buffer, index1 - 1); - l->h_dirty = 1; + for (size_t i = 0; i < buffer->views_num; i++) { + view_notify_delete_lines(buffer->views[i], index1, index2); } } void -ledit_buffer_delete_line_entry(ledit_buffer *buffer, int index) { +ledit_buffer_delete_line_entry(ledit_buffer *buffer, size_t index) { ledit_buffer_delete_line_entries(buffer, index, index); } void -ledit_buffer_delete_line_entry_base(ledit_buffer *buffer, int index) { +ledit_buffer_delete_line_entry_base(ledit_buffer *buffer, size_t index) { ledit_buffer_delete_line_entries_base(buffer, index, index); } -/* FIXME: use some sort of gap buffer (that would make this function - slightly more useful...) */ ledit_line * -ledit_buffer_get_line(ledit_buffer *buffer, int index) { - assert(index >= 0 && index < buffer->lines_num); - return &buffer->lines[index]; +ledit_buffer_get_line(ledit_buffer *buffer, size_t index) { + assert(index < buffer->lines_num); + return index < buffer->lines_gap ? + &buffer->lines[index] : + &buffer->lines[index + buffer->lines_cap - buffer->lines_num]; } -/* set text of pango layout if dirty and recalculate height of line - * - if height hasn't changed, nothing further is done - * - if height has changed, offset of all following lines is changed */ void -ledit_buffer_recalc_line(ledit_buffer *buffer, int line) { - ledit_line *l = ledit_buffer_get_line(buffer, line); - 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_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; - off += l->h; - } - buffer->total_height = off; +ledit_buffer_recalc_line(ledit_buffer *buffer, size_t line) { + for (size_t i = 0; i < buffer->views_num; i++) { + view_recalc_line(buffer->views[i], line); } } -/* set text of pango layout and recalculate height - * and offset for all lines starting at 'line' */ void -ledit_buffer_recalc_from_line(ledit_buffer *buffer, int line) { - 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) - set_pango_text_and_highlight(buffer, i); - l->h_dirty = 0; - l->y_offset = off; - off += l->h; +ledit_buffer_recalc_from_line(ledit_buffer *buffer, size_t line) { + for (size_t i = 0; i < buffer->views_num; i++) { + view_recalc_from_line(buffer->views[i], line); } - buffer->total_height = off; } -/* short for 'ledit_buffer_recalc_from_line' starting at line 0 */ void ledit_buffer_recalc_all_lines(ledit_buffer *buffer) { - /* force first line to offset 0, just in case */ - ledit_line *l = ledit_buffer_get_line(buffer, 0); - l->y_offset = 0; - ledit_buffer_recalc_from_line(buffer, 0); -} - -int -ledit_buffer_line_visible(ledit_buffer *buffer, int index) { - int text_w, text_h; - ledit_window_get_textview_size(buffer->window, &text_w, &text_h); - ledit_line *l = ledit_buffer_get_line(buffer, index); - return l->y_offset < buffer->display_offset + text_h && - l->y_offset + l->h > buffer->display_offset; + for (size_t i = 0; i < buffer->views_num; i++) { + view_recalc_all_lines(buffer->views[i]); + } } -/* get needed length of text range, including newlines - * - NUL is not included - * - if the last range ends at the end of a line, the newline is *not* included - * - the range must be sorted already */ size_t -ledit_buffer_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2) { +ledit_buffer_textlen(ledit_buffer *buffer, size_t line1, size_t byte1, size_t line2, size_t byte2) { assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); size_t len = 0; ledit_line *ll = ledit_buffer_get_line(buffer, line1); @@ -896,7 +639,7 @@ ledit_buffer_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int } else { /* + 1 for newline */ len = ll->len - byte1 + byte2 + 1; - for (int i = line1 + 1; i < line2; i++) { + for (size_t i = line1 + 1; i < line2; i++) { ll = ledit_buffer_get_line(buffer, i); len += ll->len + 1; } @@ -910,10 +653,6 @@ ledit_buffer_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int only done when it is re-rendered (and thus normalized because of pango's requirements). If a more efficient rendering backend is added, it would be good to optimize this, though. */ -/* copy text range into given buffer - * - dst is null-terminated - * - dst must be large enough to contain the text and NUL - * - the range must be sorted already */ void ledit_buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2) { assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); @@ -944,17 +683,12 @@ ledit_buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, in } } -/* copy text range into given buffer and resize it if necessary - * - *dst is reallocated and *alloc adjusted if the text doesn't fit - * - *dst is null-terminated - * - the range must be sorted already - * - returns the length of the text, not including the NUL */ void ledit_buffer_copy_text_to_txtbuf( ledit_buffer *buffer, txtbuf *buf, - int line1, int byte1, - int line2, int byte2) { + size_t line1, size_t byte1, + size_t line2, size_t byte2) { assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); size_t len = ledit_buffer_textlen(buffer, line1, byte1, line2, byte2); txtbuf_grow(buf, len + 1); @@ -965,11 +699,11 @@ ledit_buffer_copy_text_to_txtbuf( /* get char with logical index i from line */ #define LINE_CHAR(line, i) ((i) < (line)->gap ? (line)->text[i] : (line)->text[i + (line)->cap - (line)->len]) -int -ledit_line_prev_utf8(ledit_line *line, int index) { +size_t +ledit_line_prev_utf8(ledit_line *line, size_t index) { if (index <= 0) return 0; - int i = index - 1; + size_t i = index - 1; /* find valid utf8 char - this probably needs to be improved */ /* FIXME: don't go off end or beginning */ while (i > 0 && ((LINE_CHAR(line, i) & 0xC0) == 0x80)) @@ -977,11 +711,11 @@ ledit_line_prev_utf8(ledit_line *line, int index) { return i; } -int -ledit_line_next_utf8(ledit_line *line, int index) { +size_t +ledit_line_next_utf8(ledit_line *line, size_t index) { if (index >= line->len) return line->len; - int i = index + 1; + size_t i = index + 1; while (i < line->len && ((LINE_CHAR(line, i) & 0xC0) == 0x80)) i++; return i; @@ -990,11 +724,11 @@ ledit_line_next_utf8(ledit_line *line, int index) { /* Warning: this is very inefficient! */ /* FIXME: at least attempt to be more efficient by starting from the beginning or end based on approximately where in the line the byte is */ -static int -line_byte_to_char(ledit_line *line, int byte) { - int c = 0; - int i = 0; - int b = byte > line->len ? line->len : byte; /* maybe not necessary */ +size_t +line_byte_to_char(ledit_line *line, size_t byte) { + size_t c = 0; + size_t i = 0; + size_t b = byte > line->len ? line->len : byte; /* maybe not necessary */ while (i < b) { c++; i = ledit_line_next_utf8(line, i); @@ -1002,466 +736,8 @@ line_byte_to_char(ledit_line *line, int byte) { return c; } -int -ledit_buffer_next_cursor_pos(ledit_buffer *buffer, int line, int byte, int num) { - int nattrs; - 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(layout, &nattrs); - for (int i = 0; i < num; i++) { - cur_byte = ledit_line_next_utf8(ll, byte); - for (c++; c < nattrs; c++) { - if (attrs[c].is_cursor_position) - break; - cur_byte = ledit_line_next_utf8(ll, cur_byte); - } - if (cur_byte >= ll->len) - break; - } - return cur_byte <= ll->len ? cur_byte : ll->len; -} - -int -ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int line, int byte, int num) { - int nattrs; - 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(layout, &nattrs); - for (int i = 0; i < num; i++) { - cur_byte = ledit_line_prev_utf8(ll, cur_byte); - for (c--; c >= 0; c--) { - if (attrs[c].is_cursor_position) - break; - cur_byte = ledit_line_prev_utf8(ll, cur_byte); - } - if (cur_byte <= 0) - break; - } - return cur_byte > 0 ? cur_byte : 0; -} - -static int -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(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(layout, &nattrs); - for (int i = wrapped_line ? c : c + 1; i < nattrs; i++) { - if (attrs[i].is_word_start) { - if (char_ret) - *char_ret = i; - if (real_byte_ret) - *real_byte_ret = cur_byte; - return cur_byte; - } - cur_byte = ledit_line_next_utf8(ll, cur_byte); - } - return -1; -} - -static int -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(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(layout, &nattrs); - if (c > nattrs) - return -1; - for (int i = c - 1; i >= 0; i--) { - if (attrs[i].is_word_start) { - if (char_ret) - *char_ret = i; - return cur_byte; - } - cur_byte = ledit_line_prev_utf8(ll, cur_byte); - } - return -1; -} - -static int -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(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(layout, &nattrs); - int next_cursorb = byte; - int next_cursorc = c; - int found_word = 0; - for (int i = c - 1; i >= 0; i--) { - if (!found_word && !attrs[i].is_white) { - found_word = 1; - } else if (found_word && attrs[i].is_white) { - if (char_ret) - *char_ret = next_cursorc; - return next_cursorb; - } - if (found_word && c == 0) { - if (char_ret) - *char_ret = 0; - return 0; - } - if (attrs[i].is_cursor_position) { - next_cursorc = i; - next_cursorb = cur_byte; - } - cur_byte = ledit_line_prev_utf8(ll, cur_byte); - } - return -1; -} - -int -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(ll, byte); - PangoLayout *layout = get_pango_layout(buffer, line); - const PangoLogAttr *attrs = - pango_layout_get_log_attrs_readonly(layout, &nattrs); - int last_cursorb, last_cursorc; - if (wrapped_line) { - last_cursorb = byte; - last_cursorc = c; - } else { - last_cursorb = -1; - last_cursorc = -1; - } - int found_word = 0; - int cur_byte = byte; - for (int i = c; i < nattrs; i++) { - if (last_cursorb != -1 && !found_word && !attrs[i].is_white) { - found_word = 1; - } else if (found_word && attrs[i].is_white) { - if (char_ret) - *char_ret = last_cursorc; - if (real_byte_ret) - *real_byte_ret = cur_byte; - return last_cursorb; - } - if (attrs[i].is_cursor_position) { - last_cursorc = i; - last_cursorb = cur_byte; - } - cur_byte = ledit_line_next_utf8(ll, cur_byte); - } - return -1; -} - -static int -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(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(layout, &nattrs); - int last_cursorb, last_cursorc; - if (wrapped_line) { - last_cursorb = byte; - last_cursorc = c; - } else { - last_cursorb = -1; - last_cursorc = -1; - } - for (int i = c + 1; i < nattrs; i++) { - if (last_cursorb != -1 && attrs[i].is_word_end) { - if (char_ret) - *char_ret = last_cursorc; - if (real_byte_ret) - *real_byte_ret = cur_byte; - return last_cursorb; - } - if (attrs[i].is_cursor_position) { - last_cursorc = i; - last_cursorb = cur_byte; - } - cur_byte = ledit_line_next_utf8(ll, cur_byte); - } - return -1; -} - -static int -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(ll, byte); - int cur_byte = byte; - PangoLayout *layout = get_pango_layout(buffer, line); - const PangoLogAttr *attrs = - 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) { - found_ws = 1; - } else if (found_ws && !attrs[i].is_white) { - if (char_ret) - *char_ret = i; - if (real_byte_ret) - *real_byte_ret = cur_byte; - return cur_byte; - } - cur_byte = ledit_line_next_utf8(ll, cur_byte); - } - return -1; -} - -int -ledit_line_next_non_whitespace(ledit_buffer *buffer, int line, int byte) { - int c, nattrs; - 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(layout, &nattrs); - for (; c < nattrs; c++) { - if (!attrs[c].is_white) - return cur_byte; - cur_byte = ledit_line_next_utf8(ll, cur_byte); - } - return ll->len; -} - -/* FIXME: document that word and bigword are a bit weird because word uses unicode semantics */ - -#define GEN_NEXT_WORD(name, func) \ -void \ -ledit_buffer_next_##name( \ - ledit_buffer *buffer, \ - int line, int byte, int num_repeat, \ - int *line_ret, int *byte_ret, int *real_byte_ret) { \ - int cur_line = line; \ - int cur_byte = byte; \ - int cur_char = -1; \ - int real_byte = -1; \ - int wrapped_line; \ - for (int i = 0; i < num_repeat; i++) { \ - wrapped_line = 0; \ - 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++; \ - cur_byte = 0; \ - wrapped_line = 1; \ - } \ - if (cur_byte == -1 && cur_line == buffer->lines_num - 1) \ - break; \ - } \ - 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 { \ - *line_ret = cur_line; \ - *byte_ret = cur_byte; \ - *real_byte_ret = real_byte; \ - } \ -} - -#define GEN_PREV_WORD(name, func) \ -void \ -ledit_buffer_prev_##name( \ - ledit_buffer *buffer, \ - int line, int byte, int num_repeat, \ - int *line_ret, int *byte_ret, int *real_byte_ret) { \ - int cur_line = line; \ - int cur_byte = byte; \ - 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(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; \ - } \ - if (cur_byte == -1 && cur_line == 0) \ - break; \ - } \ - if (cur_byte == -1) { \ - *line_ret = 0; \ - *byte_ret = 0; \ - *real_byte_ret = 0; \ - } else { \ - *line_ret = cur_line; \ - *byte_ret = cur_byte; \ - *real_byte_ret = cur_byte; \ - } \ -} - -GEN_NEXT_WORD(word, line_next_word) -GEN_NEXT_WORD(word_end, line_next_word_end) -GEN_NEXT_WORD(bigword, line_next_bigword) -GEN_NEXT_WORD(bigword_end, line_next_bigword_end) -GEN_PREV_WORD(word, line_prev_word) -GEN_PREV_WORD(bigword, line_prev_bigword) - -void -ledit_buffer_get_pos_softline_bounds( - ledit_buffer *buffer, int line, int pos, - int *start_byte_ret, int *end_byte_ret) { - 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(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; -} - -void -ledit_buffer_get_softline_bounds( - ledit_buffer *buffer, int line, int softline, - int *start_byte_ret, int *end_byte_ret) { - assert(line >= 0 && line < buffer->lines_num); - ledit_line *ll = ledit_buffer_get_line(buffer, line); - 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; -} - -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); - if (ll->text_dirty) - set_pango_text_and_highlight(buffer, line); - return ll->softlines; -} - -int -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(layout, pos, 0, &sli, &x); - return sli; -} - -void -ledit_buffer_get_cursor_pixel_pos(ledit_buffer *buffer, int line, int pos, int *x_ret, int *y_ret, int *h_ret) { - 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(layout, 0, &strong, &weak); - *x_ret = strong.x / PANGO_SCALE; - *y_ret = strong.y / PANGO_SCALE; - *h_ret = strong.height / PANGO_SCALE; -} - -/* prev_index_ret is used instead of just calling get_legal_normal_pos - because weird things happen otherwise - -> in certain cases, this is still weird because prev_index_ret sometimes - is not at the end of the line, but this is the best I could come up - with for now */ -int -ledit_buffer_move_cursor_visually(ledit_buffer *buffer, int line, int pos, int movement, int *prev_index_ret) { - /* 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; - if (movement < 0) { - dir = -1; - num = -movement; - } - while (num > 0) { - tmp_index = new_index; - pango_layout_move_cursor_visually( - layout, TRUE, - new_index, trailing, dir, - &new_index, &trailing - ); - /* for some reason, this is necessary */ - if (new_index < 0) - new_index = 0; - else if (new_index > cur_line->len) - new_index = cur_line->len; - num--; - if (tmp_index != new_index) - last_index = tmp_index; - } - /* FIXME: Allow cursor to be at end of soft line */ - /* we don't currently support a difference between the cursor being at - the end of a soft line and the beginning of the next line */ - /* FIXME: spaces at end of softlines are weird in normal mode */ - while (trailing > 0) { - trailing--; - new_index = ledit_line_next_utf8(cur_line, new_index); - } - if (new_index < 0) - new_index = 0; - if (prev_index_ret) - *prev_index_ret = last_index; - return new_index; -} - -/* FIXME: implement */ -/* -int -ledit_line_nearest_cursor_pos(ledit_line *line, int byte) { -} - void -ledit_line_word_boundaries(ledit_line *line, int byte, int *start_ret, int *end_ret) { -} -*/ - -/* FIXME: no idea why this exists */ -/* -static void -delete_line_section(ledit_buffer *buffer, int line, int start, int length) { - delete_line_section_base(buffer, line, start, length); - ledit_buffer_recalc_line(buffer, line); -} -*/ - -static void -delete_line_section_base(ledit_buffer *buffer, int line, int start, int length) { +ledit_buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length) { ledit_line *l = ledit_buffer_get_line(buffer, line); if (start <= l->gap && start + length >= l->gap) { l->gap = start; @@ -1480,1092 +756,29 @@ delete_line_section_base(ledit_buffer *buffer, int line, int start, int length) ); } l->len -= length; - l->dirty = 1; - l->text_dirty = 1; + for (size_t i = 0; i < buffer->views_num; i++) { + view_notify_delete_text(buffer->views[i], line, start, length); + } } -int -ledit_buffer_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir) { - int new_index = ledit_buffer_delete_unicode_char_base(buffer, line_index, byte_index, dir); +size_t +ledit_buffer_delete_unicode_char(ledit_buffer *buffer, size_t line_index, size_t byte_index, int dir) { + size_t new_index = ledit_buffer_delete_unicode_char_base(buffer, line_index, byte_index, dir); ledit_buffer_recalc_line(buffer, line_index); return new_index; } -int -ledit_buffer_delete_unicode_char_base(ledit_buffer *buffer, int line_index, int byte_index, int dir) { +size_t +ledit_buffer_delete_unicode_char_base(ledit_buffer *buffer, size_t line_index, size_t byte_index, int dir) { ledit_line *l = ledit_buffer_get_line(buffer, line_index); - int new_index = byte_index; + size_t new_index = byte_index; if (dir < 0) { - int i = ledit_line_prev_utf8(l, byte_index); - delete_line_section_base(buffer, line_index, i, byte_index - i); + size_t i = ledit_line_prev_utf8(l, byte_index); + ledit_buffer_delete_line_section_base(buffer, line_index, i, byte_index - i); new_index = i; } else { - int i = ledit_line_next_utf8(l, byte_index); - delete_line_section_base(buffer, line_index, byte_index, i - byte_index); + size_t i = ledit_line_next_utf8(l, byte_index); + ledit_buffer_delete_line_section_base(buffer, line_index, byte_index, i - byte_index); } return new_index; } - -static void -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_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(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 += (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 (ll->parent_buffer->common->mode == NORMAL) { - PangoRectangle 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_buffer *buffer, int line, int x, int softline, int *pos_ret) { - int trailing = 0; - int x_relative = x; - ledit_line *ll = ledit_buffer_get_line(buffer, line); - PangoLayout *layout = get_pango_layout(buffer, line); - PangoLayoutLine *pango_line = - 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 -= (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 (ll->parent_buffer->common->mode == INSERT) { - while (trailing > 0) { - trailing--; - *pos_ret = ledit_line_next_utf8(ll, *pos_ret); - } - } -} - -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 *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(layout, &nattrs); - int cur = nattrs - 2; - ret = ledit_line_prev_utf8(ll, ret); - while (ret > 0 && cur > 0 && !attrs[cur].is_cursor_position) { - cur--; - ret = ledit_line_prev_utf8(ll, ret); - } - } - return ret; -} - -void -ledit_buffer_delete_range( - ledit_buffer *buffer, enum delete_mode delmode, - int line_index1, int byte_index1, - int line_index2, int byte_index2, - int *new_line_ret, int *new_byte_ret, - ledit_range *final_range_ret, txtbuf *text_ret) { - ledit_buffer_delete_range_base( - buffer, delmode, - line_index1, byte_index1, - line_index2, byte_index2, - new_line_ret, new_byte_ret, - final_range_ret, text_ret - ); - /* need to start recalculating one line before in case first - line was deleted and offset is now wrong */ - int min = line_index1 < line_index2 ? line_index1 : line_index2; - ledit_buffer_recalc_from_line(buffer, min > 0 ? min - 1 : min); -} - -/* Note: line_index* and byte_index* don't need to be sorted */ -/* line_index1, byte_index1 are used as the cursor position in order - to determine the new cursor position */ -/* FIXME: use at least somewhat sensible variable names */ -/* FIXME: I once noticed a bug where using 'dG' to delete to the end of - the file caused a line index way larger than buffer->lines_num to be - given, but I couldn't reproduce this bug */ -void -ledit_buffer_delete_range_base( - ledit_buffer *buffer, enum delete_mode delmode, - int line_index1, int byte_index1, - int line_index2, int byte_index2, - int *new_line_ret, int *new_byte_ret, - ledit_range *final_range_ret, txtbuf *text_ret) { - /* FIXME: Oh boy, this is nasty */ - /* range line x, range byte x */ - int rgl1 = 0, rgb1 = 0, rgl2 = 0, rgb2 = 0; - int new_line = 0, new_byte = 0; - assert(line_index1 >= 0); - assert(line_index2 >= 0); - assert(line_index1 < buffer->lines_num); - assert(line_index2 < buffer->lines_num); - /* FIXME: could this be simplified by just calculating the range and then using - the non-line-based version? */ - if (delmode == DELETE_HARDLINE) { - int x, sl_useless; - int l1 = line_index1, l2 = line_index2; - if (line_index1 > line_index2) { - l1 = line_index2; - l2 = line_index1; - } - int dell1 = l1, dell2 = l2; - ledit_line *ll = ledit_buffer_get_line(buffer, line_index1); - 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; - rgl2 = l2 + 1; - rgb2 = 0; - } else if (l1 > 0) { - rgl1 = l1 - 1; - ll = ledit_buffer_get_line(buffer, rgl1); - rgb1 = ll->len; - rgl2 = l2; - ll = ledit_buffer_get_line(buffer, rgl2); - rgb2 = ll->len; - } else if (l2 < buffer->lines_num - 1) { - rgl1 = l1; - rgb1 = 0; - rgl2 = l2 + 1; - rgb2 = 0; - } else { - rgl1 = l1; - rgb1 = 0; - rgl2 = l2; - ll = ledit_buffer_get_line(buffer, rgl2); - rgb2 = ll->len; - } - if (text_ret) { - ledit_buffer_copy_text_to_txtbuf( - buffer, text_ret, - rgl1, rgb1, rgl2, rgb2 - ); - } - /* default is dell1 = l1, dell2 = l2 */ - if (l2 < buffer->lines_num - 1) { - new_line = l1; - ledit_x_softline_to_pos( - buffer, l2 + 1, - x, 0, &new_byte - ); - } else if (l1 > 0) { - new_line = l1 - 1; - ledit_x_softline_to_pos( - buffer, l1 - 1, - x, 0, &new_byte - ); - } else { - dell1 = l1 + 1; - dell2 = l2; - new_line = l1; - new_byte = 0; - /* happens when all lines are deleted, so one line has to be cleared */ - ll = ledit_buffer_get_line(buffer, l1); - delete_line_section_base( - buffer, l1, 0, ll->len - ); - } - if (dell1 <= dell2) { - ledit_buffer_delete_line_entries_base(buffer, dell1, dell2); - } - } else if (delmode == DELETE_SOFTLINE) { - int x, softline1, softline2; - ledit_line *line1 = ledit_buffer_get_line(buffer, line_index1); - ledit_pos_to_x_softline(buffer, line_index1, byte_index1, &x, &softline1); - if (line_index1 == line_index2) { - int 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; - 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 == 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( - buffer, line_index1 + 1, - x, 0, &new_byte - ); - rgl1 = line_index1; - rgb1 = 0; - rgl2 = line_index1 + 1; - rgb2 = 0; - } else { - /* cursor has to be be moved to previous hard line - because last line in buffer is deleted */ - /* 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, 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; - rgb2 = line1->len; - } - if (text_ret) { - ledit_buffer_copy_text_to_txtbuf( - buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - ledit_buffer_delete_line_entry_base(buffer, line_index1); - } else { - assert(pl2->start_index + pl2->length - pl1->start_index >= 0); - rgl1 = rgl2 = line_index1; - rgb1 = pl1->start_index; - rgb2 = pl2->start_index + pl2->length; - if (text_ret) { - ledit_buffer_copy_text_to_txtbuf( - buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - delete_line_section_base( - buffer, line_index1, rgb1, rgb2 - rgb1 - ); - if (l2 == line1->softlines - 1 && line_index1 < buffer->lines_num - 1) { - new_line = line_index1 + 1; - ledit_x_softline_to_pos( - buffer, line_index1 + 1, - x, 0, &new_byte - ); - } else if (l2 < line1->softlines - 1) { - new_line = line_index1; - ledit_x_softline_to_pos( - buffer, line_index1, - x, l1, &new_byte - ); - } else if (l1 > 0) { - new_line = line_index1; - ledit_x_softline_to_pos( - buffer, line_index1, - x, l1 - 1, &new_byte - ); - } else { - /* the line has been emptied and is the last line remaining */ - new_line = 0; - new_byte = 0; - } - } - } else { - int x_useless, sl1, sl2; - int l1, l2, b1, b2; - if (line_index1 < line_index2) { - l1 = line_index1; - b1 = byte_index1; - l2 = line_index2; - b2 = byte_index2; - } else { - l1 = line_index2; - b1 = byte_index2; - l2 = line_index1; - b2 = byte_index1; - } - ledit_line *ll1 = ledit_buffer_get_line(buffer, l1); - ledit_line *ll2 = ledit_buffer_get_line(buffer, l2); - 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; - rgb1 = 0; - rgb2 = ll2->len; - if (text_ret) { - ledit_buffer_copy_text_to_txtbuf( - buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - delete_line_section_base(buffer, l1, 0, ll1->len); - ledit_buffer_delete_line_entries_base(buffer, l1 + 1, l2); - new_line = 0; - new_byte = 0; - } else { - if (l2 == buffer->lines_num - 1) { - new_line = l1 - 1; - ledit_line *new_lline = ledit_buffer_get_line(buffer, new_line); - 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_x_softline_to_pos( - buffer, l2 + 1, x, 0, &new_byte - ); - rgl1 = l1; - rgb1 = 0; - rgl2 = l2 + 1; - rgb2 = 0; - } - if (text_ret) { - ledit_buffer_copy_text_to_txtbuf( - buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - ledit_buffer_delete_line_entries_base(buffer, l1, l2); - } - } else if (sl1 == 0) { - rgl1 = l1; - rgb1 = 0; - rgl2 = l2; - rgb2 = pl2->start_index + pl2->length; - if (text_ret) { - ledit_buffer_copy_text_to_txtbuf( - buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - delete_line_section_base(buffer, l2, 0, pl2->start_index + pl2->length); - new_line = l1; - ledit_x_softline_to_pos(buffer, l2, x, 0, &new_byte); - ledit_buffer_delete_line_entries_base(buffer, l1, l2 - 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(buffer, l1, x, sl1 - 1, &new_byte); - } else { - new_line = l1 + 1; - ledit_x_softline_to_pos( - buffer, l2 + 1, - x, 0, &new_byte - ); - } - if (text_ret) { - ledit_buffer_copy_text_to_txtbuf( - buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - delete_line_section_base(buffer, l1, pl1->start_index, ll1->len - pl1->start_index); - ledit_buffer_delete_line_entries_base(buffer, l1 + 1, l2); - } else { - /* FIXME: this could be made nicer by just using the range to - delete all in one go at the end */ - rgl1 = l1; - rgb1 = pl1->start_index; - rgl2 = l2; - rgb2 = pl2->start_index + pl2->length; - if (text_ret) { - ledit_buffer_copy_text_to_txtbuf( - buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - delete_line_section_base(buffer, l1, pl1->start_index, ll1->len - pl1->start_index); - ledit_buffer_insert_text_from_line_base( - buffer, - l1, pl1->start_index, - l2, pl2->start_index + pl2->length, - ll2->len - (pl2->start_index + pl2->length), NULL - ); - ledit_buffer_delete_line_entries_base(buffer, l1 + 1, l2); - new_line = l1; - 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 - a bit weird because the cursor will seem to stay on the - 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( - buffer, l1, x, sl1 + 1 < ll1->softlines ? sl1 + 1 : sl1, &new_byte - ); - } - } - } else { - if (line_index1 == line_index2) { - rgl1 = rgl2 = line_index1; - if (byte_index1 < byte_index2) { - rgb1 = byte_index1; - rgb2 = byte_index2; - } else { - rgb1 = byte_index2; - rgb2 = byte_index1; - } - if (text_ret) { - ledit_buffer_copy_text_to_txtbuf( - buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - delete_line_section_base(buffer, line_index1, rgb1, rgb2 - rgb1); - new_line = line_index1; - new_byte = rgb1; - } else { - if (line_index1 < line_index2) { - rgl1 = line_index1; - rgb1 = byte_index1; - rgl2 = line_index2; - rgb2 = byte_index2; - } else { - rgl1 = line_index2; - rgb1 = byte_index2; - rgl2 = line_index1; - rgb2 = byte_index1; - } - if (text_ret) { - ledit_buffer_copy_text_to_txtbuf( - buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - ledit_line *line1 = ledit_buffer_get_line(buffer, rgl1); - ledit_line *line2 = ledit_buffer_get_line(buffer, rgl2); - delete_line_section_base(buffer, rgl1, rgb1, line1->len - rgb1); - ledit_buffer_insert_text_from_line_base( - buffer, rgl1, rgb1, rgl2, rgb2, line2->len - rgb2, NULL - ); - new_line = rgl1; - new_byte = rgb1; - ledit_buffer_delete_line_entries_base(buffer, rgl1 + 1, rgl2); - } - if (buffer->common->mode == NORMAL) - new_byte = ledit_buffer_get_legal_normal_pos(buffer, new_line, new_byte); - } - if (final_range_ret) { - final_range_ret->line1 = rgl1; - final_range_ret->byte1 = rgb1; - final_range_ret->line2 = rgl2; - final_range_ret->byte2 = rgb2; - } - if (new_line_ret) - *new_line_ret = new_line; - if (new_byte_ret) - *new_byte_ret = new_byte; -} - -/* FIXME: any way to make this more efficient? */ -void -ledit_buffer_resize_textview(ledit_buffer *buffer) { - buffer->total_height = 0; - 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); - line->w = text_w; - line->text_dirty = 1; - set_pango_text_and_highlight(buffer, i); - line->y_offset = buffer->total_height; - line->dirty = 1; - line->h_dirty = 0; - buffer->total_height += line->h; - } - ledit_window_set_scroll_max(buffer->window, buffer->total_height); - if (buffer->display_offset > 0 && - buffer->display_offset + text_h > buffer->total_height) { - ledit_buffer_scroll(buffer, buffer->total_height - text_h); - } -} - -void -ledit_buffer_scroll(ledit_buffer *buffer, long new_offset) { - int text_w, text_h; - ledit_window_get_textview_size(buffer->window, &text_w, &text_h); - if (new_offset + text_h > buffer->total_height) - new_offset = buffer->total_height - text_h; - if (new_offset < 0) - new_offset = 0; - buffer->display_offset = new_offset; - ledit_window_set_scroll_pos(buffer->window, buffer->display_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, - int line, int byte, - /*int snap_to_nearest, int snap_middle, FIXME: take these parameters */ - int *line_ret, int *byte_ret) { - PangoRectangle strong, weak; - int text_w, text_h; - int x, sl_useless; - ledit_window_get_textview_size(buffer->window, &text_w, &text_h); - ledit_line *lline = ledit_buffer_get_line(buffer, line); - 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) { - /* search for the hard line covering the top of the screen */ - int hline = line; - while (lline->y_offset + lline->h <= buffer->display_offset && hline < buffer->lines_num - 1) { - lline = ledit_buffer_get_line(buffer, ++hline); - } - /* the current hard line is now the one at the very top of the screen*/ - 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(layout, i); - if (cur_y_off + lline->y_offset >= buffer->display_offset) { - sl_index = i; - break; - } - pango_layout_line_get_pixel_extents(sl, &ink, &log); - cur_y_off += log.height; - } - if (sl_index >= 0) { - /* we found the correct soft line */ - *line_ret = hline; - 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; - 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(buffer, hline, x, num_sl - 1, byte_ret); - } - } else if (cursor_y + strong.height / PANGO_SCALE > - buffer->display_offset + text_h) { - /* search for the hard line covering the bottom of the screen */ - int hline = line; - while (lline->y_offset > buffer->display_offset + text_h && hline > 0) { - lline = ledit_buffer_get_line(buffer, --hline); - } - /* the current hard line is now the one at the very bottom of the screen*/ - 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(layout, i); - if (lline->y_offset + lline->h - cur_y_off < buffer->display_offset + text_h) { - sl_index = i; - break; - } - pango_layout_line_get_pixel_extents(sl, &ink, &log); - cur_y_off += log.height; - } - if (sl_index >= 0) { - /* we found the correct soft line */ - *line_ret = hline; - 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 = 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(buffer, hline, x, 0, byte_ret); - } - } -} - -void -ledit_xy_to_line_byte(ledit_buffer *buffer, int x, int y, int snap_to_nearest, int *line_ret, int *byte_ret) { - /* FIXME: store current line offset to speed this up */ - /* FIXME: use y_offset in lines */ - long h = 0; - double pos = buffer->display_offset + y; - for (int i = 0; i < buffer->lines_num; 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( - layout, - x * PANGO_SCALE, (int)(pos - h) * PANGO_SCALE, - &index, &trailing - ); - if (snap_to_nearest) { - while (trailing > 0) { - trailing--; - index = ledit_line_next_utf8(line, index); - } - } - *line_ret = i; - *byte_ret = index; - break; - } - h += line->h; - } -} - -static void -scroll_to_pos(ledit_buffer *buffer, int line, int byte, int top) { - PangoRectangle strong, weak; - 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); - 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); - } else { - ledit_buffer_scroll(buffer, cursor_y - text_h + strong.height / PANGO_SCALE); - } -} - -void -ledit_buffer_scroll_to_pos_top(ledit_buffer *buffer, int line, int byte) { - scroll_to_pos(buffer, line, byte, 1); -} - -void -ledit_buffer_scroll_to_pos_bottom(ledit_buffer *buffer, int line, int byte) { - scroll_to_pos(buffer, line, byte, 0); -} - -void -ledit_buffer_ensure_cursor_shown(ledit_buffer *buffer) { - PangoRectangle strong, weak; - 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( - layout, buffer->cur_index, &strong, &weak - ); - long cursor_y = strong.y / PANGO_SCALE + line->y_offset; - if (cursor_y < buffer->display_offset) { - ledit_buffer_scroll(buffer, cursor_y); - } else if (cursor_y + strong.height / PANGO_SCALE > - buffer->display_offset + text_h) { - ledit_buffer_scroll(buffer, cursor_y - text_h + strong.height / PANGO_SCALE); - } -} - -static void -swap(int *a, int *b) { - int tmp = *a; - *a = *b; - *b = tmp; -} - -void -ledit_buffer_sort_selection(int *line1, int *byte1, int *line2, int *byte2) { - if (*line1 > *line2) { - swap(line1, line2); - swap(byte1, byte2); - } else if (*line1 == *line2 && *byte1 > *byte2) { - swap(byte1, byte2); - } -} - -/* FIXME: don't reset selection when selection is clicked away */ -/* FIXME: when selecting with mouse, only call this when button is released */ -/* lines and bytes need to be sorted already! */ -static void -copy_selection_to_x_primary(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2) { - /* FIXME: let window handle this */ - txtbuf *primary = ledit_window_get_primary_clipboard_buffer(); - ledit_buffer_copy_text_to_txtbuf(buffer, primary, line1, byte1, line2, byte2); - XSetSelectionOwner(buffer->common->dpy, XA_PRIMARY, buffer->window->xwin, CurrentTime); - /* - FIXME - if (XGetSelectionOwner(state.dpy, XA_PRIMARY) != state.win) - selclear(); - */ -} - -void -ledit_buffer_set_selection(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2) { - if (line1 == buffer->sel.line1 && line2 == buffer->sel.line2 && - byte1 == buffer->sel.byte1 && byte2 == buffer->sel.byte2) { - return; - } - /* FIXME: maybe check both lines and bytes? */ - if (buffer->sel.line1 >= 0 || line1 >= 0) { - int l1_new = line1, l2_new = line2; - int b1_new = byte1, b2_new = byte2; - ledit_buffer_sort_selection(&l1_new, &b1_new, &l2_new, &b2_new); - ledit_buffer_sort_selection(&buffer->sel.line1, &buffer->sel.byte1, &buffer->sel.line2, &buffer->sel.byte2); - /* FIXME: make this a bit nicer and optimize it */ - if (buffer->sel.line1 > l2_new || buffer->sel.line2 < l1_new) { - for (int i = buffer->sel.line1; i <= buffer->sel.line2; i++) { - if (i >= 0) - ledit_buffer_wipe_line_cursor_attrs(buffer, i); - } - } else { - for (int i = buffer->sel.line1; i < l1_new; i++) { - if (i >= 0) - ledit_buffer_wipe_line_cursor_attrs(buffer, i); - } - for (int i = buffer->sel.line2; i > l2_new; i--) { - ledit_buffer_wipe_line_cursor_attrs(buffer, i); - } - } - if (l1_new >= 0 && l2_new >= 0) { - 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) - copy_selection_to_x_primary(buffer, l1_new, b1_new, l2_new, b2_new); - } - } - buffer->sel.line1 = line1; - buffer->sel.byte1 = byte1; - buffer->sel.line2 = line2; - buffer->sel.byte2 = byte2; -} - -void -ledit_buffer_scroll_handler(void *buffer, long pos) { - ((ledit_buffer *)buffer)->display_offset = pos; -} - -void -ledit_buffer_button_handler(void *data, XEvent *event) { - int l, b; - ledit_buffer *buffer = (ledit_buffer *)data; - int x = event->xbutton.x; - int y = event->xbutton.y; - int snap; - switch (event->type) { - case ButtonPress: - snap = buffer->common->mode == NORMAL ? 0 : 1; - ledit_xy_to_line_byte(buffer, x, y, snap, &l, &b); - buffer->selecting = 1; - if (buffer->common->mode == NORMAL) - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); - buffer->cur_line = l; - buffer->cur_index = b; - /* don't set selection yet because the mouse may not be - dragged, so we don't want to switch to visual (this - allows setting just the cursor position in normal mode - without always switching to visual) */ - ledit_buffer_set_selection(buffer, -1, -1, -1, -1); - if (buffer->common->mode == NORMAL) - ledit_buffer_set_line_cursor_attrs(buffer, l, b); - break; - case ButtonRelease: - buffer->selecting = 0; - break; - case MotionNotify: - if (buffer->selecting) { - y = y >= 0 ? y : 0; - ledit_xy_to_line_byte(buffer, x, y, 1, &l, &b); - if (buffer->common->mode == NORMAL) { - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); - /* FIXME: return to old mode afterwards? */ - /* should change_mode_group even be called here? */ - ledit_buffer_set_mode(buffer, VISUAL); - } - if (buffer->sel.line1 < 0 || buffer->sel.byte1 < 0) { - /* the selection has just started, so the current - position is already set to the beginning of the - selection (see case ButtonPress above) */ - ledit_buffer_set_selection( - buffer, - buffer->cur_line, buffer->cur_index, l, b - ); - } else { - ledit_buffer_set_selection( - buffer, - buffer->sel.line1, buffer->sel.byte1, l, b - ); - } - buffer->cur_line = l; - buffer->cur_index = b; - } - break; - } -} - -void -ledit_buffer_redraw(ledit_buffer *buffer) { - int h = 0; - int cur_line_y = 0; - int cursor_displayed = 0; - 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); - if (h + line->h > buffer->display_offset) { - /* 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; - int dest_y = h - buffer->display_offset; - int final_h = line->h; - if (h < buffer->display_offset) { - dest_y = 0; - final_y = buffer->display_offset - h; - final_h -= buffer->display_offset - h; - } - if (dest_y + final_h > text_h) { - final_h -= final_y + final_h - - buffer->display_offset - text_h; - } - cache_pixmap *pix = cache_get_pixmap( - buffer->cache, line->cache_pixmap_index - ); - XCopyArea( - buffer->common->dpy, pix->pixmap, - buffer->window->drawable, buffer->window->gc, - 0, final_y, line->w, final_h, 0, dest_y - ); - if (i == buffer->cur_line) { - cur_line_y = h - buffer->display_offset; - cursor_displayed = 1; - } - 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( - layout, buffer->cur_index, &strong, &weak - ); - /* FIXME: long, int, etc. */ - int cursor_y = strong.y / PANGO_SCALE + cur_line_y; - if (cursor_displayed && cursor_y >= 0) { - if (buffer->common->mode == NORMAL) { - /* FIXME: figure out if there's a better way to do this */ - /* Seriously, which of the pango folks though it would be a good idea to - not highlight spaces at the end of soft lines? That is an utterly - horrible idea. Or am I just too stupid to use it properly? */ - /* FIXME: properly document what is happening here */ - - int box_x = strong.x / PANGO_SCALE; - int box_w = 10; - /* determine where the box should be drawn */ - PangoDirection dir = PANGO_DIRECTION_LTR; - int tmp_index = buffer->cur_index; - if (buffer->cur_index >= cur_line->len) - tmp_index = cur_line->len - 1; - if (tmp_index >= 0) - dir = pango_layout_get_direction(layout, tmp_index); - - int x, 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; - } - if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL) { - box_x = box_x - box_w; - } - - if (buffer->cur_index == cur_line->len || - (cur_line->text[buffer->cur_index] == ' ' && - buffer->cur_index == sl->start_index + sl->length - 1)) { - XFillRectangle( - buffer->common->dpy, buffer->window->drawable, buffer->window->gc, - box_x, cursor_y, - box_w, strong.height / PANGO_SCALE - ); - } - } else if (buffer->common->mode == INSERT || buffer->common->mode == VISUAL) { - XDrawLine( - buffer->common->dpy, buffer->window->drawable, buffer->window->gc, - strong.x / PANGO_SCALE, cursor_y, - strong.x / PANGO_SCALE, - (strong.y + strong.height) / PANGO_SCALE + cur_line_y - ); - } - } - /* move input method position */ - if (!ledit_window_bottom_bar_text_shown(buffer->window)) { - xximspot( - buffer->window, - strong.x / PANGO_SCALE, - (strong.y + strong.height) / PANGO_SCALE + cur_line_y - ); - } -} - -static void -undo_insert_helper(void *data, int line, int byte, char *text, int text_len) { - ledit_buffer_insert_text_with_newlines_base((ledit_buffer *)data, line, byte, text, text_len, NULL, NULL); -} - -static void -undo_delete_helper(void *data, int line1, int byte1, int line2, int byte2) { - ledit_buffer_delete_range_base((ledit_buffer *)data, 0, line1, byte1, line2, byte2, NULL, NULL, NULL, NULL); -} - -void -ledit_buffer_undo(ledit_buffer *buffer) { - int min_line; - ledit_undo( - buffer->undo, buffer->common->mode, buffer, &undo_insert_helper, - &undo_delete_helper, &buffer->cur_line, &buffer->cur_index, &min_line - ); - if (buffer->common->mode == NORMAL) { - buffer->cur_index = ledit_buffer_get_legal_normal_pos( - buffer, buffer->cur_line, buffer->cur_index - ); - } - if (min_line < buffer->lines_num) - ledit_buffer_recalc_from_line(buffer, min_line > 0 ? min_line - 1 : min_line); - /* FIXME: show undo message */ -} - -void -ledit_buffer_redo(ledit_buffer *buffer) { - int min_line; - ledit_redo( - buffer->undo, buffer->common->mode, buffer, &undo_insert_helper, - &undo_delete_helper, &buffer->cur_line, &buffer->cur_index, &min_line - ); - if (buffer->common->mode == NORMAL) { - buffer->cur_index = ledit_buffer_get_legal_normal_pos( - buffer, buffer->cur_line, buffer->cur_index - ); - } - if (min_line < buffer->lines_num) - ledit_buffer_recalc_from_line(buffer, min_line > 0 ? min_line - 1 : min_line); - /* FIXME: show undo message */ -} - -static void -paste_callback(void *data, char *text, int len) { - ledit_buffer *buffer = (ledit_buffer *)data; - txtbuf ins_buf = {.text = text, .len = len, .cap = len}; - ledit_range cur_range, ins_range; - cur_range.line1 = ins_range.line1 = buffer->cur_line; - cur_range.byte1 = ins_range.byte1 = buffer->cur_index; - ledit_buffer_insert_text_with_newlines( - buffer, buffer->cur_line, buffer->cur_index, - text, len, &buffer->cur_line, &buffer->cur_index - ); - cur_range.line2 = ins_range.line2 = buffer->cur_line; - cur_range.byte2 = ins_range.byte2 = buffer->cur_index; - ledit_push_undo_insert( - buffer->undo, &ins_buf, ins_range, cur_range, 1, buffer->common->mode - ); -} - -/* FIXME: guard against buffer being destroyed before paste callback is nulled */ - -void -ledit_buffer_paste_clipboard(ledit_buffer *buffer) { - ledit_window_set_paste_callback(buffer->window, &paste_callback, buffer); - clipboard_paste_clipboard(buffer->window); -} - -void -ledit_buffer_paste_primary(ledit_buffer *buffer) { - ledit_window_set_paste_callback(buffer->window, &paste_callback, buffer); - clipboard_paste_primary(buffer->window); -} diff --git a/buffer.h b/buffer.h @@ -1,31 +1,23 @@ +#ifndef _LEDIT_BUFFER_H_ +#define _LEDIT_BUFFER_H_ + typedef struct ledit_buffer ledit_buffer; +#include "view.h" + /* FIXME: size_t for len, etc. */ typedef struct { ledit_buffer *parent_buffer; - 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 */ + char *text; /* text, stored as gap buffer */ + size_t gap; /* position of gap for gap buffer */ + size_t cap; /* allocated space for text */ + size_t len; /* actual length of text */ } ledit_line; typedef struct { char *text; - int line; - int byte; + size_t line; + size_t byte; } ledit_buffer_mark; typedef struct { @@ -35,155 +27,283 @@ typedef struct { /* TODO: advisory lock on file? also check if modification date changed before writing */ struct ledit_buffer { - ledit_common *common; /* common stuff, e.g. display, window, etc. */ - ledit_line *lines; /* array of lines */ - ledit_theme *theme; - char *filename; - int lines_cap; /* number of lines allocated in array */ - int lines_num; /* number of used lines */ - int cur_line; /* current line */ - int cur_index; /* current byte index in line */ - int trailing; /* used by pango for determining if index is at - * beginning or end of character */ - int trailing_bytes; /* same thing, but with bytes instead of utf8 characters */ - int end_of_soft_line; /* used to handle special behavior at end end of soft line */ - long total_height; /* total pixel height of all lines */ - long display_offset; /* current pixel offset of viewport */ - int selecting; - ledit_range sel; /* current selection; all entries -1 if no selection */ - ledit_cache *cache; - ledit_undo_stack *undo; - ledit_window *window; - ledit_buffer_marklist *marklist; + ledit_common *common; /* common stuff, e.g. display, etc. */ + char *filename; /* last opened filename */ + ledit_undo_stack *undo; /* undo manager */ + ledit_buffer_marklist *marklist; /* list of mark positions set */ + ledit_line *lines; /* array of lines */ + ledit_view **views; /* array of registered views */ + size_t views_num; /* number of views in array */ + size_t lines_cap; /* size of lines array */ + size_t lines_gap; /* position of gap for line gap buffer */ + size_t lines_num; /* number of lines */ + int hard_line_based; /* whether operations should work on soft- or hardlines + Note that this doesn't actually change any behavior of + the buffer functions, it is just stored here so all + views can be updated to display it in their status. */ }; -enum delete_mode { - DELETE_CHAR, - DELETE_SOFTLINE, - DELETE_HARDLINE -}; +/* + * Create a new buffer with one empty line + */ +ledit_buffer *ledit_buffer_create(ledit_common *common); + +/* + * Set the hard line mode of the buffer and update the + * displayed mode in all views. + */ +void ledit_buffer_set_hard_line_based(ledit_buffer *buffer, int hl); + +/* + * Add a new view to the buffer. + */ +void ledit_buffer_add_view(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size_t line, size_t pos); + +/* + * Remove the given view from the buffer. + * Nothing is done if the view does not belong to the buffer. + */ +void ledit_buffer_remove_view(ledit_buffer *buffer, ledit_view *view); + +/* + * Call 'view_recalc_from_line' for all views. + */ +void ledit_buffer_recalc_all_views_from_line(ledit_buffer *buffer, size_t line); -ledit_buffer *ledit_buffer_create(ledit_common *common, ledit_theme *theme, ledit_window *window); -int ledit_buffer_load_file(ledit_buffer *buffer, char *filename, int line, char **errstr); +/* + * Load a file into the buffer at line 'line'. + * If the line is empty, the text is inserted directly. + * Otherwise it is appended after the line. + * Returns 0 on success and 1 on error. In case of an error, *errstr is filled + * with an error message which must be copied as soon as possible because it may + * be overwritten by subsequent function calls. + */ +int ledit_buffer_load_file(ledit_buffer *buffer, char *filename, size_t line, char **errstr); + +/* + * Write the buffer to a file. + * Returns 0 on success and 1 on error. In case of an error, *errstr is filled + * with an error message which must be copied as soon as possible because it may + * be overwritten by subsequent function calls. + */ int ledit_buffer_write_to_file(ledit_buffer *buffer, char *filename, char **errstr); + +/* + * Destroy a buffer. + */ void ledit_buffer_destroy(ledit_buffer *buffer); + +/* + * Normalize a line, i.e. move the gap to the end and add '\0' + * so the text can be used as a normal string + */ void ledit_buffer_normalize_line(ledit_line *line); -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_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); -int ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int line, int byte, int num); -int ledit_buffer_move_cursor_visually(ledit_buffer *buffer, int line, int pos, int movement, int *prev_index_ret); - -void ledit_buffer_next_word(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret); -void ledit_buffer_next_word_end(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret); -void ledit_buffer_next_bigword(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret); -void ledit_buffer_next_bigword_end(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret); -void ledit_buffer_prev_word(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret); -void ledit_buffer_prev_bigword(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret); -void ledit_buffer_get_pos_softline_bounds(ledit_buffer *buffer, int line, int pos, int *start_byte_ret, int *end_byte_ret); -void ledit_buffer_get_softline_bounds(ledit_buffer *buffer, int line, int softline, int *start_byte_ret, int *end_byte_ret); -int ledit_buffer_get_softline_count(ledit_buffer *buffer, int line); -int ledit_buffer_pos_to_softline(ledit_buffer *buffer, int line, int pos); -void ledit_buffer_get_cursor_pixel_pos(ledit_buffer *buffer, int line, int pos, int *x_ret, int *y_ret, int *h_ret); - -size_t ledit_buffer_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2); -void ledit_buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2); -void ledit_buffer_copy_text_to_txtbuf( + +/* + * Insert 'src_len' bytes from 'src_line' starting at byte position 'src_index' + * into line 'dst_line' at byte position 'dst_index'. + * 'dst_line' must not be the same as 'src_line'. + * If 'text_ret' is not NULL, the copied text is additionally copied into 'text_ret'. + * This function does not update the views or normalize the lines, so it should + * only be used for efficiency purposes when performing multiple operations. + */ +void ledit_buffer_insert_text_from_line_base( ledit_buffer *buffer, - txtbuf *buf, /* oh, isn't that a very non-confusing name? */ - int line1, int byte1, - int line2, int byte2 + size_t dst_line, size_t dst_index, + size_t src_line, size_t src_index, size_t src_len, + txtbuf *text_ret ); -void ledit_buffer_recalc_line(ledit_buffer *buffer, int line); -void ledit_buffer_recalc_from_line(ledit_buffer *buffer, int line); -void ledit_buffer_recalc_all_lines(ledit_buffer *buffer); -/* The following functions all have two versions: - * - The _base version does not call any recalc functions - this can be used - * when multiple operations are performed before the next render in order to - * avoid recalculating everything every time. - * - The non-base versions call the appropriate recalc function in order to - * keep everything in a consistent state. */ +/* + * Same as ledit_buffer_insert_text_from_line_base, but the views are updated afterwards. + */ +void ledit_buffer_insert_text_from_line( + ledit_buffer *buffer, + size_t dst_line, size_t dst_index, + size_t src_line, size_t src_index, size_t src_len, + txtbuf *text_ret +); -void ledit_buffer_insert_text_base(ledit_buffer *buffer, int line_index, int index, char *text, int len); -void ledit_buffer_insert_text_with_newlines_base( +/* + * Insert text 'text' with length 'len' at line 'line_index' and byte position 'index'. + * The text must not contain newlines. + * This function does not update the views or normalize the lines, so it should + * only be used for efficiency purposes when performing multiple operations. + */ +void ledit_buffer_insert_text_base( ledit_buffer *buffer, - int line_index, int index, - char *text, long len, - int *end_line_ret, int *end_char_ret + size_t line_index, size_t index, + char *text, size_t len ); -void ledit_buffer_append_line_base(ledit_buffer *buffer, int line_index, int text_index); -void ledit_buffer_delete_line_entries_base(ledit_buffer *buffer, int index1, int index2); -void ledit_buffer_delete_line_entry_base(ledit_buffer *buffer, int index); -int ledit_buffer_delete_unicode_char_base(ledit_buffer *buffer, int line_index, int byte_index, int dir); -void ledit_buffer_delete_range_base( - ledit_buffer *buffer, enum delete_mode delmode, - int line_index1, int byte_index1, - int line_index2, int byte_index2, - int *new_line_ret, int *new_byte_ret, - ledit_range *final_range_ret, txtbuf *text_ret + +/* + * Same as ledit_buffer_insert_text_base, but the views are updated afterwards. + */ +void ledit_buffer_insert_text( + ledit_buffer *buffer, + size_t line_index, size_t index, + char *text, size_t len ); -void ledit_buffer_insert_text_from_line_base( + +/* Insert text 'text' with length 'len' at line 'line_index' and byte position 'index. + * The text may contain newlines. + * If end_line_ret is not NULL, the line index at the end of the insertion is + * written into it. + * If end_byte_ret is not NULL, the byte position at the end of the insertion is + * written into it. + * This function does not update the views or normalize the lines, so it should + * only be used for efficiency purposes when performing multiple operations. + */ +void ledit_buffer_insert_text_with_newlines_base( ledit_buffer *buffer, - int dst_line, int dst_index, - int src_line, int src_index, int src_len, - txtbuf *text_ret + size_t line_index, size_t index, + char *text, size_t len, + size_t *end_line_ret, size_t *end_char_ret ); -void ledit_buffer_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len); +/* + * Same as ledit_buffer_insert_text_with_newlines_base, but the views are updated afterwards. + */ void ledit_buffer_insert_text_with_newlines( ledit_buffer *buffer, - int line_index, int index, - char *text, long len, - int *end_line_ret, int *end_char_ret + size_t line_index, size_t index, + char *text, size_t len, + size_t *end_line_ret, size_t *end_char_ret ); -void ledit_buffer_append_line(ledit_buffer *buffer, int line_index, int text_index); -void ledit_buffer_delete_line_entries(ledit_buffer *buffer, int index1, int index2); -void ledit_buffer_delete_line_entry(ledit_buffer *buffer, int index); -int ledit_buffer_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir); -void ledit_buffer_delete_range( - ledit_buffer *buffer, enum delete_mode delmode, - int line_index1, int byte_index1, - int line_index2, int byte_index2, - int *new_line_ret, int *new_byte_ret, - ledit_range *final_range_ret, txtbuf *text_ret -); -void ledit_buffer_insert_text_from_line( + +/* + * Append line after line at 'line_index'. + * if 'break_text' is not 0, the text on line 'line_index' starting at + * byte index 'text_index' is moved to the newly appended line. + * The views are notified that a line has been appended, but not told to update + * their line heights and offsets. + */ +void ledit_buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text); + +/* + * Same as ledit_buffer_append_line_base, but the views are told to update + * their line heights and offsets afterwards. + */ +void ledit_buffer_append_line(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text); + +/* + * Delete lines between 'index1' and 'index2' (inclusive). + * The views are notified of the deletion but not told to + * update their line heights and offsets. + */ +void ledit_buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2); + +/* + * Same as ledit_buffer_delete_line_entries_base, but the views are told to + * update their line heights and offsets. + */ +void ledit_buffer_delete_line_entries(ledit_buffer *buffer, size_t index1, size_t index2); + +/* + * Convenience function to call ledit_buffer_delete_line_entries_base + * with two times the same line index. + */ +void ledit_buffer_delete_line_entry_base(ledit_buffer *buffer, size_t index); + +/* + * Convenience function to call ledit_buffer_delete_line_entries + * with two times the same line index. + */ +void ledit_buffer_delete_line_entry(ledit_buffer *buffer, size_t index); + +/* + * Get the line at logical index 'index'. + * The returned line is only valid until the next + * action that appends or deletes line entries. + */ +ledit_line *ledit_buffer_get_line(ledit_buffer *buffer, size_t index); + +/* + * Tell views to recalculate the height of line 'line' and + * update the pixel offsets of the following lines. + */ +void ledit_buffer_recalc_line(ledit_buffer *buffer, size_t line); + +/* + * Tell views to recalculate the height for all lines starting at 'line' + * where the text_dirty attribute is set and update the pixel offsets of + * all lines after 'line'. + * Also clear the text_dirty attribute for all lines starting at 'line'. + */ +void ledit_buffer_recalc_from_line(ledit_buffer *buffer, size_t line); + +/* + * Tell views to recalculate all lines. + * Also clear the text_dirty attribute for all lines. + */ +void ledit_buffer_recalc_all_lines(ledit_buffer *buffer); + +/* + * Get needed length of text range, including newlines. + * - NUL is not included + * - if the last range ends at the end of a line, the newline is *not* included + * - the range must be sorted already + */ +size_t ledit_buffer_textlen(ledit_buffer *buffer, size_t line1, size_t byte1, size_t line2, size_t byte2); + +/* + * Copy text range into given buffer. + * - dst is null-terminated + * - dst must be large enough to contain the text and NUL (only use this together with ledit_buffer_textlen) + * - the range must be sorted already + */ +void ledit_buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2); + +/* + * Copy text range into given buffer and resize it if necessary. + * - the range must be sorted already + */ +void ledit_buffer_copy_text_to_txtbuf( ledit_buffer *buffer, - int dst_line, int dst_index, - int src_line, int src_index, int src_len, - txtbuf *text_ret + txtbuf *buf, /* oh, isn't that a very non-confusing name? */ + size_t line1, size_t byte1, + size_t line2, size_t byte2 ); -void ledit_buffer_resize_width(ledit_buffer *buffer, int width); -/* x and y are in pixels, if snap_to_nearest is nonzero, the returned byte - is at the nearest grapheme boundary, if it is zero, the byte is always - the beginning of the grapheme under the position */ -void ledit_xy_to_line_byte(ledit_buffer *buffer, int x, int y, int snap_to_nearest, int *line_ret, int *byte_ret); -void ledit_buffer_get_nearest_legal_pos(ledit_buffer *buffer, int line, int byte, int *line_ret, int *byte_ret); -void ledit_buffer_ensure_cursor_shown(ledit_buffer *buffer); -void ledit_buffer_scroll_handler(void *buffer, long pos); -void ledit_buffer_button_handler(void *data, XEvent *event); -void ledit_buffer_redraw(ledit_buffer *buffer); -void ledit_buffer_undo(ledit_buffer *buffer); -void ledit_buffer_redo(ledit_buffer *buffer); -void ledit_buffer_set_selection(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2); -void ledit_buffer_set_mode(ledit_buffer *buffer, enum ledit_mode mode); -void ledit_buffer_paste_clipboard(ledit_buffer *buffer); -void ledit_buffer_paste_primary(ledit_buffer *buffer); -void ledit_buffer_resize_textview(ledit_buffer *buffer); -void ledit_buffer_scroll(ledit_buffer *buffer, long new_offset); -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_buffer *buffer, int line, int byte); -void ledit_buffer_insert_mark(ledit_buffer *buffer, char *mark, int len, int line, int byte); +/* + * Get the byte index of the previous utf8 character starting at byte index 'index'. + */ +size_t ledit_line_next_utf8(ledit_line *line, size_t index); + +/* + * Get the byte index of the previous utf8 character starting at byte index 'index'. + */ +size_t ledit_line_prev_utf8(ledit_line *line, size_t index); + +/* + * Get the unicode character index of a byte position. + */ +size_t line_byte_to_char(ledit_line *line, size_t byte); + +/* + * Delete the section of line 'line' starting at byte 'start' with length 'length' + * and notify the views. + * Note that this does not tell the views to recalculate their line heights and offsets. + */ +void ledit_buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length); + +/* + * Delete the unicode char at 'line_index' and 'byte_index' if 'dir' is >= 0, + * or the previous char if 'dir' is < 0. + * Returns the byte index where the deleted text was located. + * This function only notifies the views of the deletion, but does not tell + * them to recalculate their line heights and offsets. + */ +size_t ledit_buffer_delete_unicode_char_base(ledit_buffer *buffer, size_t line_index, size_t byte_index, int dir); + +/* + * Same as ledit_buffer_delete_unicode_char_base, but the views are updated. + */ +size_t ledit_buffer_delete_unicode_char(ledit_buffer *buffer, size_t line_index, size_t byte_index, int dir); + +/* + * Insert a mark with key 'mark' at line 'line' and byte 'byte'. + */ +void ledit_buffer_insert_mark(ledit_buffer *buffer, char *mark, size_t len, size_t line, size_t byte); + +#endif diff --git a/cache.c b/cache.c @@ -20,11 +20,13 @@ cache_create(Display *dpy) { 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->pixmaps[i].line = 0; + cache->pixmaps[i].valid = 0; } for (size_t i = 0; i < LAYOUT_CACHE_SIZE; i++) { cache->layouts[i].layout = NULL; - cache->layouts[i].line = -1; + cache->layouts[i].line = 0; + cache->layouts[i].valid = 0; } cache->num_pixmaps = PIXMAP_CACHE_INITIAL_SIZE; cache->num_layouts = LAYOUT_CACHE_SIZE; @@ -35,28 +37,30 @@ cache_create(Display *dpy) { void cache_flush( ledit_cache *cache, void *callback_data, - void (*set_pixmap_line)(void *, int, int), - void (*set_layout_line)(void *, int, int)) { + void (*invalidate_pixmap_line)(void *, size_t), + void (*invalidate_layout_line)(void *, size_t)) { cache_invalidate_from_line( - cache, 0, callback_data, set_pixmap_line, set_layout_line + cache, 0, callback_data, invalidate_pixmap_line, invalidate_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)) { + ledit_cache *cache, size_t start, void *callback_data, + void (*invalidate_pixmap_line)(void *, size_t), + void (*invalidate_layout_line)(void *, size_t)) { 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; + invalidate_pixmap_line(callback_data, cache->pixmaps[i].line); + cache->pixmaps[i].line = 0; + cache->pixmaps[i].valid = 0; } } 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; + invalidate_layout_line(callback_data, cache->layouts[i].line); + cache->layouts[i].line = 0; + cache->layouts[i].valid = 0; } } } @@ -79,49 +83,44 @@ cache_destroy(ledit_cache *cache) { } cache_pixmap * -cache_get_pixmap(ledit_cache *cache, int index) { - assert(index >= 0 && (size_t)index < cache->num_pixmaps); +cache_get_pixmap(ledit_cache *cache, size_t index) { + assert(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); +cache_get_layout(ledit_cache *cache, size_t index) { + assert(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 */ + +/* FIXME: max pixmap cache size */ void cache_assign_pixmap_index( - ledit_cache *cache, int line, + ledit_cache *cache, size_t line, void *callback_data, - int (*line_needed)(void *, int), - void (*set_pixmap_line)(void *, int, int)) { - int line_index; + int (*line_needed)(void *, size_t), + void (*set_pixmap_line)(void *, size_t, size_t), + void (*invalidate_pixmap_line)(void *, size_t)) { + size_t line_index; 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; + int valid = cache->pixmaps[entry_index].valid; /* 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))) { + if (!valid || + (valid && !line_needed(callback_data, line_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); + if (pix->valid) + invalidate_pixmap_line(callback_data, pix->line); pix->line = line; - set_pixmap_line(callback_data, line, (int)entry_index); + pix->valid = 1; + set_pixmap_line(callback_data, line, entry_index); return; } } @@ -133,32 +132,31 @@ cache_assign_pixmap_index( 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].line = 0; + cache->pixmaps[i].valid = 0; cache->pixmaps[i].pixmap = None; cache->pixmaps[i].draw = NULL; } 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); + pix->valid = 1; + set_pixmap_line(callback_data, line, entry_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 cache_assign_layout_index( + ledit_cache *cache, size_t line, void *callback_data, - void (*set_layout_line)(void *, int, int)) { + void (*set_layout_line)(void *, size_t, size_t), + void (*invalidate_layout_line)(void *, size_t)) { 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); + if (layout->valid) + invalidate_layout_line(callback_data, layout->line); + layout->valid = 1; layout->line = line; - set_layout_line(callback_data, line, (int)old); + set_layout_line(callback_data, line, old); } diff --git a/cache.h b/cache.h @@ -4,7 +4,7 @@ #define LAYOUT_CACHE_SIZE 40 /* - * The initial number of pixmas in the cache. + * The initial number of pixmaps in the cache. * The size is increased when more pixmaps are visible * at the same time than there are entries in the cache. */ @@ -13,13 +13,15 @@ typedef struct { Pixmap pixmap; XftDraw *draw; - int w, h; /* width and height of the pixmap */ - int line;/* the line associated with this entry, or -1 if unassigned */ + int w, h; /* width and height of the pixmap */ + size_t line; /* the line associated with this entry */ + int valid; /* whether the entry is assigned to a line */ } cache_pixmap; typedef struct { PangoLayout *layout; - int line; /* the line associated with this entry, or -1 if unassigned */ + size_t line; /* the line associated with this entry */ + int valid; /* whether the entry is assigned to a line */ } cache_layout; typedef struct { @@ -42,26 +44,26 @@ 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. + * invalidate_pixmap_line is called with callback_data as its first + * argument and a line index which has been removed from the pixmap + * cache as the second argument. + * invalidate_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) + void (*invalidate_pixmap_line)(void *, size_t), + void (*invalidate_layout_line)(void *, size_t) ); /* * Like cache_flush, but only line numbers >= start are invalidated. */ void cache_invalidate_from_line( - ledit_cache *cache, int start, + ledit_cache *cache, size_t start, void *callback_data, - void (*set_pixmap_line)(void *, int, int), - void (*set_layout_line)(void *, int, int) + void (*invalidate_pixmap_line)(void *, size_t), + void (*invalidate_layout_line)(void *, size_t) ); /* @@ -72,12 +74,12 @@ void cache_destroy(ledit_cache *cache); /* * Get the cache_pixmap at index. */ -cache_pixmap *cache_get_pixmap(ledit_cache *cache, int index); +cache_pixmap *cache_get_pixmap(ledit_cache *cache, size_t index); /* * Get the cache_layout at index. */ -cache_layout *cache_get_layout(ledit_cache *cache, int index); +cache_layout *cache_get_layout(ledit_cache *cache, size_t index); /* * The following two functions have a somewhat cumbersome interface @@ -94,17 +96,19 @@ cache_layout *cache_get_layout(ledit_cache *cache, int index); * 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. + * set before, it is reset by calling 'invalidate_pixmap_line' with + * 'callback_data' as the first argumentand the old line as the + * second argument. + * The cache index of the new line is changed by calling 'set_pixmap_line' + * with 'callback_data' as the first argument, 'line' as the second + * argument, and the new cache index as the third argument. */ void cache_assign_pixmap_index( - ledit_cache *cache, int line, + ledit_cache *cache, size_t line, void *callback_data, - int (*line_needed)(void *, int), - void (*set_pixmap_line)(void *, int, int) + int (*line_needed)(void *, size_t), + void (*set_pixmap_line)(void *, size_t, size_t), + void (*invalidate_pixmap_line)(void *, size_t) ); /* @@ -112,14 +116,16 @@ void cache_assign_pixmap_index( * 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. + * set before, it is reset by calling 'invalidate_layout_line' with + * 'callback_data' as the first argument and the old line as the + * second argument. + * The cache index of the new line is changed by calling 'set_layout_line' + * with 'callback_data' as the first argument, 'line' as the second + * argument, and the new cache index as the third argument. */ void cache_assign_layout_index( - ledit_cache *cache, int line, + ledit_cache *cache, size_t line, void *callback_data, - void (*set_layout_line)(void *, int, int) + void (*set_layout_line)(void *, size_t, size_t), + void (*invalidate_layout_line)(void *, size_t) ); diff --git a/cleanup.h b/cleanup.h @@ -0,0 +1,3 @@ +/* This is here so it can be called from other places + even though the function definition is in ledit.c */ +void ledit_cleanup(void); diff --git a/common.h b/common.h @@ -1,8 +1,8 @@ typedef struct { - int line1; - int byte1; - int line2; - int byte2; + size_t line1; + size_t byte1; + size_t line2; + size_t byte2; } ledit_range; enum ledit_mode { @@ -18,5 +18,4 @@ typedef struct { int screen; int depth; int redraw; - enum ledit_mode mode; } ledit_common; diff --git a/keys_basic.c b/keys_basic.c @@ -1,5 +1,6 @@ -/* FIXME: I guess hard_line_based should be updated for all buffers/windows when that gets supported */ -/* FIXME: cursor isn't shown on spaces at end of softlines */ +/* FIXME: the stacks here are shared for all views which can cause weird + behavior, but I'm not sure what would be more logical */ +/* FIXME: cursor isn't shown properly on spaces at end of softlines */ /* FIXME: selection is sometimes not reset when it is "clicked away" */ /* FIXME: use weak cursor */ /* FIXME: spaces at end of soft line are weird in bidi text @@ -28,10 +29,10 @@ #include "theme.h" #include "window.h" #include "buffer.h" +#include "view.h" #include "search.h" #include "keys.h" -#include "action.h" #include "keys_basic.h" #include "keys_command.h" #include "keys_basic_config.h" @@ -43,7 +44,7 @@ static int last_lines_scrolled = -1; struct repetition_stack_elem { char *key_text; - int len; + size_t len; KeySym sym; unsigned int key_state; int lang_index; @@ -57,7 +58,7 @@ static struct { struct repetition_stack_elem *tmp_stack; } repetition_stack = {0, 0, 0, 0, NULL, 0, 0, NULL}; -typedef void (*motion_callback)(ledit_buffer *buffer, int line, int char_pos, enum key_type type); +typedef void (*motion_callback)(ledit_view *view, size_t line, size_t char_pos, enum key_type type); struct key_stack_elem { enum key_type key; @@ -68,8 +69,6 @@ struct key_stack_elem { * the command should operate on lines or chars) */ motion_callback motion_cb; int count; /* number of repetitions */ - int data1; /* misc. data 1 */ - int data2; /* misc. data 2 */ }; static struct { @@ -77,7 +76,7 @@ static struct { struct key_stack_elem *stack; } key_stack = {0, 0, NULL}; -static struct action (*grab_char_cb)(ledit_buffer *buffer, char *text, int len) = NULL; +static struct action (*grab_char_cb)(ledit_view *view, char *text, size_t len) = NULL; static int hard_line_based = 1; void @@ -109,24 +108,24 @@ static struct key_stack_elem *peek_key_stack(void); static struct key_stack_elem *pop_key_stack(void); void clear_key_stack(void); -static void move_cursor_left_right(ledit_buffer *buffer, int dir, int allow_illegal_index); -static void move_cursor_up_down(ledit_buffer *buffer, int dir); +static void move_cursor_left_right(ledit_view *view, int dir, int allow_illegal_index); +static void move_cursor_up_down(ledit_view *view, int dir); static void push_num(int num); -static void delete_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type); -static void yank_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type); +static void delete_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type); +static void yank_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type); static void get_new_line_softline( - ledit_buffer *buffer, int cur_line, int cur_index, - int movement, int *new_line_ret, int *new_softline_ret + ledit_view *view, size_t cur_line, size_t cur_index, + int movement, size_t *new_line_ret, int *new_softline_ret ); -static void move_cursor_logically(ledit_buffer *buffer, int movement_dir, int allow_illegal_index); -static void change_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type); -static void push_undo_empty_insert(ledit_buffer *buffer, int line, int index, int start_group); -static void move_half_screen(ledit_buffer *buffer, int movement); +static void move_cursor_logically(ledit_view *view, int movement_dir, int allow_illegal_index); +static void change_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type); +static void push_undo_empty_insert(ledit_view *view, size_t line, size_t index, int start_group); +static void move_half_screen(ledit_view *view, int movement); /* FIXME: move to common */ static void -swap(int *a, int *b) { - int tmp = *a; +swap_sz(size_t *a, size_t *b) { + size_t tmp = *a; *a = *b; *b = tmp; } @@ -151,8 +150,6 @@ push_key_stack(void) { e->followup = KEY_NONE; e->motion_cb = NULL; e->count = 0; - e->data1 = 0; - e->data2 = 0; key_stack.len++; return e; } @@ -182,8 +179,8 @@ clear_key_stack(void) { } static struct action -err_invalid_key(ledit_buffer *buffer) { - ledit_window_show_message(buffer->window, "Invalid key", -1); +err_invalid_key(ledit_view *view) { + ledit_window_show_message(view->window, "Invalid key", -1); clear_key_stack(); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; @@ -213,8 +210,14 @@ get_key_repeat_and_motion_cb(motion_callback *cb_ret) { } else if (e->count == 0) { num = 0; } - if (e != NULL) - num *= (e->count > 0 ? e->count : 1); + if (e != NULL) { + int new_count = e->count > 0 ? e->count : 1; + if (INT_MAX / new_count < num) { + /* FIXME: show error */ + num = INT_MAX; + } + num *= new_count; + } } else { num = 0; } @@ -329,27 +332,28 @@ finalize_repetition_stack(void) { down (negative means up, positive means down) */ static void get_new_line_softline( - ledit_buffer *buffer, int cur_line, int cur_index, int movement, - int *new_line_ret, int *new_softline_ret) { + ledit_view *view, size_t cur_line, size_t cur_index, int movement, + size_t *new_line_ret, int *new_softline_ret) { if (hard_line_based) { - *new_line_ret = cur_line + movement; - if (*new_line_ret < 0) + if (movement < 0 && (size_t)-movement > cur_line) *new_line_ret = 0; - else if (*new_line_ret >= buffer->lines_num) - *new_line_ret = buffer->lines_num - 1; + else + *new_line_ret = cur_line + movement; + if (*new_line_ret >= view->lines_num) + *new_line_ret = view->lines_num - 1; *new_softline_ret = 0; } else { - int softline = ledit_buffer_pos_to_softline(buffer, cur_line, cur_index); + int softline = view_pos_to_softline(view, cur_line, cur_index); if (movement > 0) { - int softlines = ledit_buffer_get_softline_count(buffer, cur_line); + int softlines = view_get_softline_count(view, cur_line); if (softlines - softline > movement) { *new_line_ret = cur_line; *new_softline_ret = softline + movement; } else { movement -= (softlines - softline - 1); - int endline = cur_line + 1; - while (movement > 0 && endline < buffer->lines_num) { - softlines = ledit_buffer_get_softline_count(buffer, endline); + size_t endline = cur_line + 1; + while (movement > 0 && endline < view->lines_num) { + softlines = view_get_softline_count(view, endline); movement -= softlines; endline++; } @@ -368,13 +372,12 @@ get_new_line_softline( *new_softline_ret = softline + movement; } else { movement += softline; - int endline = cur_line - 1; - while (movement < 0 && endline >= 0) { - softlines = ledit_buffer_get_softline_count(buffer, endline); + size_t endline = cur_line; + while (movement < 0 && endline > 0) { + softlines = view_get_softline_count(view, endline-1); movement += softlines; endline--; } - endline++; if (movement >= 0) { *new_softline_ret = movement; } else { @@ -389,21 +392,21 @@ get_new_line_softline( } } -/* FIXME: don't overwrite buffer->cur_line, etc. here? */ +/* FIXME: don't overwrite view->cur_line, etc. here? */ static void delete_range( - ledit_buffer *buffer, + ledit_view *view, int line_based, int selected, - int line_index1, int byte_index1, - int line_index2, int byte_index2, + size_t line_index1, size_t byte_index1, + size_t line_index2, size_t byte_index2, int copy_to_buffer) { (void)selected; /* FIXME */ if (copy_to_buffer && !paste_buffer) paste_buffer = txtbuf_new(); txtbuf *buf = copy_to_buffer ? paste_buffer : txtbuf_new(); ledit_range cur_range, del_range; - cur_range.line1 = buffer->cur_line; - cur_range.byte1 = buffer->cur_index; + cur_range.line1 = view->cur_line; + cur_range.byte1 = view->cur_index; enum delete_mode delmode = DELETE_CHAR; if (line_based) { if (hard_line_based) @@ -411,17 +414,17 @@ delete_range( else delmode = DELETE_SOFTLINE; } - ledit_buffer_delete_range( - buffer, delmode, + view_delete_range( + view, delmode, line_index1, byte_index1, line_index2, byte_index2, - &buffer->cur_line, &buffer->cur_index, + &view->cur_line, &view->cur_index, &del_range, buf ); - cur_range.line2 = buffer->cur_line; - cur_range.byte2 = buffer->cur_index; + cur_range.line2 = view->cur_line; + cur_range.byte2 = view->cur_index; ledit_push_undo_delete( - buffer->undo, buf, del_range, cur_range, 1, buffer->common->mode + view->buffer->undo, buf, del_range, cur_range, 1, view->mode ); if (!copy_to_buffer) txtbuf_destroy(buf); @@ -429,214 +432,214 @@ delete_range( static void insert_text( - ledit_buffer *buffer, - int line, int index, - char *text, int len, - int cur_line1, int cur_index1, - int cur_line2, int cur_index2, int start_group) { - if (len < 0) - len = strlen(text); + ledit_view *view, + size_t line, size_t index, + char *text, size_t len, + size_t cur_line1, size_t cur_index1, + size_t cur_line2, size_t cur_index2, + int set_range_start, int set_range_end, int start_group) { txtbuf ins_buf = {.text = text, .len = len, .cap = len}; ledit_range cur_range, del_range; - if (cur_line1 >= 0 && cur_index1 >= 0) { + if (set_range_start) { cur_range.line1 = cur_line1; cur_range.byte1 = cur_index1; } else { - cur_range.line1 = buffer->cur_line; - cur_range.byte1 = buffer->cur_index; + cur_range.line1 = view->cur_line; + cur_range.byte1 = view->cur_index; } del_range.line1 = line; del_range.byte1 = index; - int cur_line, cur_index; + size_t cur_line, cur_index; ledit_buffer_insert_text_with_newlines( - buffer, line, index, text, len, + view->buffer, line, index, text, len, &cur_line, &cur_index ); /* this is mainly for pasting, where the new line and index should not be at the end of the pasted text */ - if (cur_line2 >= 0 && cur_index2 >= 0) { - cur_range.line2 = buffer->cur_line = cur_line2; - cur_range.byte2 = buffer->cur_index = cur_index2; + if (set_range_end) { + cur_range.line2 = view->cur_line = cur_line2; + cur_range.byte2 = view->cur_index = cur_index2; } else { - cur_range.line2 = buffer->cur_line = cur_line; - cur_range.byte2 = buffer->cur_index = cur_index; + cur_range.line2 = view->cur_line = cur_line; + cur_range.byte2 = view->cur_index = cur_index; } del_range.line2 = cur_line; del_range.byte2 = cur_index; ledit_push_undo_insert( - buffer->undo, &ins_buf, del_range, cur_range, start_group, buffer->common->mode + view->buffer->undo, &ins_buf, del_range, cur_range, start_group, view->mode ); } static int -delete_selection(ledit_buffer *buffer) { - if (buffer->sel.line1 != buffer->sel.line2 || buffer->sel.byte1 != buffer->sel.byte2) { +delete_selection(ledit_view *view) { + if (view->sel_valid && (view->sel.line1 != view->sel.line2 || view->sel.byte1 != view->sel.byte2)) { delete_range( - buffer, 0, 0, - buffer->sel.line1, buffer->sel.byte1, - buffer->sel.line2, buffer->sel.byte2, 1 + view, 0, 0, + view->sel.line1, view->sel.byte1, + view->sel.line2, view->sel.byte2, 1 ); paste_buffer_line_based = 0; - /* FIXME: maybe just set this to the current cursor pos? */ - buffer->sel.line1 = buffer->sel.line2 = -1; - buffer->sel.byte1 = buffer->sel.byte2 = -1; - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + view->sel_valid = 0; + view->sel.line1 = view->sel.line2 = 0; + view->sel.byte1 = view->sel.byte2 = 0; + view_wipe_line_cursor_attrs(view, view->cur_line); return 1; } return 0; } static struct action -delete_chars_forwards(ledit_buffer *buffer, char *text, int len) { +delete_chars_forwards(ledit_view *view, char *text, size_t len) { (void)text; (void)len; int num = get_key_repeat(); if (num == -1) { - ledit_window_show_message(buffer->window, "Invalid key", -1); + ledit_window_show_message(view->window, "Invalid key", -1); return (struct action){ACTION_NONE, NULL}; } else if (num == 0) { num = 1; } - int end_index = ledit_buffer_next_cursor_pos( - buffer, buffer->cur_line, buffer->cur_index, num + size_t end_index = view_next_cursor_pos( + view, view->cur_line, view->cur_index, num ); delete_range( - buffer, 0, 0, - buffer->cur_line, buffer->cur_index, - buffer->cur_line, end_index, 1 + view, 0, 0, + view->cur_line, view->cur_index, + view->cur_line, end_index, 1 ); paste_buffer_line_based = 0; - buffer->cur_index = ledit_buffer_get_legal_normal_pos( - buffer, buffer->cur_line, buffer->cur_index + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, view->cur_index ); - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); return (struct action){ACTION_NONE, NULL}; } static struct action -delete_chars_backwards(ledit_buffer *buffer, char *text, int len) { +delete_chars_backwards(ledit_view *view, char *text, size_t len) { (void)text; (void)len; int num = get_key_repeat(); if (num == -1) { - ledit_window_show_message(buffer->window, "Invalid key", -1); + ledit_window_show_message(view->window, "Invalid key", -1); return (struct action){ACTION_NONE, NULL}; } else if (num == 0) { num = 1; } - int start_index = ledit_buffer_prev_cursor_pos( - buffer, buffer->cur_line, buffer->cur_index, num + size_t start_index = view_prev_cursor_pos( + view, view->cur_line, view->cur_index, num ); delete_range( - buffer, 0, 0, - buffer->cur_line, start_index, - buffer->cur_line, buffer->cur_index, 1 + view, 0, 0, + view->cur_line, start_index, + view->cur_line, view->cur_index, 1 ); paste_buffer_line_based = 0; /* I guess this is technically unnecessary since only text before the current position is deleted */ - buffer->cur_index = ledit_buffer_get_legal_normal_pos( - buffer, buffer->cur_line, start_index + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, start_index ); - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); return (struct action){ACTION_NONE, NULL}; } /* used to set cursor - I guess this is sort of a hack */ static void -push_undo_empty_insert(ledit_buffer *buffer, int line, int index, int start_group) { +push_undo_empty_insert(ledit_view *view, size_t line, size_t index, int start_group) { txtbuf ins_buf = {.text = "", .len = 0, .cap = 0}; ledit_range ins_range = {.line1 = line, .byte1 = index, .line2 = line, .byte2 = index}; ledit_range cur_range = {.line1 = line, .byte1 = index, .line2 = line, .byte2 = index}; ledit_push_undo_insert( - buffer->undo, &ins_buf, ins_range, cur_range, start_group, buffer->common->mode + view->buffer->undo, &ins_buf, ins_range, cur_range, start_group, view->mode ); } static struct action -append_line_above(ledit_buffer *buffer, char *text, int len) { - int start, end; +append_line_above(ledit_view *view, char *text, size_t len) { + size_t start, end; /* do this here already so the mode group is the same for the newline insertion */ - enter_insert(buffer, text, len); - ledit_buffer_get_pos_softline_bounds(buffer, buffer->cur_line, buffer->cur_index, &start, &end); + enter_insert(view, text, len); + view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end); if (hard_line_based || start == 0) { - insert_text(buffer, buffer->cur_line, 0, "\n", -1, -1, -1, buffer->cur_line, 0, 1); + insert_text(view, view->cur_line, 0, "\n", 1, 0, 0, view->cur_line, 0, 0, 1, 1); } else { - insert_text(buffer, buffer->cur_line, start, "\n\n", -1, -1, -1, buffer->cur_line + 1, 0, 1); + /* FIXME: this interface really is horrible */ + insert_text(view, view->cur_line, start, "\n\n", 2, 0, 0, view->cur_line + 1, 0, 0, 1, 1); } return (struct action){ACTION_NONE, NULL}; } static struct action -append_line_below(ledit_buffer *buffer, char *text, int len) { - int start, end; - enter_insert(buffer, text, len); - ledit_buffer_get_pos_softline_bounds(buffer, buffer->cur_line, buffer->cur_index, &start, &end); - ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); +append_line_below(ledit_view *view, char *text, size_t len) { + size_t start, end; + enter_insert(view, text, len); + view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end); + ledit_line *ll = ledit_buffer_get_line(view->buffer, view->cur_line); if (hard_line_based || end == ll->len) { - insert_text(buffer, buffer->cur_line, ll->len, "\n", -1, -1, -1, buffer->cur_line + 1, 0, 1); + insert_text(view, view->cur_line, ll->len, "\n", 1, 0, 0, view->cur_line + 1, 0, 0, 1, 1); } else { - insert_text(buffer, buffer->cur_line, end, "\n\n", -1, -1, -1, buffer->cur_line + 1, 0, 1); + insert_text(view, view->cur_line, end, "\n\n", 2, 0, 0, view->cur_line + 1, 0, 0, 1, 1); } return (struct action){ACTION_NONE, NULL}; } static struct action -append_after_cursor(ledit_buffer *buffer, char *text, int len) { - enter_insert(buffer, text, len); +append_after_cursor(ledit_view *view, char *text, size_t len) { + enter_insert(view, text, len); /* make cursor jump back to original position on undo */ - push_undo_empty_insert(buffer, buffer->cur_line, buffer->cur_index, 1); - buffer->cur_index = ledit_buffer_next_cursor_pos( - buffer, buffer->cur_line, buffer->cur_index, 1 + push_undo_empty_insert(view, view->cur_line, view->cur_index, 1); + view->cur_index = view_next_cursor_pos( + view, view->cur_line, view->cur_index, 1 ); return (struct action){ACTION_NONE, NULL}; } static struct action -append_after_eol(ledit_buffer *buffer, char *text, int len) { - int start, end; - enter_insert(buffer, text, len); - ledit_buffer_get_pos_softline_bounds(buffer, buffer->cur_line, buffer->cur_index, &start, &end); +append_after_eol(ledit_view *view, char *text, size_t len) { + size_t start, end; + enter_insert(view, text, len); + view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end); /* make cursor jump back to original position on undo */ - push_undo_empty_insert(buffer, buffer->cur_line, buffer->cur_index, 1); - ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); + push_undo_empty_insert(view, view->cur_line, view->cur_index, 1); + ledit_line *ll = ledit_buffer_get_line(view->buffer, view->cur_line); if (hard_line_based) - buffer->cur_index = ll->len; + view->cur_index = ll->len; else - buffer->cur_index = end; + view->cur_index = end; return (struct action){ACTION_NONE, NULL}; } static struct action -move_to_line(ledit_buffer *buffer, char *text, int len) { +move_to_line(ledit_view *view, char *text, size_t len) { (void)text; (void)len; motion_callback cb = NULL; int repeat = get_key_repeat_and_motion_cb(&cb); - int line; + size_t line; if (repeat > 0) - line = repeat > buffer->lines_num ? buffer->lines_num : repeat; + line = (size_t)repeat > view->lines_num ? view->lines_num : (size_t)repeat; else if (repeat == 0) - line = buffer->lines_num; + line = view->lines_num; else - return err_invalid_key(buffer); + return err_invalid_key(view); if (cb != NULL) { - cb(buffer, line - 1, 0, KEY_MOTION_LINE); + cb(view, line - 1, 0, KEY_MOTION_LINE); } else { - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); - buffer->cur_line = line - 1; - buffer->cur_index = 0; + view_wipe_line_cursor_attrs(view, view->cur_line); + view->cur_line = line - 1; + view->cur_index = 0; int text_w, text_h; - ledit_window_get_textview_size(buffer->window, &text_w, &text_h); - ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); + ledit_window_get_textview_size(view->window, &text_w, &text_h); + ledit_view_line *vl = view_get_line(view, view->cur_line); int x, y, h; - ledit_buffer_get_cursor_pixel_pos(buffer, buffer->cur_line, 0, &x, &y, &h); + view_get_cursor_pixel_pos(view, view->cur_line, 0, &x, &y, &h); /* if cursor is not on screen anymore, move to middle of screen */ - if (ll->y_offset < buffer->display_offset || - ll->y_offset + h > buffer->display_offset + text_h) { - ledit_buffer_scroll(buffer, ll->y_offset - text_h / 2); + if (vl->y_offset < view->display_offset || + vl->y_offset + h > view->display_offset + text_h) { + view_scroll(view, vl->y_offset - text_h / 2); } - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); discard_repetition_stack(); } return (struct action){ACTION_NONE, NULL}; @@ -644,117 +647,117 @@ move_to_line(ledit_buffer *buffer, char *text, int len) { /* FIXME: should these scrolling functions change behavior when hard_line_based == 1? */ static void -scroll_lines(ledit_buffer *buffer, int lines, int dir) { +scroll_lines(ledit_view *view, int lines, int dir) { if (last_lines_scrolled <= 0 && lines <= 0) { /* no scroll command yet - scroll half of screen */ - move_half_screen(buffer, dir); + move_half_screen(view, dir); } else { int x, y, h, sli; int final_lines, text_w, text_h; - 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); + ledit_view_line *vl = view_get_line(view, view->cur_line); + view_get_cursor_pixel_pos(view, view->cur_line, view->cur_index, &x, &y, &h); /* get the middle position of char */ - 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); + ledit_pos_to_x_softline(view, view->cur_line, view->cur_index, &x, &sli); + long abs_pos = vl->y_offset + y; + ledit_window_get_textview_size(view->window, &text_w, &text_h); if (lines > 0) final_lines = last_lines_scrolled = lines; else final_lines = last_lines_scrolled; - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + view_wipe_line_cursor_attrs(view, view->cur_line); get_new_line_softline( - buffer, buffer->cur_line, buffer->cur_index, + view, view->cur_line, view->cur_index, dir < 0 ? -final_lines : final_lines, - &buffer->cur_line, &sli + &view->cur_line, &sli ); - 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(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)); - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + size_t start, end; + view_get_softline_bounds(view, view->cur_line, sli, &start, &end); + vl = view_get_line(view, view->cur_line); + ledit_x_softline_to_pos(view, view->cur_line, x, sli, &view->cur_index); + view_get_cursor_pixel_pos(view, view->cur_line, view->cur_index, &x, &y, &h); + long new_abs_pos = vl->y_offset + y; + view_scroll(view, view->display_offset + (new_abs_pos - abs_pos)); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } } static struct action -scroll_lines_up(ledit_buffer *buffer, char *text, int len) { +scroll_lines_up(ledit_view *view, char *text, size_t len) { (void)text; (void)len; int repeat = get_key_repeat(); if (repeat >= 0) - scroll_lines(buffer, repeat, -1); + scroll_lines(view, repeat, -1); else - ledit_window_show_message(buffer->window, "Invalid key", -1); + ledit_window_show_message(view->window, "Invalid key", -1); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static struct action -scroll_lines_down(ledit_buffer *buffer, char *text, int len) { +scroll_lines_down(ledit_view *view, char *text, size_t len) { (void)text; (void)len; int repeat = get_key_repeat(); if (repeat >= 0) - scroll_lines(buffer, repeat, 1); + scroll_lines(view, repeat, 1); else - ledit_window_show_message(buffer->window, "Invalid key", -1); + ledit_window_show_message(view->window, "Invalid key", -1); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static void -scroll_with_cursor(ledit_buffer *buffer, int movement) { +scroll_with_cursor(ledit_view *view, int movement) { int x, y, h; - ledit_buffer_get_cursor_pixel_pos(buffer, buffer->cur_line, buffer->cur_index, &x, &y, &h); - int pix_movement = movement * h; - ledit_buffer_scroll(buffer, buffer->display_offset + pix_movement); - int old_line = buffer->cur_line; - int old_index = buffer->cur_index; - ledit_buffer_get_nearest_legal_pos( - buffer, old_line, old_index, - &buffer->cur_line, &buffer->cur_index + view_get_cursor_pixel_pos(view, view->cur_line, view->cur_index, &x, &y, &h); + int pix_movement = movement * h; /* FIXME: overflow */ + view_scroll(view, view->display_offset + pix_movement); + size_t old_line = view->cur_line; + size_t old_index = view->cur_index; + view_get_nearest_legal_pos( + view, old_line, old_index, + &view->cur_line, &view->cur_index ); - if (old_line != buffer->cur_line || old_index != buffer->cur_index) { - ledit_buffer_wipe_line_cursor_attrs(buffer, old_line); + if (old_line != view->cur_line || old_index != view->cur_index) { + view_wipe_line_cursor_attrs(view, old_line); /* if cursor is at top or bottom of screen, snap it to the very edge to avoid it looking weird */ if (movement > 0) { - ledit_buffer_scroll_to_pos_top( - buffer, buffer->cur_line, buffer->cur_index + view_scroll_to_pos_top( + view, view->cur_line, view->cur_index ); } else { - ledit_buffer_scroll_to_pos_bottom( - buffer, buffer->cur_line, buffer->cur_index + view_scroll_to_pos_bottom( + view, view->cur_line, view->cur_index ); } } - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } static struct action -scroll_with_cursor_up(ledit_buffer *buffer, char *text, int len) { +scroll_with_cursor_up(ledit_view *view, char *text, size_t len) { (void)text; (void)len; int repeat = get_key_repeat(); if (repeat >= 0) - scroll_with_cursor(buffer, -(repeat == 0 ? 1 : repeat)); + scroll_with_cursor(view, -(repeat == 0 ? 1 : repeat)); else - ledit_window_show_message(buffer->window, "Invalid key", -1); + ledit_window_show_message(view->window, "Invalid key", -1); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static struct action -scroll_with_cursor_down(ledit_buffer *buffer, char *text, int len) { +scroll_with_cursor_down(ledit_view *view, char *text, size_t len) { (void)text; (void)len; int repeat = get_key_repeat(); if (repeat >= 0) - scroll_with_cursor(buffer, repeat == 0 ? 1 : repeat); + scroll_with_cursor(view, repeat == 0 ? 1 : repeat); else - ledit_window_show_message(buffer->window, "Invalid key", -1); + ledit_window_show_message(view->window, "Invalid key", -1); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } @@ -765,160 +768,161 @@ scroll_with_cursor_down(ledit_buffer *buffer, char *text, int len) { (unless the screen is already at the very top or bottom - then it is the other way around) */ /* FIXME: this is a bit weird at the moment */ static void -move_half_screen(ledit_buffer *buffer, int movement) { +move_half_screen(ledit_view *view, int movement) { int w, h; - ledit_window_get_textview_size(buffer->window, &w, &h); + ledit_window_get_textview_size(view->window, &w, &h); /* FIXME: overflow */ int total = movement * h/2; - ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); + ledit_view_line *vl = view_get_line(view, view->cur_line); int cur_x, cur_y, cur_h; - ledit_buffer_get_cursor_pixel_pos( - buffer, buffer->cur_line, buffer->cur_index, &cur_x, &cur_y, &cur_h + view_get_cursor_pixel_pos( + view, view->cur_line, view->cur_index, &cur_x, &cur_y, &cur_h ); - long real_cur_y = ll->y_offset - buffer->display_offset + cur_y; + long real_cur_y = vl->y_offset - view->display_offset + cur_y; /* new pixel position of cursor */ /* Note: this usually causes at least part of a line of overlap because ensure_cursor_shown scrolls back a bit if the line isn't completely shown (this behavior could be changed using - ledit_buffer_get_nearest_legal_pos) */ + view_get_nearest_legal_pos) */ int y = movement > 0 ? 0 : h; int half_screen = abs(movement) % 2 == 1; if (half_screen) { /* if only half screens are moved and we are at the beginning or end, just move the cursor the movement amount instead of moving it to the very top or bottom */ - if (buffer->display_offset + total <= 0 || - buffer->display_offset + total + h >= buffer->total_height) { + if (view->display_offset + total <= 0 || + view->display_offset + total + h >= view->total_height) { y = real_cur_y + total; } } else { - if (buffer->display_offset + total <= 0) + if (view->display_offset + total <= 0) y = 0; - else if (buffer->display_offset + total + h > buffer->total_height) + else if (view->display_offset + total + h > view->total_height) y = h; } if (y < 0) y = 0; if (y > h) y = h; - ledit_buffer_scroll(buffer, buffer->display_offset + total); - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + view_scroll(view, view->display_offset + total); + view_wipe_line_cursor_attrs(view, view->cur_line); /* 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(buffer, buffer->cur_line, buffer->cur_index, &x, &softline); + ledit_pos_to_x_softline(view, view->cur_line, view->cur_index, &x, &softline); ledit_xy_to_line_byte( - buffer, x / PANGO_SCALE, y, 0, - &buffer->cur_line, &buffer->cur_index + view, x / PANGO_SCALE, y, 0, + &view->cur_line, &view->cur_index ); - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } static struct action -screen_up(ledit_buffer *buffer, char *text, int len) { +screen_up(ledit_view *view, char *text, size_t len) { (void)text; (void)len; int repeat = get_key_repeat(); if (repeat >= 0) - move_half_screen(buffer, -(repeat == 0 ? 2 : repeat*2)); + move_half_screen(view, -(repeat == 0 ? 2 : repeat*2)); else - ledit_window_show_message(buffer->window, "Invalid key", -1); + ledit_window_show_message(view->window, "Invalid key", -1); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static struct action -screen_down(ledit_buffer *buffer, char *text, int len) { +screen_down(ledit_view *view, char *text, size_t len) { (void)text; (void)len; int repeat = get_key_repeat(); if (repeat >= 0) - move_half_screen(buffer, repeat == 0 ? 2 : repeat*2); + move_half_screen(view, repeat == 0 ? 2 : repeat*2); else - ledit_window_show_message(buffer->window, "Invalid key", -1); + ledit_window_show_message(view->window, "Invalid key", -1); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static struct action -delete_to_eol(ledit_buffer *buffer, char *text, int len) { +delete_to_eol(ledit_view *view, char *text, size_t len) { (void)text; (void)len; if (!key_stack_empty()) - return err_invalid_key(buffer); - int start, end; - ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); + return err_invalid_key(view); + size_t start, end; + ledit_line *ll = ledit_buffer_get_line(view->buffer, view->cur_line); if (hard_line_based) { end = ll->len; } else { - ledit_buffer_get_pos_softline_bounds(buffer, buffer->cur_line, buffer->cur_index, &start, &end); + view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end); } delete_range( - buffer, 0, 0, - buffer->cur_line, buffer->cur_index, - buffer->cur_line, end, 1 + view, 0, 0, + view->cur_line, view->cur_index, + view->cur_line, end, 1 ); paste_buffer_line_based = 0; - buffer->cur_index = ledit_buffer_get_legal_normal_pos( - buffer, buffer->cur_line, buffer->cur_index + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, view->cur_index ); - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); return (struct action){ACTION_NONE, NULL}; } static struct action -change_to_eol(ledit_buffer *buffer, char *text, int len) { +change_to_eol(ledit_view *view, char *text, size_t len) { (void)text; (void)len; if (!key_stack_empty()) - return err_invalid_key(buffer); - ledit_buffer_set_mode(buffer, INSERT); - int start, end; - ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); + return err_invalid_key(view); + view_set_mode(view, INSERT); + size_t start, end; + ledit_line *ll = ledit_buffer_get_line(view->buffer, view->cur_line); if (hard_line_based) { end = ll->len; } else { - ledit_buffer_get_pos_softline_bounds(buffer, buffer->cur_line, buffer->cur_index, &start, &end); + view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end); } delete_range( - buffer, 0, 0, - buffer->cur_line, buffer->cur_index, - buffer->cur_line, end, 1 + view, 0, 0, + view->cur_line, view->cur_index, + view->cur_line, end, 1 ); paste_buffer_line_based = 0; - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + view_wipe_line_cursor_attrs(view, view->cur_line); return (struct action){ACTION_NONE, NULL}; } /* FIXME: clear selection on most commands */ /* FIXME: don't include escape when repeating change with '.'? */ static struct action -change(ledit_buffer *buffer, char *text, int len) { +change(ledit_view *view, char *text, size_t len) { (void)text; (void)len; motion_callback cb = NULL; int num = get_key_repeat_and_motion_cb(&cb); if (num == -1) - return err_invalid_key(buffer); - if (buffer->common->mode == VISUAL) { - ledit_buffer_set_mode(buffer, INSERT); - delete_selection(buffer); - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + return err_invalid_key(view); + if (view->mode == VISUAL) { + view_set_mode(view, INSERT); + delete_selection(view); + view_wipe_line_cursor_attrs(view, view->cur_line); clear_key_stack(); } else { if (cb == &change_cb) { int lines = num > 0 ? num : 1; - int new_line, new_softline; + size_t new_line; + int new_softline; get_new_line_softline( - buffer, buffer->cur_line, buffer->cur_index, + view, view->cur_line, view->cur_index, lines - 1, &new_line, &new_softline ); - int start, end; - ledit_buffer_get_softline_bounds(buffer, new_line, new_softline, &start, &end); - cb(buffer, new_line, start, KEY_MOTION_LINE); + size_t start, end; + view_get_softline_bounds(view, new_line, new_softline, &start, &end); + cb(view, new_line, start, KEY_MOTION_LINE); clear_key_stack(); } else if (cb != NULL) { - return err_invalid_key(buffer); + return err_invalid_key(view); } else { struct key_stack_elem *e = push_key_stack(); e->key = KEY_MOTION; /* ? */ @@ -930,55 +934,55 @@ change(ledit_buffer *buffer, char *text, int len) { } static void -change_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type) { +change_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) { /* set mode first so the deletion is included in the undo group */ - ledit_buffer_set_mode(buffer, INSERT); + view_set_mode(view, INSERT); int line_based = type == KEY_MOTION_LINE ? 1 : 0; /* this hackery is needed to avoid deleting the entire last line and instead leave an empty line - this should be made nicer (FIXME) */ - int pos1 = buffer->cur_index, pos2 = char_pos; + size_t pos1 = view->cur_index, pos2 = char_pos; if (line_based && !hard_line_based) { - int pos1, pos2, tmp; - ledit_buffer_get_pos_softline_bounds(buffer, buffer->cur_line, buffer->cur_index, &pos1, &tmp); - ledit_buffer_get_pos_softline_bounds(buffer, line, char_pos, &tmp, &pos2); + size_t pos1, pos2, tmp; + view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &pos1, &tmp); + view_get_pos_softline_bounds(view, line, char_pos, &tmp, &pos2); } else if (line_based && hard_line_based) { pos1 = 0; - ledit_line *ll = ledit_buffer_get_line(buffer, line); + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); pos2 = ll->len; } /* force line_based to 0 (see comment about hackery above) */ delete_range( - buffer, 0, 0, - buffer->cur_line, pos1, + view, 0, 0, + view->cur_line, pos1, line, pos2, 1 ); paste_buffer_line_based = line_based; - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + view_wipe_line_cursor_attrs(view, view->cur_line); } static struct action -yank(ledit_buffer *buffer, char *text, int len) { +yank(ledit_view *view, char *text, size_t len) { (void)text; (void)len; if (!paste_buffer) paste_buffer = txtbuf_new(); - if (buffer->common->mode == VISUAL) { - ledit_buffer_sort_selection( - &buffer->sel.line1, &buffer->sel.byte1, &buffer->sel.line2, &buffer->sel.byte2 + if (view->mode == VISUAL) { + view_sort_selection( + &view->sel.line1, &view->sel.byte1, &view->sel.line2, &view->sel.byte2 ); ledit_buffer_copy_text_to_txtbuf( - buffer, paste_buffer, - buffer->sel.line1, buffer->sel.byte1, buffer->sel.line2, buffer->sel.byte2 + view->buffer, paste_buffer, + view->sel.line1, view->sel.byte1, view->sel.line2, view->sel.byte2 ); paste_buffer_line_based = 0; - buffer->cur_line = buffer->sel.line1; - buffer->cur_index = buffer->sel.byte1; - ledit_buffer_set_selection(buffer, -1, -1, -1, -1); - ledit_buffer_set_mode(buffer, NORMAL); - buffer->cur_index = ledit_buffer_get_legal_normal_pos( - buffer, buffer->cur_line, buffer->cur_index + view->cur_line = view->sel.line1; + view->cur_index = view->sel.byte1; + view_wipe_selection(view); + view_set_mode(view, NORMAL); + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, view->cur_index ); - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); clear_key_stack(); } else { motion_callback cb = NULL; @@ -986,14 +990,15 @@ yank(ledit_buffer *buffer, char *text, int len) { if (num == 0) num = 1; if (cb == &yank_cb) { - int new_line, new_softline; + size_t new_line; + int new_softline; get_new_line_softline( - buffer, buffer->cur_line, buffer->cur_index, + view, view->cur_line, view->cur_index, num - 1, &new_line, &new_softline ); - int start, end; - ledit_buffer_get_softline_bounds(buffer, new_line, new_softline, &start, &end); - cb(buffer, new_line, start, KEY_MOTION_LINE); + size_t start, end; + view_get_softline_bounds(view, new_line, new_softline, &start, &end); + cb(view, new_line, start, KEY_MOTION_LINE); clear_key_stack(); } else if (cb == NULL) { struct key_stack_elem *e = push_key_stack(); @@ -1009,61 +1014,62 @@ yank(ledit_buffer *buffer, char *text, int len) { } static struct action -yank_lines(ledit_buffer *buffer, char *text, int len) { +yank_lines(ledit_view *view, char *text, size_t len) { (void)text; (void)len; int num = get_key_repeat(); if (num == -1) - return err_invalid_key(buffer); + return err_invalid_key(view); else if (num == 0) num = 1; - int new_line, new_softline; + size_t new_line; + int new_softline; get_new_line_softline( - buffer, buffer->cur_line, buffer->cur_index, + view, view->cur_line, view->cur_index, num - 1, &new_line, &new_softline ); - int start, end; - ledit_buffer_get_softline_bounds(buffer, new_line, new_softline, &start, &end); - yank_cb(buffer, new_line, start, KEY_MOTION_LINE); + size_t start, end; + view_get_softline_bounds(view, new_line, new_softline, &start, &end); + yank_cb(view, new_line, start, KEY_MOTION_LINE); clear_key_stack(); return (struct action){ACTION_NONE, NULL}; } static void -yank_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type) { +yank_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) { int line_based = type == KEY_MOTION_LINE ? 1 : 0; - int l1 = buffer->cur_line, l2 = line, b1 = buffer->cur_index, b2 = char_pos; + size_t l1 = view->cur_line, l2 = line, b1 = view->cur_index, b2 = char_pos; if (!paste_buffer) paste_buffer = txtbuf_new(); if (l2 < l1 || (l1 == l2 && b2 < b1)) { - swap(&l1, &l2); - swap(&b1, &b2); + swap_sz(&l1, &l2); + swap_sz(&b1, &b2); } if (line_based && !hard_line_based) { - int start1, end2, tmp; - ledit_buffer_get_pos_softline_bounds(buffer, l1, b1, &start1, &tmp); - ledit_buffer_get_pos_softline_bounds(buffer, l2, b2, &tmp, &end2); - ledit_line *ll = ledit_buffer_get_line(buffer, l2); - if (end2 == ll->len && l2 < buffer->lines_num - 1) { + size_t start1, end2, tmp; + view_get_pos_softline_bounds(view, l1, b1, &start1, &tmp); + view_get_pos_softline_bounds(view, l2, b2, &tmp, &end2); + ledit_line *ll = ledit_buffer_get_line(view->buffer, l2); + if (end2 == ll->len && l2 < view->lines_num - 1) { l2++; end2 = 0; } ledit_buffer_copy_text_to_txtbuf( - buffer, paste_buffer, l1, start1, l2, end2 + view->buffer, paste_buffer, l1, start1, l2, end2 ); } else if (line_based && hard_line_based) { - ledit_line *ll = ledit_buffer_get_line(buffer, l2); - int end = ll->len; - if (l2 < buffer->lines_num - 1) { + ledit_line *ll = ledit_buffer_get_line(view->buffer, l2); + size_t end = ll->len; + if (l2 < view->lines_num - 1) { l2++; end = 0; } ledit_buffer_copy_text_to_txtbuf( - buffer, paste_buffer, l1, 0, l2, end + view->buffer, paste_buffer, l1, 0, l2, end ); } else { ledit_buffer_copy_text_to_txtbuf( - buffer, paste_buffer, l1, b1, l2, b2 + view->buffer, paste_buffer, l1, b1, l2, b2 ); } paste_buffer_line_based = line_based; @@ -1071,33 +1077,34 @@ yank_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type) { } static struct action -delete(ledit_buffer *buffer, char *text, int len) { +delete(ledit_view *view, char *text, size_t len) { (void)text; (void)len; motion_callback cb = NULL; int num = get_key_repeat_and_motion_cb(&cb); if (num == -1) - return err_invalid_key(buffer); - if (delete_selection(buffer)) { - ledit_buffer_set_mode(buffer, NORMAL); - buffer->cur_index = ledit_buffer_get_legal_normal_pos(buffer, buffer->cur_line, buffer->cur_index); - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + return err_invalid_key(view); + if (delete_selection(view)) { + view_set_mode(view, NORMAL); + view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); clear_key_stack(); } else { /* FIXME: checking equality of the function pointer may be a bit risky */ if (cb == &delete_cb) { int lines = num > 0 ? num : 1; - int new_line, new_softline; + size_t new_line; + int new_softline; get_new_line_softline( - buffer, buffer->cur_line, buffer->cur_index, + view, view->cur_line, view->cur_index, lines - 1, &new_line, &new_softline ); - int start, end; - ledit_buffer_get_softline_bounds(buffer, new_line, new_softline, &start, &end); - cb(buffer, new_line, start, KEY_MOTION_LINE); + size_t start, end; + view_get_softline_bounds(view, new_line, new_softline, &start, &end); + cb(view, new_line, start, KEY_MOTION_LINE); clear_key_stack(); } else if (cb != NULL) { - return err_invalid_key(buffer); + return err_invalid_key(view); } else { struct key_stack_elem *e = push_key_stack(); e->key = KEY_MOTION; /* ? */ @@ -1110,15 +1117,15 @@ delete(ledit_buffer *buffer, char *text, int len) { /* FIXME: should this get number of lines to remove or actual end line? */ static void -delete_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type) { +delete_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) { int line_based = type == KEY_MOTION_LINE ? 1 : 0; delete_range( - buffer, line_based, 0, - buffer->cur_line, buffer->cur_index, + view, line_based, 0, + view->cur_line, view->cur_index, line, char_pos, 1 ); paste_buffer_line_based = line_based; - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); finalize_repetition_stack(); } @@ -1127,30 +1134,30 @@ delete_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type) { hard lines, which may be unexpected, but the alternatives I could think of are even weirder */ static struct action -paste_normal(ledit_buffer *buffer, char *text, int len) { +paste_normal(ledit_view *view, char *text, size_t len) { (void)text; (void)len; if (!paste_buffer) { - ledit_window_show_message(buffer->window, "Nothing to paste", -1); + ledit_window_show_message(view->window, "Nothing to paste", -1); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } if (paste_buffer_line_based) { - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); - ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); - int brk = 0; + view_wipe_line_cursor_attrs(view, view->cur_line); + ledit_line *ll = ledit_buffer_get_line(view->buffer, view->cur_line); + size_t brk = 0; if (hard_line_based) { brk = ll->len; } else { - int tmp; - ledit_buffer_get_pos_softline_bounds(buffer, buffer->cur_line, buffer->cur_index, &tmp, &brk); + size_t tmp; + view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &tmp, &brk); } insert_text( - buffer, buffer->cur_line, brk, - "\n", -1, -1, -1, buffer->cur_line, buffer->cur_index, 1 + view, view->cur_line, brk, + "\n", 1, 0, 0, view->cur_line, view->cur_index, 0, 1, 1 ); - int text_len = paste_buffer->len; - ll = ledit_buffer_get_line(buffer, buffer->cur_line + 1); + size_t text_len = paste_buffer->len; + ll = ledit_buffer_get_line(view->buffer, view->cur_line + 1); if (ll->len == 0 && paste_buffer->text[text_len-1] == '\n') { /* remove trailing newline if it exists and text is already on own line */ text_len--; @@ -1158,79 +1165,80 @@ paste_normal(ledit_buffer *buffer, char *text, int len) { } else if (ll->len > 0 && paste_buffer->text[text_len-1] != '\n') { /* ensure pasted text is on its own hard line */ insert_text( - buffer, buffer->cur_line + 1, 0, - "\n", -1, -1, -1, buffer->cur_line, buffer->cur_index, 0 + view, view->cur_line + 1, 0, + "\n", 1, 0, 0, view->cur_line, view->cur_index, 0, 1, 0 ); } insert_text( - buffer, buffer->cur_line + 1, 0, - paste_buffer->text, text_len, -1, -1, buffer->cur_line + 1, 0, 0 + view, view->cur_line + 1, 0, + paste_buffer->text, text_len, 0, 0, view->cur_line + 1, 0, 0, 1, 0 ); } else { - int old_line = buffer->cur_line; - int old_index = buffer->cur_index; + size_t old_line = view->cur_line; + size_t old_index = view->cur_index; /* must allow illegal index so text can be pasted at end of line */ - move_cursor_logically(buffer, 1, 1); + move_cursor_logically(view, 1, 1); insert_text( - buffer, buffer->cur_line, buffer->cur_index, + view, view->cur_line, view->cur_index, paste_buffer->text, paste_buffer->len, - old_line, old_index, buffer->cur_line, buffer->cur_index, 1 + old_line, old_index, view->cur_line, view->cur_index, 1, 1, 1 ); } - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static struct action -paste_normal_backwards(ledit_buffer *buffer, char *text, int len) { +paste_normal_backwards(ledit_view *view, char *text, size_t len) { (void)text; (void)len; if (!paste_buffer) { - ledit_window_show_message(buffer->window, "Nothing to paste", -1); + ledit_window_show_message(view->window, "Nothing to paste", -1); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } if (paste_buffer_line_based) { - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); - ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); - int brk = 0; + view_wipe_line_cursor_attrs(view, view->cur_line); + ledit_line *ll = ledit_buffer_get_line(view->buffer, view->cur_line); + size_t brk = 0; if (!hard_line_based) { - int tmp; - ledit_buffer_get_pos_softline_bounds(buffer, buffer->cur_line, buffer->cur_index, &brk, &tmp); + size_t tmp; + view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &brk, &tmp); } + /* FIXME: better interface without these weird int args */ insert_text( - buffer, buffer->cur_line, brk, - "\n", -1, -1, -1, buffer->cur_line, buffer->cur_index, 1 + view, view->cur_line, brk, + "\n", 1, 0, 0, view->cur_line, view->cur_index, 0, 1, 1 ); - int text_len = paste_buffer->len; - ll = ledit_buffer_get_line(buffer, buffer->cur_line); + size_t text_len = paste_buffer->len; + ll = ledit_buffer_get_line(view->buffer, view->cur_line); if (paste_buffer->text[text_len-1] == '\n') { /* remove trailing newline if it exists */ text_len--; paste_buffer->text[text_len] = '\0'; } - int new_line = buffer->cur_line; + size_t new_line = view->cur_line; if (ll->len > 0) { /* ensure pasted text is on its own hard line */ insert_text( - buffer, buffer->cur_line, ll->len, - "\n", -1, -1, -1, buffer->cur_line, buffer->cur_index, 0 + view, view->cur_line, ll->len, + "\n", 1, 0, 0, view->cur_line, view->cur_index, 0, 1, 0 ); - new_line = buffer->cur_line + 1; + new_line = view->cur_line + 1; } insert_text( - buffer, new_line, 0, - paste_buffer->text, text_len, -1, -1, new_line, 0, 0 + view, new_line, 0, + paste_buffer->text, text_len, 0, 0, new_line, 0, 0, 1, 0 ); } else { insert_text( - buffer, buffer->cur_line, buffer->cur_index, + view, view->cur_line, view->cur_index, paste_buffer->text, paste_buffer->len, - -1, -1, buffer->cur_line, buffer->cur_index, 1 + 0, 0, view->cur_line, view->cur_index, 0, 1, 1 ); } - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } @@ -1243,14 +1251,22 @@ push_num(int num) { e->key = KEY_NUMBER; e->followup = KEY_NUMBER|KEY_NUMBERALLOWED; } - /* FIXME: error (overflow) checking */ + /* FIXME: error messages */ + if (INT_MAX / 10 < e->count) { + clear_key_stack(); + return; + } e->count *= 10; + if (INT_MAX - num < e->count) { + clear_key_stack(); + return; + } e->count += num; } static struct action -push_0(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +push_0(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; push_num(0); @@ -1258,8 +1274,8 @@ push_0(ledit_buffer *buffer, char *text, int len) { } static struct action -push_1(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +push_1(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; push_num(1); @@ -1267,8 +1283,8 @@ push_1(ledit_buffer *buffer, char *text, int len) { } static struct action -push_2(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +push_2(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; push_num(2); @@ -1276,8 +1292,8 @@ push_2(ledit_buffer *buffer, char *text, int len) { } static struct action -push_3(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +push_3(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; push_num(3); @@ -1285,8 +1301,8 @@ push_3(ledit_buffer *buffer, char *text, int len) { } static struct action -push_4(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +push_4(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; push_num(4); @@ -1294,8 +1310,8 @@ push_4(ledit_buffer *buffer, char *text, int len) { } static struct action -push_5(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +push_5(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; push_num(5); @@ -1303,8 +1319,8 @@ push_5(ledit_buffer *buffer, char *text, int len) { } static struct action -push_6(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +push_6(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; push_num(6); @@ -1312,8 +1328,8 @@ push_6(ledit_buffer *buffer, char *text, int len) { } static struct action -push_7(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +push_7(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; push_num(7); @@ -1321,8 +1337,8 @@ push_7(ledit_buffer *buffer, char *text, int len) { } static struct action -push_8(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +push_8(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; push_num(8); @@ -1330,8 +1346,8 @@ push_8(ledit_buffer *buffer, char *text, int len) { } static struct action -push_9(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +push_9(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; push_num(9); @@ -1339,130 +1355,129 @@ push_9(ledit_buffer *buffer, char *text, int len) { } static struct action -backspace(ledit_buffer *buffer, char *text, int len) { +backspace(ledit_view *view, char *text, size_t len) { (void)text; (void)len; /* FIXME: don't copy to paste buffer on del_sel here; delete entire grapheme */ - if (delete_selection(buffer)) { + if (delete_selection(view)) { /* NOP */ - } else if (buffer->cur_index == 0) { - if (buffer->cur_line != 0) { - ledit_line *l1 = ledit_buffer_get_line(buffer, buffer->cur_line - 1); - delete_range(buffer, 0, 0, buffer->cur_line - 1, l1->len, buffer->cur_line, 0, 0); + } else if (view->cur_index == 0) { + if (view->cur_line != 0) { + ledit_line *l1 = ledit_buffer_get_line(view->buffer, view->cur_line - 1); + delete_range(view, 0, 0, view->cur_line - 1, l1->len, view->cur_line, 0, 0); } } else { - ledit_line *l = ledit_buffer_get_line(buffer, buffer->cur_line); - 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_line *l = ledit_buffer_get_line(view->buffer, view->cur_line); + int i = ledit_line_prev_utf8(l, view->cur_index); + delete_range(view, 0, 0, view->cur_line, view->cur_index, view->cur_line, i, 0); } - /* 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}; } static struct action -delete_key(ledit_buffer *buffer, char *text, int len) { +delete_key(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - ledit_line *cur_line = ledit_buffer_get_line(buffer, buffer->cur_line); - if (delete_selection(buffer)) { + ledit_line *cur_line = ledit_buffer_get_line(view->buffer, view->cur_line); + if (delete_selection(view)) { /* NOP */ - } else if (buffer->cur_index == cur_line->len) { - if (buffer->cur_line != buffer->lines_num - 1) { - delete_range(buffer, 0, 0, buffer->cur_line, cur_line->len, buffer->cur_line + 1, 0, 0); + } else if (view->cur_index == cur_line->len) { + if (view->cur_line != view->lines_num - 1) { + delete_range(view, 0, 0, view->cur_line, cur_line->len, view->cur_line + 1, 0, 0); } } else { - 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); + int i = ledit_line_next_utf8(cur_line, view->cur_index); + delete_range(view, 0, 0, view->cur_line, view->cur_index, view->cur_line, i, 0); } /* FIXME: This was probably a mistake earlier, right? - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);*/ + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);*/ return (struct action){ACTION_NONE, NULL}; } static struct action -move_to_eol(ledit_buffer *buffer, char *text, int len) { +move_to_eol(ledit_view *view, char *text, size_t len) { (void)text; (void)len; motion_callback cb; int num = get_key_repeat_and_motion_cb(&cb); if (num == -1) - return err_invalid_key(buffer); + return err_invalid_key(view); if (num == 0) num = 1; - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); - int new_line, new_softline; + view_wipe_line_cursor_attrs(view, view->cur_line); + size_t new_line; + int new_softline; get_new_line_softline( - buffer, buffer->cur_line, buffer->cur_index, num - 1, + view, view->cur_line, view->cur_index, num - 1, &new_line, &new_softline ); - ledit_line *ll = ledit_buffer_get_line(buffer, new_line); - int end_index = ll->len; + ledit_line *ll = ledit_buffer_get_line(view->buffer, new_line); + size_t end_index = ll->len; if (!hard_line_based) { - int tmp; - ledit_buffer_get_softline_bounds(buffer, new_line, new_softline, &tmp, &end_index); + size_t tmp; + view_get_softline_bounds(view, new_line, new_softline, &tmp, &end_index); } if (cb != NULL) { - cb(buffer, new_line, end_index, KEY_MOTION_CHAR); + cb(view, new_line, end_index, KEY_MOTION_CHAR); } else { - buffer->cur_line = new_line; - buffer->cur_index = end_index; - if (buffer->common->mode == VISUAL) { - ledit_buffer_set_selection( - buffer, - buffer->sel.line1, buffer->sel.byte1, + view->cur_line = new_line; + view->cur_index = end_index; + if (view->mode == VISUAL) { + view_set_selection( + view, + view->sel.line1, view->sel.byte1, new_line, end_index ); } else { /* FIXME: this is weird because the cursor is actually on the next soft line, but the alternative has too many weird effects with bidi text */ - buffer->cur_index = ledit_buffer_get_legal_normal_pos( - buffer, buffer->cur_line, buffer->cur_index + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, view->cur_index ); } - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } return (struct action){ACTION_NONE, NULL}; } #define GEN_WORD_MOVEMENT(name, func) \ static struct action \ -name(ledit_buffer *buffer, char *text, int len) { \ +name(ledit_view *view, char *text, size_t len) { \ (void)text; \ (void)len; \ motion_callback cb; \ int num = get_key_repeat_and_motion_cb(&cb); \ if (num == -1) \ - return err_invalid_key(buffer); \ + return err_invalid_key(view); \ if (num == 0) \ num = 1; \ - int new_line, new_index, new_real_index; \ + size_t new_line, new_index, new_real_index; \ func( \ - buffer, \ - buffer->cur_line, buffer->cur_index, num, \ + view, \ + view->cur_line, view->cur_index, num, \ &new_line, &new_index, &new_real_index \ ); \ if (cb != NULL) { \ - cb(buffer, new_line, new_real_index, KEY_MOTION_CHAR); \ + cb(view, new_line, new_real_index, KEY_MOTION_CHAR); \ } else { \ - if (buffer->common->mode == VISUAL) { \ - ledit_buffer_set_selection( \ - buffer, \ - buffer->sel.line1, buffer->sel.byte1, \ + if (view->mode == VISUAL) { \ + view_set_selection( \ + view, \ + view->sel.line1, view->sel.byte1, \ new_line, new_real_index \ ); \ - buffer->cur_line = new_line; \ - buffer->cur_index = new_real_index; \ + view->cur_line = new_line; \ + view->cur_index = new_real_index; \ } else { \ - if (new_line != buffer->cur_line) \ - ledit_buffer_wipe_line_cursor_attrs( \ - buffer, buffer->cur_line \ + if (new_line != view->cur_line) \ + view_wipe_line_cursor_attrs( \ + view, view->cur_line \ ); \ - buffer->cur_line = new_line; \ - buffer->cur_index = new_index; \ - ledit_buffer_set_line_cursor_attrs( \ - buffer, buffer->cur_line, buffer->cur_index \ + view->cur_line = new_line; \ + view->cur_index = new_index; \ + view_set_line_cursor_attrs( \ + view, view->cur_line, view->cur_index \ ); \ } \ discard_repetition_stack(); \ @@ -1471,33 +1486,33 @@ name(ledit_buffer *buffer, char *text, int len) { return (struct action){ACTION_NONE, NULL}; \ } -GEN_WORD_MOVEMENT(next_word, ledit_buffer_next_word) -GEN_WORD_MOVEMENT(next_word_end, ledit_buffer_next_word_end) -GEN_WORD_MOVEMENT(next_bigword, ledit_buffer_next_bigword) -GEN_WORD_MOVEMENT(next_bigword_end, ledit_buffer_next_bigword_end) -GEN_WORD_MOVEMENT(prev_word, ledit_buffer_prev_word) -GEN_WORD_MOVEMENT(prev_bigword, ledit_buffer_prev_bigword) +GEN_WORD_MOVEMENT(next_word, view_next_word) +GEN_WORD_MOVEMENT(next_word_end, view_next_word_end) +GEN_WORD_MOVEMENT(next_bigword, view_next_bigword) +GEN_WORD_MOVEMENT(next_bigword_end, view_next_bigword_end) +GEN_WORD_MOVEMENT(prev_word, view_prev_word) +GEN_WORD_MOVEMENT(prev_bigword, view_prev_bigword) static void -move_cursor_left_right(ledit_buffer *buffer, int dir, int allow_illegal_index) { +move_cursor_left_right(ledit_view *view, int dir, int allow_illegal_index) { motion_callback cb; int num = get_key_repeat_and_motion_cb(&cb); if (num == -1) - (void)err_invalid_key(buffer); + (void)err_invalid_key(view); if (num == 0) num = 1; - ledit_line *cur_line = ledit_buffer_get_line(buffer, buffer->cur_line); + ledit_line *cur_line = ledit_buffer_get_line(view->buffer, view->cur_line); /* FIXME: standardize interface - num * dir or separately? */ - int last_index; - int new_index = ledit_buffer_move_cursor_visually( - buffer, buffer->cur_line, buffer->cur_index, num * dir, &last_index + size_t last_index; + size_t new_index = view_move_cursor_visually( + view, view->cur_line, view->cur_index, num * dir, &last_index ); /* when in normal mode, the cursor cannot be at the very end of the line because it's always covering a character */ if (new_index >= cur_line->len) { if (!allow_illegal_index && - buffer->common->mode == NORMAL && cb == NULL) { + view->mode == NORMAL && cb == NULL) { new_index = last_index; } else { /* FIXME: I guess this is unnecessary */ @@ -1505,17 +1520,15 @@ move_cursor_left_right(ledit_buffer *buffer, int dir, int allow_illegal_index) { } } if (cb != NULL) { - cb(buffer, buffer->cur_line, new_index, KEY_MOTION_CHAR); + cb(view, view->cur_line, new_index, KEY_MOTION_CHAR); } else { - buffer->cur_index = new_index; - if (buffer->common->mode == VISUAL) { - ledit_buffer_set_selection(buffer, buffer->sel.line1, buffer->sel.byte1, buffer->sel.line2, new_index); - } else if (buffer->common->mode == INSERT && - (buffer->sel.line1 != buffer->sel.line2 || - buffer->sel.byte1 != buffer->sel.byte2)) { - ledit_buffer_set_selection(buffer, buffer->cur_line, new_index, buffer->cur_line, new_index); - } else if (buffer->common->mode == NORMAL) { - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view->cur_index = new_index; + if (view->mode == VISUAL) { + view_set_selection(view, view->sel.line1, view->sel.byte1, view->cur_line, new_index); + } else if (view->mode == INSERT && view->sel_valid) { + view_wipe_selection(view); + } else if (view->mode == NORMAL) { + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } discard_repetition_stack(); } @@ -1523,133 +1536,118 @@ move_cursor_left_right(ledit_buffer *buffer, int dir, int allow_illegal_index) { } static struct action -cursor_left(ledit_buffer *buffer, char *text, int len) { +cursor_left(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - move_cursor_left_right(buffer, -1, 0); + move_cursor_left_right(view, -1, 0); return (struct action){ACTION_NONE, NULL}; } static struct action -cursor_right(ledit_buffer *buffer, char *text, int len) { +cursor_right(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - move_cursor_left_right(buffer, 1, 0); + move_cursor_left_right(view, 1, 0); return (struct action){ACTION_NONE, NULL}; } static struct action -return_key(ledit_buffer *buffer, char *text, int len) { +return_key(ledit_view *view, char *text, size_t len) { (void)text; (void)len; int start_group = 1; - if (delete_selection(buffer)) + if (delete_selection(view)) start_group = 0; - insert_text(buffer, buffer->cur_line, buffer->cur_index, "\n", -1, -1, -1, -1, -1, start_group); - /* FIXME: these aren't needed, right? This only works in insert mode - * anyways, so there's nothing to wipe */ - /* ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); - buffer->cur_line++; - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); - buffer->cur_index = 0; */ + insert_text(view, view->cur_line, view->cur_index, "\n", 1, 0, 0, 0, 0, 0, 0, start_group); return (struct action){ACTION_NONE, NULL}; } static void -move_cursor_logically(ledit_buffer *buffer, int movement_dir, int allow_illegal_index) { +move_cursor_logically(ledit_view *view, int movement_dir, int allow_illegal_index) { if (movement_dir < 0) { - buffer->cur_index = ledit_buffer_prev_cursor_pos( - buffer, buffer->cur_line, buffer->cur_index, 1 + view->cur_index = view_prev_cursor_pos( + view, view->cur_line, view->cur_index, 1 ); } else { - buffer->cur_index = ledit_buffer_next_cursor_pos( - buffer, buffer->cur_line, buffer->cur_index, 1 + view->cur_index = view_next_cursor_pos( + view, view->cur_line, view->cur_index, 1 ); } if (!allow_illegal_index) { - buffer->cur_index = ledit_buffer_get_legal_normal_pos( - buffer, buffer->cur_line, buffer->cur_index + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, view->cur_index ); } } static struct action -escape_key(ledit_buffer *buffer, char *text, int len) { +escape_key(ledit_view *view, char *text, size_t len) { (void)text; (void)len; clear_key_stack(); - if (buffer->common->mode == INSERT) + if (view->mode == INSERT) finalize_repetition_stack(); - if (buffer->common->mode == INSERT && - (buffer->sel.line1 != buffer->sel.line2 || - buffer->sel.byte1 != buffer->sel.byte2)) { - ledit_buffer_set_mode(buffer, VISUAL); - } else if (buffer->common->mode != NORMAL) { - ledit_buffer_set_mode(buffer, NORMAL); - move_cursor_logically(buffer, -1, 0); - if (buffer->sel.line1 != buffer->sel.line2) { - int min = buffer->sel.line1 < buffer->sel.line2 ? buffer->sel.line1 : buffer->sel.line2; - int max = buffer->sel.line1 > buffer->sel.line2 ? buffer->sel.line1 : buffer->sel.line2; - for (int i = min; i <= max; i++) { - ledit_buffer_wipe_line_cursor_attrs(buffer, i); - } - } - buffer->sel.line1 = buffer->sel.line2 = -1; - buffer->sel.byte1 = buffer->sel.byte2 = -1; - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + if (view->mode == INSERT && view->sel_valid) { + view_set_mode(view, VISUAL); + } else if (view->mode != NORMAL) { + view_set_mode(view, NORMAL); + move_cursor_logically(view, -1, 0); + view_wipe_selection(view); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } return (struct action){ACTION_NONE, NULL}; } static struct action -enter_insert(ledit_buffer *buffer, char *text, int len) { +enter_insert(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - if (buffer->common->mode == NORMAL) - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); - ledit_buffer_set_mode(buffer, INSERT); + if (view->mode == NORMAL) + view_wipe_line_cursor_attrs(view, view->cur_line); + view_set_mode(view, INSERT); clear_key_stack(); return (struct action){ACTION_NONE, NULL}; } /* FIXME: Check if previous key allows motion command - or should this be checked automatically before? */ static void -move_cursor_up_down(ledit_buffer *buffer, int dir) { - int new_line, new_softline; +move_cursor_up_down(ledit_view *view, int dir) { + size_t new_line; + int new_softline; motion_callback cb; int num = get_key_repeat_and_motion_cb(&cb); if (num == -1) - (void)err_invalid_key(buffer); + (void)err_invalid_key(view); if (num == 0) num = 1; num *= dir; get_new_line_softline( - buffer, buffer->cur_line, buffer->cur_index, + view, view->cur_line, view->cur_index, num, &new_line, &new_softline ); 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); + size_t start, end; + view_get_softline_bounds(view, new_line, new_softline, &start, &end); + cb(view, new_line, start, KEY_MOTION_LINE); } else { + /* FIXME: when selecting on last line, moving down moves the cursor back + one (when it stays on the same line because it's the last one) */ int lineno, x; - 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; - - if (buffer->common->mode == VISUAL) { - ledit_buffer_set_selection(buffer, buffer->sel.line1, buffer->sel.byte1, buffer->cur_line, buffer->cur_index); - } else if (buffer->common->mode == INSERT && - (buffer->sel.line1 != buffer->sel.line2 || - buffer->sel.byte1 != buffer->sel.byte2)) { - ledit_buffer_set_selection(buffer, buffer->cur_line, buffer->cur_index, buffer->cur_line, buffer->cur_index); - } else if (buffer->common->mode == NORMAL) { - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + ledit_pos_to_x_softline(view, view->cur_line, view->cur_index, &x, &lineno); + ledit_x_softline_to_pos(view, new_line, x, new_softline, &view->cur_index); + if (view->cur_line != new_line) + view_wipe_line_cursor_attrs(view, view->cur_line); + view->cur_line = new_line; + + if (view->mode == VISUAL) { + view_set_selection(view, view->sel.line1, view->sel.byte1, view->cur_line, view->cur_index); + } else if (view->mode == INSERT && view->sel_valid) { + view_wipe_selection(view); + } else if (view->mode == NORMAL) { + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } discard_repetition_stack(); } @@ -1657,63 +1655,63 @@ move_cursor_up_down(ledit_buffer *buffer, int dir) { } static struct action -cursor_down(ledit_buffer *buffer, char *text, int len) { +cursor_down(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - move_cursor_up_down(buffer, 1); + move_cursor_up_down(view, 1); return (struct action){ACTION_NONE, NULL}; } static struct action -cursor_up(ledit_buffer *buffer, char *text, int len) { +cursor_up(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - move_cursor_up_down(buffer, -1); + move_cursor_up_down(view, -1); return (struct action){ACTION_NONE, NULL}; } static struct action -join_lines(ledit_buffer *buffer, char *text, int len) { +join_lines(ledit_view *view, char *text, size_t len) { (void)text; (void)len; int num = get_key_repeat(); if (num == -1) - return err_invalid_key(buffer); + return err_invalid_key(view); if (num == 0) num = 1; int start_group = 1; ledit_line *ll1; - int cur_line = buffer->cur_line; + size_t cur_line = view->cur_line; /* FIXME: have a general tmp buf for everyone to use */ txtbuf *buf = txtbuf_new(); - int oldlen; + size_t oldlen; ledit_range cur_range, del_range; cur_range.line1 = cur_range.line2 = cur_line; for (int i = 0; i < num; i++) { - if (cur_line == buffer->lines_num - 1) + if (cur_line == view->lines_num - 1) break; /* getting cur line again should be unnecessary, but I'll just leave it in case I change the way lines are stored later */ - ll1 = ledit_buffer_get_line(buffer, cur_line); + ll1 = ledit_buffer_get_line(view->buffer, cur_line); oldlen = ll1->len; /* FIXME: truncate whitespace to one space */ - ledit_buffer_delete_range( - buffer, DELETE_CHAR, + view_delete_range( + view, DELETE_CHAR, cur_line, ll1->len, cur_line + 1, 0, NULL, NULL, &del_range, buf ); - cur_range.byte1 = buffer->cur_index; - cur_range.byte2 = buffer->cur_index = - ledit_buffer_get_legal_normal_pos(buffer, buffer->cur_line, oldlen); + cur_range.byte1 = view->cur_index; + cur_range.byte2 = view->cur_index = + view_get_legal_normal_pos(view, view->cur_line, oldlen); ledit_push_undo_delete( - buffer->undo, buf, del_range, cur_range, - start_group, buffer->common->mode + view->buffer->undo, buf, del_range, cur_range, + start_group, view->mode ); start_group = 0; } - ledit_buffer_set_line_cursor_attrs( - buffer, buffer->cur_line, buffer->cur_index + view_set_line_cursor_attrs( + view, view->cur_line, view->cur_index ); txtbuf_destroy(buf); finalize_repetition_stack(); @@ -1721,49 +1719,49 @@ join_lines(ledit_buffer *buffer, char *text, int len) { } static struct action -insert_at_beginning(ledit_buffer *buffer, char *text, int len) { +insert_at_beginning(ledit_view *view, char *text, size_t len) { if (!key_stack_empty()) - return err_invalid_key(buffer); - enter_insert(buffer, text, len); - int new_index = 0; + return err_invalid_key(view); + enter_insert(view, text, len); + size_t new_index = 0; if (!hard_line_based) { - int tmp; - ledit_buffer_get_pos_softline_bounds(buffer, buffer->cur_line, buffer->cur_index, &new_index, &tmp); + size_t tmp; + view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &new_index, &tmp); } - push_undo_empty_insert(buffer, buffer->cur_line, buffer->cur_index, 1); - buffer->cur_index = new_index; - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + push_undo_empty_insert(view, view->cur_line, view->cur_index, 1); + view->cur_index = new_index; + view_wipe_line_cursor_attrs(view, view->cur_line); return (struct action){ACTION_NONE, NULL}; } static struct action -cursor_to_first_non_ws(ledit_buffer *buffer, char *text, int len) { +cursor_to_first_non_ws(ledit_view *view, char *text, size_t len) { (void)text; (void)len; motion_callback cb; int num = get_key_repeat_and_motion_cb(&cb); if (num != 0) - return err_invalid_key(buffer); - int new_index = 0; + return err_invalid_key(view); + size_t new_index = 0; if (hard_line_based) { - new_index = ledit_line_next_non_whitespace(buffer, buffer->cur_line, 0); + new_index = view_line_next_non_whitespace(view, view->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(buffer, buffer->cur_line, start); + size_t start, end; + view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end); + new_index = view_line_next_non_whitespace(view, view->cur_line, start); /* next non-whitespace might be on next softline */ if (new_index >= end) { - new_index = ledit_buffer_prev_cursor_pos( - buffer, buffer->cur_line, end, 1 + new_index = view_prev_cursor_pos( + view, view->cur_line, end, 1 ); } } if (cb != NULL) { - cb(buffer, buffer->cur_line, new_index, KEY_MOTION_CHAR); + cb(view, view->cur_line, new_index, KEY_MOTION_CHAR); } else { - buffer->cur_index = new_index; - ledit_buffer_set_line_cursor_attrs( - buffer, buffer->cur_line, buffer->cur_index + view->cur_index = new_index; + view_set_line_cursor_attrs( + view, view->cur_line, view->cur_index ); discard_repetition_stack(); } @@ -1771,32 +1769,32 @@ cursor_to_first_non_ws(ledit_buffer *buffer, char *text, int len) { } static struct action -cursor_to_beginning(ledit_buffer *buffer, char *text, int len) { +cursor_to_beginning(ledit_view *view, char *text, size_t len) { (void)text; (void)len; motion_callback cb; int num = get_key_repeat_and_motion_cb(&cb); if (num != 0) - return err_invalid_key(buffer); + return err_invalid_key(view); /* FIXME: should anything be done with num? */ - int start_index = 0; + size_t start_index = 0; if (!hard_line_based) { - int tmp; - ledit_buffer_get_pos_softline_bounds(buffer, buffer->cur_line, buffer->cur_index, &start_index, &tmp); + size_t tmp; + view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start_index, &tmp); } if (cb != NULL) { - cb(buffer, buffer->cur_line, start_index, KEY_MOTION_CHAR); + cb(view, view->cur_line, start_index, KEY_MOTION_CHAR); } else { - buffer->cur_index = start_index; - if (buffer->common->mode == VISUAL) { - ledit_buffer_set_selection( - buffer, - buffer->sel.line1, buffer->sel.byte1, - buffer->cur_line, buffer->cur_index + view->cur_index = start_index; + if (view->mode == VISUAL) { + view_set_selection( + view, + view->sel.line1, view->sel.byte1, + view->cur_line, view->cur_index ); } else { - ledit_buffer_set_line_cursor_attrs( - buffer, buffer->cur_line, buffer->cur_index + view_set_line_cursor_attrs( + view, view->cur_line, view->cur_index ); } discard_repetition_stack(); @@ -1806,25 +1804,25 @@ cursor_to_beginning(ledit_buffer *buffer, char *text, int len) { } static struct action -enter_visual(ledit_buffer *buffer, char *text, int len) { +enter_visual(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - ledit_buffer_set_mode(buffer, VISUAL); - buffer->sel.line1 = buffer->sel.line2 = buffer->cur_line; - buffer->sel.byte1 = buffer->sel.byte2 = buffer->cur_index; - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + view_set_mode(view, VISUAL); + view->sel.line1 = view->sel.line2 = view->cur_line; + view->sel.byte1 = view->sel.byte2 = view->cur_index; + view_wipe_line_cursor_attrs(view, view->cur_line); clear_key_stack(); /* FIXME: error if not empty? */ return (struct action){ACTION_NONE, NULL}; } static struct action -switch_selection_end(ledit_buffer *buffer, char *text, int len) { +switch_selection_end(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - swap(&buffer->sel.line1, &buffer->sel.line2); - swap(&buffer->sel.byte1, &buffer->sel.byte2); - buffer->cur_line = buffer->sel.line2; - buffer->cur_index = buffer->sel.byte2; + swap_sz(&view->sel.line1, &view->sel.line2); + swap_sz(&view->sel.byte1, &view->sel.byte2); + view->cur_line = view->sel.line2; + view->cur_index = view->sel.byte2; return (struct action){ACTION_NONE, NULL}; } @@ -1832,103 +1830,106 @@ switch_selection_end(ledit_buffer *buffer, char *text, int len) { #define XK_NO_MOD 0 static struct action -enter_commandedit(ledit_buffer *buffer, char *text, int len) { +enter_commandedit(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - ledit_window_set_bottom_bar_text(buffer->window, ":", -1); - ledit_window_set_bottom_bar_min_pos(buffer->window, 1); - ledit_window_set_bottom_bar_cursor(buffer->window, 1); - ledit_command_set_type(CMD_EDIT); - ledit_window_set_bottom_bar_text_shown(buffer->window, 1); + ledit_window_set_bottom_bar_text(view->window, ":", -1); + ledit_window_set_bottom_bar_min_pos(view->window, 1); + ledit_window_set_bottom_bar_cursor(view->window, 1); + view->cur_command_type = CMD_EDIT; + ledit_window_set_bottom_bar_text_shown(view->window, 1); discard_repetition_stack(); return (struct action){ACTION_GRABKEY, &ledit_command_key_handler}; } static struct action -enter_searchedit_forward(ledit_buffer *buffer, char *text, int len) { +enter_searchedit_forward(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - ledit_window_set_bottom_bar_text(buffer->window, "/", -1); - ledit_window_set_bottom_bar_min_pos(buffer->window, 1); - ledit_window_set_bottom_bar_cursor(buffer->window, 1); - ledit_command_set_type(CMD_EDITSEARCH); - ledit_window_set_bottom_bar_text_shown(buffer->window, 1); + ledit_window_set_bottom_bar_text(view->window, "/", -1); + ledit_window_set_bottom_bar_min_pos(view->window, 1); + ledit_window_set_bottom_bar_cursor(view->window, 1); + view->cur_command_type = CMD_EDITSEARCH; + ledit_window_set_bottom_bar_text_shown(view->window, 1); discard_repetition_stack(); return (struct action){ACTION_GRABKEY, &ledit_command_key_handler}; } static struct action -enter_searchedit_backward(ledit_buffer *buffer, char *text, int len) { +enter_searchedit_backward(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - ledit_window_set_bottom_bar_text(buffer->window, "?", -1); - ledit_window_set_bottom_bar_min_pos(buffer->window, 1); - ledit_window_set_bottom_bar_cursor(buffer->window, 1); - ledit_command_set_type(CMD_EDITSEARCHB); - ledit_window_set_bottom_bar_text_shown(buffer->window, 1); + ledit_window_set_bottom_bar_text(view->window, "?", -1); + ledit_window_set_bottom_bar_min_pos(view->window, 1); + ledit_window_set_bottom_bar_cursor(view->window, 1); + view->cur_command_type = CMD_EDITSEARCHB; + ledit_window_set_bottom_bar_text_shown(view->window, 1); discard_repetition_stack(); return (struct action){ACTION_GRABKEY, &ledit_command_key_handler}; } /* FIXME: differentiate between jumping to line and index like nvi */ static struct action -mark_line_cb(ledit_buffer *buffer, char *text, int len) { +mark_line_cb(ledit_view *view, char *text, size_t len) { grab_char_cb = NULL; ledit_buffer_insert_mark( - buffer, text, len, buffer->cur_line, buffer->cur_index + view->buffer, text, len, view->cur_line, view->cur_index ); return (struct action){ACTION_NONE, NULL}; } -/* FIXME: check that byte is actually in at grapheme boundary */ +/* FIXME: move part of this to buffer.c */ +/* FIXME: check that byte is actually at grapheme boundary */ static struct action -jump_to_mark_cb(ledit_buffer *buffer, char *text, int len) { +jump_to_mark_cb(ledit_view *view, char *text, size_t len) { grab_char_cb = NULL; - ledit_buffer_marklist *marklist = buffer->marklist; + ledit_buffer_marklist *marklist = view->buffer->marklist; motion_callback cb; int num = get_key_repeat_and_motion_cb(&cb); if (num > 0) - return err_invalid_key(buffer); - int line = -1, index = -1; + return err_invalid_key(view); + size_t line = 0, index = 0; + int mark_found = 0; for (size_t i = 0; i < marklist->len; i++) { if (!strncmp(text, marklist->marks[i].text, len)) { ledit_line *ll; ledit_buffer_mark *m = &marklist->marks[i]; - if (m->line >= buffer->lines_num) { - line = buffer->lines_num - 1; - ll = ledit_buffer_get_line(buffer, line); + if (m->line >= view->lines_num) { + line = view->lines_num - 1; + ll = ledit_buffer_get_line(view->buffer, line); index = ll->len; } else { line = m->line; - ll = ledit_buffer_get_line(buffer, m->line); + ll = ledit_buffer_get_line(view->buffer, m->line); if (m->byte >= ll->len) index = ll->len; else index = m->byte; } + mark_found = 1; break; } } - if (line == -1) - return err_invalid_key(buffer); - if (buffer->common->mode == VISUAL) { - ledit_buffer_set_selection( - buffer, buffer->sel.line1, buffer->sel.byte1, line, index + if (!mark_found) + return err_invalid_key(view); + if (view->mode == VISUAL) { + view_set_selection( + view, view->sel.line1, view->sel.byte1, line, index ); - buffer->cur_line = line; - buffer->cur_index = index; + view->cur_line = line; + view->cur_index = index; } else { if (cb) { - cb(buffer, line, index, KEY_MOTION_LINE); + cb(view, line, index, KEY_MOTION_LINE); } else { - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); - buffer->cur_line = line; - buffer->cur_index = index; - buffer->cur_index = ledit_buffer_get_legal_normal_pos( - buffer, buffer->cur_line, buffer->cur_index + view_wipe_line_cursor_attrs(view, view->cur_line); + view->cur_line = line; + view->cur_index = index; + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, view->cur_index ); - ledit_buffer_set_line_cursor_attrs( - buffer, buffer->cur_line, buffer->cur_index + view_set_line_cursor_attrs( + view, view->cur_line, view->cur_index ); discard_repetition_stack(); } @@ -1937,8 +1938,8 @@ jump_to_mark_cb(ledit_buffer *buffer, char *text, int len) { } static struct action -mark_line(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +mark_line(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; grab_char_cb = &mark_line_cb; @@ -1947,8 +1948,8 @@ mark_line(ledit_buffer *buffer, char *text, int len) { } static struct action -jump_to_mark(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +jump_to_mark(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; grab_char_cb = &jump_to_mark_cb; @@ -1958,125 +1959,129 @@ jump_to_mark(ledit_buffer *buffer, char *text, int len) { /* FIXME: support visual mode, i.e. change selection to new place? */ static struct action -key_search_next(ledit_buffer *buffer, char *text, int len) { +key_search_next(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - search_next(buffer); + search_next(view); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static struct action -key_search_prev(ledit_buffer *buffer, char *text, int len) { +key_search_prev(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - search_prev(buffer); + search_prev(view); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static struct action -show_line(ledit_buffer *buffer, char *text, int len) { +show_line(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - int textlen = snprintf(NULL, 0, "Line %d of %d", buffer->cur_line + 1, buffer->lines_num); + int textlen = snprintf(NULL, 0, "Line %zu of %zu", view->cur_line + 1, view->lines_num); char *str = ledit_malloc(textlen + 1); - snprintf(str, textlen + 1, "Line %d of %d", buffer->cur_line + 1, buffer->lines_num); - ledit_window_show_message(buffer->window, str, textlen); + snprintf(str, textlen + 1, "Line %zu of %zu", view->cur_line + 1, view->lines_num); + ledit_window_show_message(view->window, str, textlen); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } /* FIXME: return status! */ static struct action -undo(ledit_buffer *buffer, char *text, int len) { +undo(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - ledit_buffer_set_selection(buffer, 0, 0, 0, 0); - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); - ledit_buffer_undo(buffer); - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_wipe_selection(view); + view_wipe_line_cursor_attrs(view, view->cur_line); + view_undo(view); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static struct action -redo(ledit_buffer *buffer, char *text, int len) { +redo(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - ledit_buffer_set_selection(buffer, 0, 0, 0, 0); - ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); - ledit_buffer_redo(buffer); - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + view_wipe_selection(view); + view_wipe_line_cursor_attrs(view, view->cur_line); + view_redo(view); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static struct action -insert_mode_insert_text(ledit_buffer *buffer, char *text, int len) { - delete_selection(buffer); - insert_text(buffer, buffer->cur_line, buffer->cur_index, text, len, -1, -1, -1, -1, 1); +insert_mode_insert_text(ledit_view *view, char *text, size_t len) { + /* FIXME: set cur_index when deleting selection */ + delete_selection(view); + insert_text(view, view->cur_line, view->cur_index, text, len, 0, 0, 0, 0, 0, 0, 1); return (struct action){ACTION_NONE, NULL}; } static struct action -clipcopy(ledit_buffer *buffer, char *text, int len) { +clipcopy(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - /* FIXME: abstract this through buffer */ - clipboard_primary_to_clipboard(buffer->window); + /* FIXME: abstract this through view */ + clipboard_primary_to_clipboard(view->window); discard_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } static struct action -clippaste(ledit_buffer *buffer, char *text, int len) { +clippaste(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - ledit_buffer_paste_clipboard(buffer); + view_paste_clipboard(view); finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } /* FIXME: make sure the found position is valid cursor position? */ static int -search_str_backwards(char *haystack, int hlen, char *needle, int nlen, int start_index) { - if (start_index < 0 || start_index > hlen) +search_str_backwards(char *haystack, size_t hlen, char *needle, size_t nlen, size_t start_index, size_t *ret) { + if (start_index > hlen) return -1; - int new_index = start_index; - for (new_index--; new_index >= 0; new_index--) { - if (!strncmp(haystack + new_index, needle, nlen)) - return new_index; + size_t new_index = start_index; + for (; new_index > 0; new_index--) { + if (!strncmp(haystack + new_index - 1, needle, nlen)) { + *ret = new_index; + return 0; + } } return -1; } static int -search_str_forwards(char *haystack, int hlen, char *needle, int nlen, int start_index) { - if (start_index < 0 || start_index >= hlen) +search_str_forwards(char *haystack, size_t hlen, char *needle, size_t nlen, size_t start_index, size_t *ret) { + if (start_index >= hlen) return -1; /* duplicate so it is nul-terminated */ char *search_str = ledit_strndup(needle, nlen); char *res = strstr(haystack + start_index + 1, search_str); free(search_str); /* FIXME: is this legal? */ - if (res) - return (int)(res - haystack); - else + if (res) { + *ret = (size_t)(res - haystack); + return 0; + } else { return -1; + } } /* just to make the macro below works for all cases */ /* FIXME: is there a more elegant way to do this? */ -static int -dummy_cursor_helper(ledit_buffer *buffer, int line, int index, int num) { - (void)buffer; +static size_t +dummy_cursor_helper(ledit_view *view, size_t line, size_t index, int num) { + (void)view; (void)line; (void)num; return index; } -/* FIXME: call normalize on line first? */ /* FIXME: add checks to functions that current mode is supported */ /* name is the name of the generated pair of functions @@ -2089,42 +2094,45 @@ dummy_cursor_helper(ledit_buffer *buffer, int line, int index, int num) { cur_funcv is called to position the cursor in visual mode */ #define GEN_MOVE_TO_CHAR(name, search_func, cur_funcm, cur_funcn, cur_funcv) \ static struct action \ -name##_cb(ledit_buffer *buffer, char *text, int len) { \ +name##_cb(ledit_view *view, char *text, size_t len) { \ motion_callback cb = NULL; \ int num = get_key_repeat_and_motion_cb(&cb); \ if (num == -1) \ - return err_invalid_key(buffer); \ + return err_invalid_key(view); \ if (num == 0) \ num = 1; \ - ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); \ - int new_index = buffer->cur_index; \ + ledit_line *ll = ledit_buffer_get_line(view->buffer, view->cur_line); \ + size_t new_index = view->cur_index; \ + int ret = -1; \ for (int i = 0; i < num; i++) { \ - new_index = search_func(ll->text, ll->len, text, len, new_index); \ - if (new_index == -1) \ + if ((ret = search_func( \ + ll->text, ll->len, text, len, \ + new_index, &new_index)) == -1) { \ break; \ + } \ } \ - if (new_index >= 0) { \ + if (!ret) { \ if (cb != NULL) { \ new_index = cur_funcm( \ - buffer, buffer->cur_line, new_index, 1 \ + view, view->cur_line, new_index, 1 \ ); \ - cb(buffer, buffer->cur_line, new_index, KEY_MOTION_CHAR); \ + cb(view, view->cur_line, new_index, KEY_MOTION_CHAR); \ } else { \ - if (buffer->common->mode == VISUAL) { \ - buffer->cur_index = cur_funcv( \ - buffer, buffer->cur_line, new_index, 1 \ + if (view->mode == VISUAL) { \ + view->cur_index = cur_funcv( \ + view, view->cur_line, new_index, 1 \ ); \ - ledit_buffer_set_selection( \ - buffer, \ - buffer->sel.line1, buffer->sel.byte1, \ - buffer->cur_line, buffer->cur_index \ + view_set_selection( \ + view, \ + view->sel.line1, view->sel.byte1, \ + view->cur_line, view->cur_index \ ); \ } else { \ - buffer->cur_index = cur_funcn( \ - buffer, buffer->cur_line, new_index, 1 \ + view->cur_index = cur_funcn( \ + view, view->cur_line, new_index, 1 \ ); \ - ledit_buffer_set_line_cursor_attrs( \ - buffer, buffer->cur_line, buffer->cur_index \ + view_set_line_cursor_attrs( \ + view, view->cur_line, view->cur_index \ ); \ } \ discard_repetition_stack(); \ @@ -2136,8 +2144,8 @@ name##_cb(ledit_buffer *buffer, char *text, int len) { } \ \ static struct action \ -name(ledit_buffer *buffer, char *text, int len) { \ - (void)buffer; \ +name(ledit_view *view, char *text, size_t len) { \ + (void)view; \ (void)text; \ (void)len; \ grab_char_cb = &name##_cb; \ @@ -2148,15 +2156,15 @@ name(ledit_buffer *buffer, char *text, int len) { /* FIXME: dummy_cursor_helper is kind of ugly */ GEN_MOVE_TO_CHAR( find_next_char_forwards, search_str_forwards, - dummy_cursor_helper, ledit_buffer_prev_cursor_pos, dummy_cursor_helper + dummy_cursor_helper, view_prev_cursor_pos, dummy_cursor_helper ) GEN_MOVE_TO_CHAR( find_next_char_backwards, search_str_backwards, - ledit_buffer_next_cursor_pos, ledit_buffer_next_cursor_pos, ledit_buffer_next_cursor_pos + view_next_cursor_pos, view_next_cursor_pos, view_next_cursor_pos ) GEN_MOVE_TO_CHAR( find_char_forwards, search_str_forwards, - ledit_buffer_next_cursor_pos, dummy_cursor_helper, dummy_cursor_helper + view_next_cursor_pos, dummy_cursor_helper, dummy_cursor_helper ) GEN_MOVE_TO_CHAR( find_char_backwards, search_str_backwards, @@ -2164,68 +2172,55 @@ GEN_MOVE_TO_CHAR( ) static struct action -replace_cb(ledit_buffer *buffer, char *text, int len) { - int start_index = buffer->cur_index; +replace_cb(ledit_view *view, char *text, size_t len) { + size_t start_index = view->cur_index; /* FIXME: replace with (key repeat) * text instead of just text */ - int end_index = ledit_buffer_next_cursor_pos( - buffer, buffer->cur_line, buffer->cur_index, 1 + size_t end_index = view_next_cursor_pos( + view, view->cur_line, view->cur_index, 1 ); delete_range( - buffer, 0, 0, - buffer->cur_line, start_index, buffer->cur_line, end_index, 0 + view, 0, 0, + view->cur_line, start_index, view->cur_line, end_index, 0 ); insert_text( - buffer, buffer->cur_line, start_index, text, len, - buffer->cur_line, start_index, buffer->cur_line, start_index, 0 + view, view->cur_line, start_index, text, len, + view->cur_line, start_index, view->cur_line, start_index, 1, 1, 0 ); /* this should not be necessary, but just in case */ - buffer->cur_index = ledit_buffer_get_legal_normal_pos( - buffer, buffer->cur_line, buffer->cur_index + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, view->cur_index ); - push_undo_empty_insert(buffer, buffer->cur_line, buffer->cur_index, 0); + push_undo_empty_insert(view, view->cur_line, view->cur_index, 0); grab_char_cb = NULL; return (struct action){ACTION_NONE, NULL}; } static struct action -replace(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +replace(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; int num = get_key_repeat(); if (num != 0) - return err_invalid_key(buffer); + return err_invalid_key(view); grab_char_cb = &replace_cb; return (struct action){ACTION_NONE, NULL}; } -static void -set_hard_line_based(ledit_buffer *buffer, int hl) { - hard_line_based = hl; - char *text = hl ? "|HL" : "|SL"; - ledit_window_set_mode_extra_text(buffer->window, text); -} - static struct action -toggle_hard_line_based(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +toggle_hard_line_based(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; int num = get_key_repeat(); if (num != 0) - return err_invalid_key(buffer); - set_hard_line_based(buffer, !hard_line_based); + return err_invalid_key(view); + ledit_buffer_set_hard_line_based(view->buffer, !view->buffer->hard_line_based); return (struct action){ACTION_NONE, NULL}; } -/* FIXME: this is sort of all over the place and ugly */ -void -keys_basic_init(ledit_buffer *buffer) { - set_hard_line_based(buffer, 1); -} - static struct action -handle_key(ledit_buffer *buffer, char *key_text, int len, KeySym sym, unsigned int key_state, int lang_index, int *found) { +handle_key(ledit_view *view, char *key_text, size_t len, KeySym sym, unsigned int key_state, int lang_index, int *found) { struct key *cur_keys = keys[lang_index].keys; int num_keys = keys[lang_index].num_keys; struct key_stack_elem *e = peek_key_stack(); @@ -2236,13 +2231,13 @@ handle_key(ledit_buffer *buffer, char *key_text, int len, KeySym sym, unsigned i return (struct action){ACTION_NONE, NULL}; } else if (len > 0 && grab_char_cb) { *found = 1; - return grab_char_cb(buffer, key_text, len); + return grab_char_cb(view, key_text, len); } *found = 0; for (int i = 0; i < num_keys; i++) { if (cur_keys[i].text) { if (len > 0 && - (cur_keys[i].modes & buffer->common->mode) && + (cur_keys[i].modes & view->mode) && (!e || (e->key & cur_keys[i].prev_keys)) && ((!strncmp(cur_keys[i].text, key_text, len) && match_key(cur_keys[i].mods, key_state & ~ShiftMask)) || @@ -2250,21 +2245,21 @@ handle_key(ledit_buffer *buffer, char *key_text, int len, KeySym sym, unsigned i /* FIXME: seems a bit hacky to remove shift, but it is needed to make keys that use shift match */ *found = 1; - return cur_keys[i].func(buffer, key_text, len); + return cur_keys[i].func(view, key_text, len); } - } else if ((cur_keys[i].modes & buffer->common->mode) && + } else if ((cur_keys[i].modes & view->mode) && cur_keys[i].keysym == sym && match_key(cur_keys[i].mods, key_state)) { *found = 1; - return cur_keys[i].func(buffer, key_text, len); + return cur_keys[i].func(view, key_text, len); } } return (struct action){ACTION_NONE, NULL}; } static struct action -repeat_command(ledit_buffer *buffer, char *text, int len) { - (void)buffer; +repeat_command(ledit_view *view, char *text, size_t len) { + (void)view; (void)text; (void)len; int found; @@ -2273,7 +2268,7 @@ repeat_command(ledit_buffer *buffer, char *text, int len) { unwind_repetition_stack(); struct repetition_stack_elem *e = get_cur_repetition_stack_elem(); while (e) { - (void)handle_key(buffer, e->key_text, e->len, e->sym, e->key_state, e->lang_index, &found); + (void)handle_key(view, e->key_text, e->len, e->sym, e->key_state, e->lang_index, &found); advance_repetition_stack(); e = get_cur_repetition_stack_elem(); } @@ -2284,29 +2279,29 @@ repeat_command(ledit_buffer *buffer, char *text, int len) { } struct action -basic_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index) { +basic_key_handler(ledit_view *view, XEvent *event, int lang_index) { char buf[64]; KeySym sym; int n; unsigned int key_state = event->xkey.state; - preprocess_key(buffer->window, event, &sym, buf, sizeof(buf), &n); + preprocess_key(view->window, event, &sym, buf, sizeof(buf), &n); struct repetition_stack_elem *re = push_repetition_stack(); re->key_text = ledit_strndup(buf, (size_t)n); - re->len = n; + re->len = (size_t)n; re->sym = sym; re->key_state = key_state; re->lang_index = lang_index; /* FIXME: only hide when actually necessary */ - ledit_window_hide_message(buffer->window); + ledit_window_hide_message(view->window); int found = 0; - struct action act = handle_key(buffer, buf, n, sym, key_state, lang_index, &found); + struct action act = handle_key(view, buf, (size_t)n, sym, key_state, lang_index, &found); /* FIXME: only do this when necessary */ if (found) - ledit_buffer_ensure_cursor_shown(buffer); + view_ensure_cursor_shown(view); /* FIXME: maybe show error if not found */ return act; } diff --git a/keys_basic.h b/keys_basic.h @@ -1,3 +1,2 @@ -void keys_basic_init(ledit_buffer *buffer); void basic_key_cleanup(void); -struct action basic_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index); +struct action basic_key_handler(ledit_view *view, XEvent *event, int lang_index); diff --git a/keys_basic_config.h b/keys_basic_config.h @@ -13,88 +13,88 @@ enum key_type { }; struct key { - char *text; /* for keys that correspond with text */ - unsigned int mods; /* modifier mask */ - KeySym keysym; /* for other keys, e.g. arrow keys */ - enum ledit_mode modes; /* modes in which this keybinding is functional */ - enum key_type prev_keys; /* allowed previous keys */ - enum key_type key_types; /* key types - used to determine if the key is allowed */ - struct action (*func)(ledit_buffer *, char *, int); /* callback function */ + char *text; /* for keys that correspond with text */ + unsigned int mods; /* modifier mask */ + KeySym keysym; /* for other keys, e.g. arrow keys */ + enum ledit_mode modes; /* modes in which this keybinding is functional */ + enum key_type prev_keys; /* allowed previous keys */ + enum key_type key_types; /* key types - used to determine if the key is allowed */ + struct action (*func)(ledit_view *, char *, size_t); /* callback function */ }; -static struct action backspace(ledit_buffer *buffer, char *text, int len); -static struct action cursor_left(ledit_buffer *buffer, char *text, int len); -static struct action cursor_right(ledit_buffer *buffer, char *text, int len); -static struct action cursor_up(ledit_buffer *buffer, char *text, int len); -static struct action cursor_down(ledit_buffer *buffer, char *text, int len); -static struct action return_key(ledit_buffer *buffer, char *text, int len); -static struct action delete_key(ledit_buffer *buffer, char *text, int len); -static struct action escape_key(ledit_buffer *buffer, char *text, int len); -static struct action enter_insert(ledit_buffer *buffer, char *text, int len); -static struct action cursor_to_beginning(ledit_buffer *buffer, char *text, int len); -static struct action push_0(ledit_buffer *buffer, char *text, int len); -static struct action push_1(ledit_buffer *buffer, char *text, int len); -static struct action push_2(ledit_buffer *buffer, char *text, int len); -static struct action push_3(ledit_buffer *buffer, char *text, int len); -static struct action push_4(ledit_buffer *buffer, char *text, int len); -static struct action push_5(ledit_buffer *buffer, char *text, int len); -static struct action push_6(ledit_buffer *buffer, char *text, int len); -static struct action push_7(ledit_buffer *buffer, char *text, int len); -static struct action push_8(ledit_buffer *buffer, char *text, int len); -static struct action push_9(ledit_buffer *buffer, char *text, int len); -static struct action delete(ledit_buffer *buffer, char *text, int len); -static struct action enter_visual(ledit_buffer *buffer, char *text, int len); -static struct action switch_selection_end(ledit_buffer *buffer, char *text, int len); -static struct action clipcopy(ledit_buffer *buffer, char *text, int len); -static struct action clippaste(ledit_buffer *buffer, char *text, int len); -static struct action show_line(ledit_buffer *buffer, char *text, int len); -static struct action enter_commandedit(ledit_buffer *buffer, char *text, int len); -static struct action enter_searchedit_backward(ledit_buffer *buffer, char *text, int len); -static struct action enter_searchedit_forward(ledit_buffer *buffer, char *text, int len); -static struct action key_search_next(ledit_buffer *buffer, char *text, int len); -static struct action key_search_prev(ledit_buffer *buffer, char *text, int len); -static struct action undo(ledit_buffer *buffer, char *text, int len); -static struct action redo(ledit_buffer *buffer, char *text, int len); -static struct action insert_mode_insert_text(ledit_buffer *buffer, char *text, int len); -static struct action repeat_command(ledit_buffer *buffer, char *text, int len); -static struct action screen_up(ledit_buffer *buffer, char *text, int len); -static struct action screen_down(ledit_buffer *buffer, char *text, int len); -static struct action scroll_with_cursor_up(ledit_buffer *buffer, char *text, int len); -static struct action scroll_with_cursor_down(ledit_buffer *buffer, char *text, int len); -static struct action scroll_lines_up(ledit_buffer *buffer, char *text, int len); -static struct action scroll_lines_down(ledit_buffer *buffer, char *text, int len); -static struct action move_to_line(ledit_buffer *buffer, char *text, int len); -static struct action paste_normal(ledit_buffer *buffer, char *text, int len); -static struct action paste_normal_backwards(ledit_buffer *buffer, char *text, int len); -static struct action change(ledit_buffer *buffer, char *text, int len); -static struct action move_to_eol(ledit_buffer *buffer, char *text, int len); -static struct action mark_line(ledit_buffer *buffer, char *text, int len); -static struct action jump_to_mark(ledit_buffer *buffer, char *text, int len); -static struct action next_word(ledit_buffer *buffer, char *text, int len); -static struct action next_word_end(ledit_buffer *buffer, char *text, int len); -static struct action next_bigword(ledit_buffer *buffer, char *text, int len); -static struct action next_bigword_end(ledit_buffer *buffer, char *text, int len); -static struct action prev_word(ledit_buffer *buffer, char *text, int len); -static struct action prev_bigword(ledit_buffer *buffer, char *text, int len); -static struct action append_after_eol(ledit_buffer *buffer, char *text, int len); -static struct action append_after_cursor(ledit_buffer *buffer, char *text, int len); -static struct action append_line_above(ledit_buffer *buffer, char *text, int len); -static struct action append_line_below(ledit_buffer *buffer, char *text, int len); -static struct action find_next_char_forwards(ledit_buffer *buffer, char *text, int len); -static struct action find_next_char_backwards(ledit_buffer *buffer, char *text, int len); -static struct action find_char_forwards(ledit_buffer *buffer, char *text, int len); -static struct action find_char_backwards(ledit_buffer *buffer, char *text, int len); -static struct action change_to_eol(ledit_buffer *buffer, char *text, int len); -static struct action delete_to_eol(ledit_buffer *buffer, char *text, int len); -static struct action delete_chars_forwards(ledit_buffer *buffer, char *text, int len); -static struct action delete_chars_backwards(ledit_buffer *buffer, char *text, int len); -static struct action yank(ledit_buffer *buffer, char *text, int len); -static struct action yank_lines(ledit_buffer *buffer, char *text, int len); -static struct action replace(ledit_buffer *buffer, char *text, int len); -static struct action cursor_to_first_non_ws(ledit_buffer *buffer, char *text, int len); -static struct action join_lines(ledit_buffer *buffer, char *text, int len); -static struct action insert_at_beginning(ledit_buffer *buffer, char *text, int len); -static struct action toggle_hard_line_based(ledit_buffer *buffer, char *text, int len); +static struct action backspace(ledit_view *view, char *text, size_t len); +static struct action cursor_left(ledit_view *view, char *text, size_t len); +static struct action cursor_right(ledit_view *view, char *text, size_t len); +static struct action cursor_up(ledit_view *view, char *text, size_t len); +static struct action cursor_down(ledit_view *view, char *text, size_t len); +static struct action return_key(ledit_view *view, char *text, size_t len); +static struct action delete_key(ledit_view *view, char *text, size_t len); +static struct action escape_key(ledit_view *view, char *text, size_t len); +static struct action enter_insert(ledit_view *view, char *text, size_t len); +static struct action cursor_to_beginning(ledit_view *view, char *text, size_t len); +static struct action push_0(ledit_view *view, char *text, size_t len); +static struct action push_1(ledit_view *view, char *text, size_t len); +static struct action push_2(ledit_view *view, char *text, size_t len); +static struct action push_3(ledit_view *view, char *text, size_t len); +static struct action push_4(ledit_view *view, char *text, size_t len); +static struct action push_5(ledit_view *view, char *text, size_t len); +static struct action push_6(ledit_view *view, char *text, size_t len); +static struct action push_7(ledit_view *view, char *text, size_t len); +static struct action push_8(ledit_view *view, char *text, size_t len); +static struct action push_9(ledit_view *view, char *text, size_t len); +static struct action delete(ledit_view *view, char *text, size_t len); +static struct action enter_visual(ledit_view *view, char *text, size_t len); +static struct action switch_selection_end(ledit_view *view, char *text, size_t len); +static struct action clipcopy(ledit_view *view, char *text, size_t len); +static struct action clippaste(ledit_view *view, char *text, size_t len); +static struct action show_line(ledit_view *view, char *text, size_t len); +static struct action enter_commandedit(ledit_view *view, char *text, size_t len); +static struct action enter_searchedit_backward(ledit_view *view, char *text, size_t len); +static struct action enter_searchedit_forward(ledit_view *view, char *text, size_t len); +static struct action key_search_next(ledit_view *view, char *text, size_t len); +static struct action key_search_prev(ledit_view *view, char *text, size_t len); +static struct action undo(ledit_view *view, char *text, size_t len); +static struct action redo(ledit_view *view, char *text, size_t len); +static struct action insert_mode_insert_text(ledit_view *view, char *text, size_t len); +static struct action repeat_command(ledit_view *view, char *text, size_t len); +static struct action screen_up(ledit_view *view, char *text, size_t len); +static struct action screen_down(ledit_view *view, char *text, size_t len); +static struct action scroll_with_cursor_up(ledit_view *view, char *text, size_t len); +static struct action scroll_with_cursor_down(ledit_view *view, char *text, size_t len); +static struct action scroll_lines_up(ledit_view *view, char *text, size_t len); +static struct action scroll_lines_down(ledit_view *view, char *text, size_t len); +static struct action move_to_line(ledit_view *view, char *text, size_t len); +static struct action paste_normal(ledit_view *view, char *text, size_t len); +static struct action paste_normal_backwards(ledit_view *view, char *text, size_t len); +static struct action change(ledit_view *view, char *text, size_t len); +static struct action move_to_eol(ledit_view *view, char *text, size_t len); +static struct action mark_line(ledit_view *view, char *text, size_t len); +static struct action jump_to_mark(ledit_view *view, char *text, size_t len); +static struct action next_word(ledit_view *view, char *text, size_t len); +static struct action next_word_end(ledit_view *view, char *text, size_t len); +static struct action next_bigword(ledit_view *view, char *text, size_t len); +static struct action next_bigword_end(ledit_view *view, char *text, size_t len); +static struct action prev_word(ledit_view *view, char *text, size_t len); +static struct action prev_bigword(ledit_view *view, char *text, size_t len); +static struct action append_after_eol(ledit_view *view, char *text, size_t len); +static struct action append_after_cursor(ledit_view *view, char *text, size_t len); +static struct action append_line_above(ledit_view *view, char *text, size_t len); +static struct action append_line_below(ledit_view *view, char *text, size_t len); +static struct action find_next_char_forwards(ledit_view *view, char *text, size_t len); +static struct action find_next_char_backwards(ledit_view *view, char *text, size_t len); +static struct action find_char_forwards(ledit_view *view, char *text, size_t len); +static struct action find_char_backwards(ledit_view *view, char *text, size_t len); +static struct action change_to_eol(ledit_view *view, char *text, size_t len); +static struct action delete_to_eol(ledit_view *view, char *text, size_t len); +static struct action delete_chars_forwards(ledit_view *view, char *text, size_t len); +static struct action delete_chars_backwards(ledit_view *view, char *text, size_t len); +static struct action yank(ledit_view *view, char *text, size_t len); +static struct action yank_lines(ledit_view *view, char *text, size_t len); +static struct action replace(ledit_view *view, char *text, size_t len); +static struct action cursor_to_first_non_ws(ledit_view *view, char *text, size_t len); +static struct action join_lines(ledit_view *view, char *text, size_t len); +static struct action insert_at_beginning(ledit_view *view, char *text, size_t len); +static struct action toggle_hard_line_based(ledit_view *view, char *text, size_t len); /* FIXME: maybe sort these and use binary search -> but that would mess with the catch-all keys */ diff --git a/keys_command.c b/keys_command.c @@ -19,63 +19,77 @@ #include "theme.h" #include "window.h" #include "buffer.h" +#include "view.h" #include "search.h" +#include "cleanup.h" #include "keys.h" -#include "action.h" #include "keys_command.h" #include "keys_command_config.h" /* FIXME: history for search and commands */ -/* FIXME: THIS WON'T WORK WHEN THERE ARE MULTIPLE BUFFERS! */ -/* this must first be set by caller before jumping to key handler */ -static enum ledit_command_type cur_type; +static int create_view(ledit_view *view, char *cmd, size_t l1, size_t l2); +static int close_view(ledit_view *view, char *cmd, size_t l1, size_t l2); +static int handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2); +static int handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2); +static int handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2); +static int handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2); +static int parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid); +static int handle_cmd(ledit_view *view, char *cmd, size_t len); -void -command_key_cleanup(void) { - /* nothing right now */ +static int +handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) { + (void)view; + (void)cmd; + (void)l1; + (void)l2; + /* FIXME: Implement properly; handle error */ + char *errstr; + if (view->buffer->filename) + ledit_buffer_write_to_file(view->buffer, view->buffer->filename, &errstr); + return 0; } -void -ledit_command_set_type(enum ledit_command_type type) { - cur_type = type; +static int +handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) { + (void)view; + (void)cmd; + (void)l1; + (void)l2; + /* FIXME: ask to save changes */ + ledit_cleanup(); + exit(0); + return 0; } -static int handle_write(ledit_buffer *buffer, char *cmd, int l1, int l2); -static int handle_quit(ledit_buffer *buffer, char *cmd, int l1, int l2); -static int handle_write_quit(ledit_buffer *buffer, char *cmd, int l1, int l2); -static int handle_substitute(ledit_buffer *buffer, char *cmd, int l1, int l2); -static int parse_range(ledit_buffer *buffer, char *cmd, int len, char **cmd_ret, int *line1_ret, int *line2_ret); -static int handle_cmd(ledit_buffer *buffer, char *cmd, int len); - static int -handle_write(ledit_buffer *buffer, char *cmd, int l1, int l2) { - (void)buffer; +create_view(ledit_view *view, char *cmd, size_t l1, size_t l2) { (void)cmd; (void)l1; (void)l2; - /* FIXME: Implement properly; handle error */ - char *errstr; - if (buffer->filename) - ledit_buffer_write_to_file(buffer, buffer->filename, &errstr); + ledit_buffer_add_view(view->buffer, view->theme, view->mode, view->cur_line, view->cur_index); return 0; } static int -handle_quit(ledit_buffer *buffer, char *cmd, int l1, int l2) { - (void)buffer; +close_view(ledit_view *view, char *cmd, size_t l1, size_t l2) { (void)cmd; (void)l1; (void)l2; - /* FIXME: Implement */ - exit(1); + /* FIXME: This will lead to problems if I add something that + requires access to the view after the command is handled. */ + ledit_buffer_remove_view(view->buffer, view); + if (view->buffer->views_num == 0) { + ledit_cleanup(); + exit(0); + } return 0; } static int -handle_write_quit(ledit_buffer *buffer, char *cmd, int l1, int l2) { - (void)buffer; +handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) { + (void)view; (void)cmd; (void)l1; (void)l2; @@ -84,8 +98,8 @@ handle_write_quit(ledit_buffer *buffer, char *cmd, int l1, int l2) { } static int -handle_substitute(ledit_buffer *buffer, char *cmd, int l1, int l2) { - (void)buffer; +handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) { + (void)view; (void)cmd; (void)l1; (void)l2; @@ -101,11 +115,13 @@ enum cmd_type { static const struct { char *cmd; enum cmd_type type; - int (*handler)(ledit_buffer *buffer, char *cmd, int l1, int l2); + int (*handler)(ledit_view *view, char *cmd, size_t l1, size_t l2); } cmds[] = { {"wq", CMD_OPTIONAL_RANGE, &handle_write_quit}, {"w", CMD_OPTIONAL_RANGE, &handle_write}, {"q", CMD_NORMAL, &handle_quit}, + {"v", CMD_NORMAL, &create_view}, + {"c", CMD_NORMAL, &close_view}, {"s", CMD_OPTIONAL_RANGE, &handle_substitute} }; @@ -117,7 +133,7 @@ $ last line /* FIXME: ACTUALLY USE LEN!!! */ static int -parse_range(ledit_buffer *buffer, char *cmd, int len, char **cmd_ret, int *line1_ret, int *line2_ret) { +parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid) { (void)len; enum { START_LINENO = 1, @@ -125,28 +141,34 @@ parse_range(ledit_buffer *buffer, char *cmd, int len, char **cmd_ret, int *line1 IN_RANGE = 4, IN_LINENO = 8 } s = START_LINENO | START_RANGE; - int l1 = -1, l2 = -1; + size_t l1 = 0, l2 = 0; + *l1_valid = 0; + *l2_valid = 0; char *c = cmd; for (; *c != '\0'; c++) { if (isdigit(*c)) { /* FIXME: integer overflow */ if (s & IN_LINENO) { - if (l2 != -1) + if (*l2_valid) { l2 = l2 * 10 + (*c - '0'); - else + } else { l1 = l1 * 10 + (*c - '0'); + *l1_valid = 1; + } } else if ((s & START_LINENO) && (s & START_RANGE)) { l1 = *c - '0'; + *l1_valid = 1; s &= ~START_RANGE; s &= ~START_LINENO; s |= IN_RANGE | IN_LINENO; } else if ((s & START_LINENO)) { l2 = *c - '0'; + *l2_valid = 1; s &= ~START_LINENO; s |= IN_LINENO; } } else if (*c == ',' && !(s & START_RANGE)) { - if (l1 != -1 && l2 != -1) { + if (*l1_valid && *l2_valid) { return 1; } else { s |= START_LINENO; @@ -155,17 +177,21 @@ parse_range(ledit_buffer *buffer, char *cmd, int len, char **cmd_ret, int *line1 } else if (*c == '%') { if (s & START_RANGE) { l1 = 1; - l2 = buffer->lines_num; + l2 = view->lines_num; + *l1_valid = *l2_valid = 1; break; } else { return 1; } } else if (*c == '$') { if (s & START_LINENO) { - if (l1 == -1) - l1 = buffer->lines_num; - else - l2 = buffer->lines_num; + if (!*l1_valid) { + l1 = view->lines_num; + *l1_valid = 1; + } else { + l2 = view->lines_num; + *l2_valid = 1; + } s &= ~START_LINENO; s &= ~IN_LINENO; } else { @@ -175,7 +201,7 @@ parse_range(ledit_buffer *buffer, char *cmd, int len, char **cmd_ret, int *line1 break; } } - if ((l1 == -1 || l2 == -1) && !(s & START_RANGE)) + if ((!*l1_valid || !*l2_valid) && !(s & START_RANGE)) return 1; *cmd_ret = c; *line1_ret = l1; @@ -184,196 +210,196 @@ parse_range(ledit_buffer *buffer, char *cmd, int len, char **cmd_ret, int *line1 } static int -handle_cmd(ledit_buffer *buffer, char *cmd, int len) { - if (len < 0) - len = strlen(cmd); +handle_cmd(ledit_view *view, char *cmd, size_t len) { if (len < 1) return 0; char *c; - int l1, l2; + size_t l1, l2; + int l1_valid, l2_valid; /* FIXME: show error msg here */ - if (parse_range(buffer, cmd, len, &c, &l1, &l2)) + if (parse_range(view, cmd, len, &c, &l1, &l2, &l1_valid, &l2_valid)) return 0; - int range_given = l1 != -1 && l2 != -1; + int range_given = l1_valid && l2_valid; for (size_t i = 0; i < LENGTH(cmds); i++) { if (!strncmp(cmds[i].cmd, c, strlen(cmds[i].cmd)) && (!range_given || cmds[i].type == CMD_OPTIONAL_RANGE)) { - return cmds[i].handler(buffer, c, l1, l2); + return cmds[i].handler(view, c, l1, l2); } } return 0; } static int -substitute_yes(ledit_buffer *buffer, char *key_text, int len) { - (void)buffer; +substitute_yes(ledit_view *view, char *key_text, size_t len) { + (void)view; (void)key_text; (void)len; return 1; } static int -substitute_yes_all(ledit_buffer *buffer, char *key_text, int len) { - (void)buffer; +substitute_yes_all(ledit_view *view, char *key_text, size_t len) { + (void)view; (void)key_text; (void)len; return 0; } static int -substitute_no(ledit_buffer *buffer, char *key_text, int len) { - (void)buffer; +substitute_no(ledit_view *view, char *key_text, size_t len) { + (void)view; (void)key_text; (void)len; return 1; } static int -substitute_no_all(ledit_buffer *buffer, char *key_text, int len) { - (void)buffer; +substitute_no_all(ledit_view *view, char *key_text, size_t len) { + (void)view; (void)key_text; (void)len; return 0; } static int -edit_insert_text(ledit_buffer *buffer, char *key_text, int len) { - ledit_window_insert_bottom_bar_text(buffer->window, key_text, len); +edit_insert_text(ledit_view *view, char *key_text, size_t len) { + /* FIXME: overflow */ + ledit_window_insert_bottom_bar_text(view->window, key_text, len); ledit_window_set_bottom_bar_cursor( - buffer->window, ledit_window_get_bottom_bar_cursor(buffer->window) + len + view->window, ledit_window_get_bottom_bar_cursor(view->window) + len ); return 1; } static int -edit_cursor_to_end(ledit_buffer *buffer, char *key_text, int len) { +edit_cursor_to_end(ledit_view *view, char *key_text, size_t len) { (void)key_text; (void)len; - ledit_window_bottom_bar_cursor_to_end(buffer->window); + ledit_window_bottom_bar_cursor_to_end(view->window); return 1; } static int -edit_cursor_to_beginning(ledit_buffer *buffer, char *key_text, int len) { +edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len) { (void)key_text; (void)len; - ledit_window_bottom_bar_cursor_to_beginning(buffer->window); + ledit_window_bottom_bar_cursor_to_beginning(view->window); return 1; } static int -edit_cursor_left(ledit_buffer *buffer, char *key_text, int len) { +edit_cursor_left(ledit_view *view, char *key_text, size_t len) { (void)key_text; (void)len; - ledit_window_move_bottom_bar_cursor(buffer->window, -1); + ledit_window_move_bottom_bar_cursor(view->window, -1); return 1; } static int -edit_cursor_right(ledit_buffer *buffer, char *key_text, int len) { +edit_cursor_right(ledit_view *view, char *key_text, size_t len) { (void)key_text; (void)len; - ledit_window_move_bottom_bar_cursor(buffer->window, 1); + ledit_window_move_bottom_bar_cursor(view->window, 1); return 1; } static int -edit_backspace(ledit_buffer *buffer, char *key_text, int len) { +edit_backspace(ledit_view *view, char *key_text, size_t len) { (void)key_text; (void)len; - ledit_window_delete_bottom_bar_char(buffer->window, -1); + ledit_window_delete_bottom_bar_char(view->window, -1); return 1; } static int -edit_delete(ledit_buffer *buffer, char *key_text, int len) { +edit_delete(ledit_view *view, char *key_text, size_t len) { (void)key_text; (void)len; - ledit_window_delete_bottom_bar_char(buffer->window, 1); + ledit_window_delete_bottom_bar_char(view->window, 1); return 1; } static int -edit_submit(ledit_buffer *buffer, char *key_text, int len) { +edit_submit(ledit_view *view, char *key_text, size_t len) { (void)key_text; (void)len; - ledit_window_set_bottom_bar_text_shown(buffer->window, 0); + ledit_window_set_bottom_bar_text_shown(view->window, 0); /* FIXME: this is hacky */ - return handle_cmd(buffer, ledit_window_get_bottom_bar_text(buffer->window) + 1, -1); + return handle_cmd(view, ledit_window_get_bottom_bar_text(view->window) + 1, -1); } /* FIXME: support visual mode, i.e. change selection to new place? */ void -search_next(ledit_buffer *buffer) { - 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); +search_next(ledit_view *view) { + view_wipe_line_cursor_attrs(view, view->cur_line); + enum ledit_search_state ret = ledit_search_next(view, &view->cur_line, &view->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); if (ret != SEARCH_NORMAL) - ledit_window_show_message(buffer->window, ledit_search_state_to_str(ret), -1); + ledit_window_show_message(view->window, ledit_search_state_to_str(ret), -1); } void -search_prev(ledit_buffer *buffer) { - 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); +search_prev(ledit_view *view) { + view_wipe_line_cursor_attrs(view, view->cur_line); + enum ledit_search_state ret = ledit_search_prev(view, &view->cur_line, &view->cur_index); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); if (ret != SEARCH_NORMAL) - ledit_window_show_message(buffer->window, ledit_search_state_to_str(ret), -1); + ledit_window_show_message(view->window, ledit_search_state_to_str(ret), -1); } static int -editsearch_submit(ledit_buffer *buffer, char *key_text, int len) { +editsearch_submit(ledit_view *view, char *key_text, size_t len) { (void)key_text; (void)len; - ledit_window_set_bottom_bar_text_shown(buffer->window, 0); - ledit_set_search_forward(ledit_window_get_bottom_bar_text(buffer->window) + 1); - search_next(buffer); + ledit_window_set_bottom_bar_text_shown(view->window, 0); + ledit_set_search_forward(ledit_window_get_bottom_bar_text(view->window) + 1); + search_next(view); return 0; } static int -editsearchb_submit(ledit_buffer *buffer, char *key_text, int len) { +editsearchb_submit(ledit_view *view, char *key_text, size_t len) { (void)key_text; (void)len; - ledit_window_set_bottom_bar_text_shown(buffer->window, 0); - ledit_set_search_backward(ledit_window_get_bottom_bar_text(buffer->window) + 1); - search_next(buffer); + ledit_window_set_bottom_bar_text_shown(view->window, 0); + ledit_set_search_backward(ledit_window_get_bottom_bar_text(view->window) + 1); + search_next(view); return 0; } static int -edit_discard(ledit_buffer *buffer, char *key_text, int len) { - (void)buffer; +edit_discard(ledit_view *view, char *key_text, size_t len) { + (void)view; (void)key_text; (void)len; - ledit_window_set_bottom_bar_text_shown(buffer->window, 0); + ledit_window_set_bottom_bar_text_shown(view->window, 0); return 0; } struct action -ledit_command_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index) { +ledit_command_key_handler(ledit_view *view, XEvent *event, int lang_index) { char buf[64]; KeySym sym; int n; struct key *cur_keys = keys[lang_index].keys; int num_keys = keys[lang_index].num_keys; unsigned int key_state = event->xkey.state; - preprocess_key(buffer->window, event, &sym, buf, sizeof(buf), &n); + preprocess_key(view->window, event, &sym, buf, sizeof(buf), &n); int grabkey = 1; for (int i = 0; i < num_keys; i++) { if (cur_keys[i].text) { if (n > 0 && - (cur_keys[i].type == cur_type) && + (cur_keys[i].type == view->cur_command_type) && ((!strncmp(cur_keys[i].text, buf, n) && match_key(cur_keys[i].mods, key_state & ~ShiftMask)) || cur_keys[i].text[0] == '\0')) { - grabkey = cur_keys[i].func(buffer, buf, n); + grabkey = cur_keys[i].func(view, buf, (size_t)n); break; } - } else if ((cur_keys[i].type == cur_type) && + } else if ((cur_keys[i].type == view->cur_command_type) && (cur_keys[i].keysym == sym) && (match_key(cur_keys[i].mods, key_state))) { - grabkey = cur_keys[i].func(buffer, buf, n); + grabkey = cur_keys[i].func(view, buf, (size_t)n); break; } } diff --git a/keys_command.h b/keys_command.h @@ -1,16 +1,5 @@ -enum ledit_command_type { - CMD_EDIT, - CMD_EDITSEARCH, - CMD_EDITSEARCHB, - CMD_SEARCH, - CMD_SEARCHB, - CMD_SUBSTITUTE -}; - /* these are only here so they can also be used by keys_basic */ -void search_next(ledit_buffer *buffer); -void search_prev(ledit_buffer *buffer); +void search_next(ledit_view *view); +void search_prev(ledit_view *view); -void command_key_cleanup(void); -void ledit_command_set_type(enum ledit_command_type type); -struct action ledit_command_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index); +struct action ledit_command_key_handler(ledit_view *view, XEvent *event, int lang_index); diff --git a/keys_command_config.h b/keys_command_config.h @@ -1,25 +1,25 @@ -static int substitute_yes(ledit_buffer *buffer, char *key_text, int len); -static int substitute_yes_all(ledit_buffer *buffer, char *key_text, int len); -static int substitute_no(ledit_buffer *buffer, char *key_text, int len); -static int substitute_no_all(ledit_buffer *buffer, char *key_text, int len); -static int edit_insert_text(ledit_buffer *buffer, char *key_text, int len); -static int edit_cursor_left(ledit_buffer *buffer, char *key_text, int len); -static int edit_cursor_right(ledit_buffer *buffer, char *key_text, int len); -static int edit_cursor_to_end(ledit_buffer *buffer, char *key_text, int len); -static int edit_cursor_to_beginning(ledit_buffer *buffer, char *key_text, int len); -static int edit_backspace(ledit_buffer *buffer, char *key_text, int len); -static int edit_delete(ledit_buffer *buffer, char *key_text, int len); -static int edit_submit(ledit_buffer *buffer, char *key_text, int len); -static int editsearch_submit(ledit_buffer *buffer, char *key_text, int len); -static int editsearchb_submit(ledit_buffer *buffer, char *key_text, int len); -static int edit_discard(ledit_buffer *buffer, char *key_text, int len); +static int substitute_yes(ledit_view *view, char *key_text, size_t len); +static int substitute_yes_all(ledit_view *view, char *key_text, size_t len); +static int substitute_no(ledit_view *view, char *key_text, size_t len); +static int substitute_no_all(ledit_view *view, char *key_text, size_t len); +static int edit_insert_text(ledit_view *view, char *key_text, size_t len); +static int edit_cursor_left(ledit_view *view, char *key_text, size_t len); +static int edit_cursor_right(ledit_view *view, char *key_text, size_t len); +static int edit_cursor_to_end(ledit_view *view, char *key_text, size_t len); +static int edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len); +static int edit_backspace(ledit_view *view, char *key_text, size_t len); +static int edit_delete(ledit_view *view, char *key_text, size_t len); +static int edit_submit(ledit_view *view, char *key_text, size_t len); +static int editsearch_submit(ledit_view *view, char *key_text, size_t len); +static int editsearchb_submit(ledit_view *view, char *key_text, size_t len); +static int edit_discard(ledit_view *view, char *key_text, size_t len); struct key { char *text; /* for keys that correspond with text */ unsigned int mods; /* modifier mask */ KeySym keysym; /* for other keys, e.g. arrow keys */ enum ledit_command_type type; /* substitute, etc. */ - int (*func)(ledit_buffer *, char *, int); /* callback function */ + int (*func)(ledit_view *, char *, size_t); /* callback function */ }; /* "" means catch-all, i.e. all keys with text are given to that callback */ diff --git a/ledit.c b/ledit.c @@ -1,3 +1,4 @@ +/* FIXME: Just use int for everything? size_t just doesn't seem to be worth it */ /* FIXME: Make scrolling more smooth */ /* FIXME: Document that everything is assumed to be utf8 */ /* FIXME: Only redraw part of screen if needed */ @@ -40,41 +41,25 @@ #include "cache.h" #include "undo.h" #include "buffer.h" -#include "action.h" +#include "view.h" #include "search.h" #include "keys.h" #include "keys_basic.h" #include "keys_command.h" +#include "macros.h" +#include "cleanup.h" -static void resize_window(int w, int h); static void mainloop(void); static void setup(int argc, char *argv[]); -static void cleanup(void); static void redraw(void); -static int button_press(XEvent *event, int scroll_num); -static int button_release(XEvent *event); -static int drag_motion(XEvent *event); static void change_keyboard(char *lang); -static void key_press(XEvent *event); +static void key_press(ledit_view *view, XEvent *event); ledit_theme *theme = NULL; -ledit_window *window = NULL; ledit_buffer *buffer = NULL; ledit_common common; int cur_lang = 0; -struct action cur_action = {ACTION_NONE, NULL}; - -/* stolen from OpenBSD */ -#define ledit_timespecsub(tsp, usp, vsp) \ - do { \ - (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ - (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ - if ((vsp)->tv_nsec < 0) { \ - (vsp)->tv_sec--; \ - (vsp)->tv_nsec += 1000000000L; \ - } \ - } while (0) static void mainloop(void) { @@ -83,6 +68,7 @@ mainloop(void) { int major, minor; if (!XkbQueryExtension(common.dpy, 0, &xkb_event_type, NULL, &major, &minor)) { fprintf(stderr, "XKB not supported."); + ledit_cleanup(); exit(1); } /*printf("XKB (%d.%d) supported.\n", major, minor);*/ @@ -103,19 +89,11 @@ mainloop(void) { int running = 1; int change_kbd = 0; - int need_redraw = 0; redraw(); - /* store last time that a mouse event was processed in order to - avoid sending too many mouse events to be processed */ - /* also store last draw time so framerate can be limited */ - struct timespec now, elapsed, last, last_scroll, last_motion, last_resize, sleep_time; + /* store last draw time so framerate can be limited */ + struct timespec now, elapsed, last, sleep_time; clock_gettime(CLOCK_MONOTONIC, &last); - last_scroll = last_motion = last_resize = last; sleep_time.tv_sec = 0; - XEvent last_scroll_event, last_motion_event, last_resize_event; - int last_scroll_valid = 0, last_motion_valid = 0, last_resize_valid = 0; - int scroll_num = 0; - int scroll_delta = 0; while (running) { while (XPending(common.dpy)) { XNextEvent(common.dpy, &event); @@ -125,91 +103,55 @@ mainloop(void) { } if (XFilterEvent(&event, None)) continue; + ledit_view *view = NULL; + /* FIXME: abstract view handling a bit (don't access directly here) */ + for (size_t i = 0; i < buffer->views_num; i++) { + if (buffer->views[i]->window->xwin == event.xany.window) { + view = buffer->views[i]; + break; + } + } + if (view == NULL) + continue; /* shouldn't happen */ + ledit_window *window = view->window; switch (event.type) { case Expose: - /* FIXME: why did I do this? */ - redraw(); - need_redraw = 1; + view->redraw = 1; break; case ConfigureNotify: - last_resize_event = event; - last_resize_valid = 1; + ledit_window_register_resize(view->window, &event); break; case ButtonPress: - /* FIXME: this is all a bit hacky */ - if (event.xbutton.button == Button4 || - event.xbutton.button == Button5) { - scroll_delta = event.xbutton.button == Button4 ? -1 : 1; - if (last_scroll_valid) { - scroll_num += scroll_delta; - } else { - last_scroll_event = event; - last_scroll_valid = 1; - scroll_num = scroll_delta; - } - } else { - need_redraw |= button_press(&event, 0); - } + ledit_window_register_button_press(view->window, &event); break; case ButtonRelease: - need_redraw |= button_release(&event); + ledit_window_button_release(view->window, &event); break; case MotionNotify: - /* FIXME: is it legal to just copy event like this? */ - last_motion_event = event; - last_motion_valid = 1; + ledit_window_register_motion(window, &event); break; case KeyPress: - need_redraw = 1; - key_press(&event); + key_press(view, &event); break; case ClientMessage: - if ((Atom)event.xclient.data.l[0] == buffer->window->wm_delete_msg) - running = 0; + if ((Atom)event.xclient.data.l[0] == view->window->wm_delete_msg) { + ledit_buffer_remove_view(buffer, view); + if (buffer->views_num == 0) + running = 0; + } break; case SelectionNotify: case PropertyNotify: - /* redraw because text may be pasted, then fall - through and let window handle the event */ - need_redraw = 1; - /* fall through */ case SelectionRequest: - ledit_window_clipboard_event(buffer->window, &event); + ledit_window_clipboard_event(view->window, &event); break; default: break; } }; - if (last_motion_valid) { - clock_gettime(CLOCK_MONOTONIC, &now); - ledit_timespecsub(&now, &last_motion, &elapsed); - if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= MOUSE_TICK) { - need_redraw |= drag_motion(&last_motion_event); - last_motion = now; - last_motion_valid = 0; - } - } - if (last_scroll_valid) { - clock_gettime(CLOCK_MONOTONIC, &now); - ledit_timespecsub(&now, &last_scroll, &elapsed); - if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= MOUSE_TICK) { - need_redraw |= button_press(&last_scroll_event, scroll_num); - last_scroll = now; - last_scroll_valid = 0; - } - } - if (last_resize_valid) { - clock_gettime(CLOCK_MONOTONIC, &now); - ledit_timespecsub(&now, &last_resize, &elapsed); - if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= RESIZE_TICK) { - resize_window( - last_resize_event.xconfigure.width, - last_resize_event.xconfigure.height - ); - last_resize = now; - last_resize_valid = 0; - need_redraw = 1; - } + + for (size_t i = 0; i < buffer->views_num; i++) { + ledit_window_handle_filtered_events(buffer->views[i]->window); } if (change_kbd) { @@ -226,10 +168,7 @@ mainloop(void) { XFree(group); XkbFreeKeyboard(desc, XkbAllComponentsMask, True); } - if (need_redraw) { - redraw(); - need_redraw = 0; - } + redraw(); clock_gettime(CLOCK_MONOTONIC, &now); ledit_timespecsub(&now, &last, &elapsed); @@ -258,6 +197,7 @@ setup(int argc, char *argv[]) { ); if (!info || num_screens < 1 || info->count < 1) { fprintf(stderr, "No visuals support Xdbe.\n"); + ledit_cleanup(); exit(1); } XVisualInfo xvisinfo_templ; @@ -276,11 +216,13 @@ setup(int argc, char *argv[]) { stderr, "Couldn't match a Visual with double buffering\n" ); + ledit_cleanup(); exit(1); } common.vis = xvisinfo_match->visual; } else { fprintf(stderr, "No Xdbe support.\n"); + ledit_cleanup(); exit(1); } @@ -288,63 +230,43 @@ setup(int argc, char *argv[]) { common.cm = DefaultColormap(common.dpy, common.screen); theme = ledit_theme_create(&common); - window = ledit_window_create(&common, theme); - buffer = ledit_buffer_create(&common, theme, window); + buffer = ledit_buffer_create(&common); /* FIXME: Support multiple buffers/files */ if (argc > 1) { char *load_err; if (ledit_buffer_load_file(buffer, argv[1], 0, &load_err)) { fprintf(stderr, "Error opening file '%s': %s\n", argv[1], load_err); + ledit_cleanup(); exit(1); } /* FIXME: encapsulate */ buffer->filename = ledit_strdup(argv[1]); } - ledit_buffer_set_mode(buffer, NORMAL); - ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); - keys_basic_init(buffer); + ledit_buffer_add_view(buffer, theme, NORMAL, 0, 0); + /* FIXME: don't access view directly here */ + ledit_view *view = buffer->views[0]; + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); redraw(); } -static void -cleanup(void) { +void +ledit_cleanup(void) { /* FIXME: check for other things to clean up */ ledit_search_cleanup(); basic_key_cleanup(); - command_key_cleanup(); - ledit_buffer_destroy(buffer); - ledit_window_destroy(window); - ledit_theme_destroy(&common, theme); + if (buffer) + ledit_buffer_destroy(buffer); + if (theme) + ledit_theme_destroy(&common, theme); XCloseDisplay(common.dpy); } static void redraw(void) { - ledit_window_clear(window); - ledit_buffer_redraw(buffer); - ledit_window_redraw(window); -} - -static int -button_press(XEvent *event, int scroll_num) { - return ledit_window_button_press(window, event, scroll_num); -} - -static int -button_release(XEvent *event) { - return ledit_window_button_release(window, event); -} - -static int -drag_motion(XEvent *event) { - return ledit_window_drag_motion(window, event); -} - -static void -resize_window(int w, int h) { - ledit_window_resize(window, w, h); - ledit_buffer_resize_textview(buffer); + for (size_t i = 0; i < buffer->views_num; i++) { + view_redraw(buffer->views[i]); + } } static void @@ -356,11 +278,13 @@ change_keyboard(char *lang) { } static void -key_press(XEvent *event) { - if (cur_action.type == ACTION_GRABKEY && cur_action.callback) { - cur_action = cur_action.callback(buffer, event, cur_lang); +key_press(ledit_view *view, XEvent *event) { + /* FIXME: just let view handle this since the action is part + of it anyways now */ + if (view->cur_action.type == ACTION_GRABKEY && view->cur_action.callback) { + view->cur_action = view->cur_action.callback(view, event, cur_lang); } else { - cur_action = basic_key_handler(buffer, event, cur_lang); + view->cur_action = basic_key_handler(view, event, cur_lang); } } @@ -368,7 +292,7 @@ int main(int argc, char *argv[]) { setup(argc, argv); mainloop(); - cleanup(); + ledit_cleanup(); return 0; } diff --git a/macros.h b/macros.h @@ -0,0 +1,10 @@ +/* stolen from OpenBSD */ +#define ledit_timespecsub(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ + if ((vsp)->tv_nsec < 0) { \ + (vsp)->tv_sec--; \ + (vsp)->tv_nsec += 1000000000L; \ + } \ + } while (0) diff --git a/memory.c b/memory.c @@ -2,11 +2,21 @@ #include <stdint.h> #include <stdlib.h> #include <string.h> +#include <assert.h> + +#include "cleanup.h" -/* FIXME: clean up on exit */ static void fatal_err(const char *msg) { fprintf(stderr, "%s", msg); + ledit_cleanup(); + exit(1); +} + +void +err_overflow(void) { + fprintf(stderr, "Integer overflow.\n"); + ledit_cleanup(); exit(1); } @@ -68,7 +78,7 @@ ledit_strcat(const char *str1, const char *str2) { } /* - * This is from OpenBSD (adapted to exit on error): + * This (reallocarray) is from OpenBSD (adapted to exit on error): * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net> */ @@ -83,7 +93,105 @@ 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"); + fatal_err("Integer overflow in reallocarray.\n"); } return realloc(optr, size * nmemb); } + +void +move_gap( + void *array, size_t elem_size, size_t index, + size_t gap, size_t cap, size_t len, + size_t *new_gap_ret) { + assert(array != NULL); + assert(index <= len); + assert(len <= cap); + char *carray = (char *)array; /* cast to char * for pointer arithmetic */ + /* since the array has size cap * elem_size, it is assumed that no overflow happens */ + if (index > gap) { + /* move piece between end of original gap and + index to beginning of original gap */ + memmove( + carray + gap * elem_size, + carray + (gap + cap - len) * elem_size, + (index - gap) * elem_size + ); + } else if (index < gap) { + /* move piece between index and original gap to + end of original gap */ + memmove( + carray + (index + cap - len) * elem_size, + carray + index * elem_size, + (gap - index) * elem_size + ); + } + if (new_gap_ret) + *new_gap_ret = index; +} + +/* FIXME: replace with macro version that takes type parameter in order + to avoid errors with elem_size */ +/* This is almost certainly premature optimization and maybe + not optimization at all. */ +void * +resize_and_move_gap( + void *array, size_t elem_size, + size_t old_gap, size_t old_cap, size_t len, + size_t min_size, size_t index, + size_t *new_gap_ret, size_t *new_cap_ret) { + assert(index <= len); + assert(len <= old_cap); + size_t gap_size = old_cap - len; + size_t new_cap = old_cap; + /* FIXME: read up on what the best values are here */ + if (new_cap < min_size) + new_cap = old_cap * 2 > min_size ? old_cap * 2 : min_size; + if (new_cap < min_size) + err_overflow(); + if (new_cap != old_cap) + array = ledit_reallocarray(array, new_cap, elem_size); + char *carray = (char*)array; /* cast to char to do pointer arithmetic */ + /* we already know new_cap * elem_size does not wrap around because array + is of that size, so all the other multiplications here should be safe + (at least that's what I think, but I may be wrong) */ + if (index > old_gap) { + /* move piece between end of original gap and index to + beginning of original gap */ + memmove( + carray + old_gap * elem_size, + carray + (old_gap + gap_size) * elem_size, + (index - old_gap) * elem_size + ); + /* move piece after index to end of buffer */ + memmove( + carray + (new_cap - (len - index)) * elem_size, + carray + (index + gap_size) * elem_size, + (len - index) * elem_size + ); + } else if (index < old_gap) { + /* move piece after original gap to end of buffer */ + memmove( + carray + (new_cap - (len - old_gap)) * elem_size, + carray + (old_gap + gap_size) * elem_size, + (len - old_gap) * elem_size + ); + /* move piece between index and original gap to end */ + memmove( + carray + (new_cap - len + index) * elem_size, + carray + index * elem_size, + (old_gap - index) * elem_size + ); + } else { + /* move piece after original gap to end of buffer */ + memmove( + carray + (new_cap - (len - old_gap)) * elem_size, + carray + (old_gap + gap_size) * elem_size, + (len - old_gap) * elem_size + ); + } + if (new_gap_ret) + *new_gap_ret = index; + if (new_cap_ret) + *new_cap_ret = new_cap; + return carray; +} diff --git a/memory.h b/memory.h @@ -5,3 +5,35 @@ 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); + +/* + * Move the gap of a gap buffer with elements of size 'elem_size' to 'index'. + * 'index' is also written to 'new_gap_ret' if it is not NULL. This is just + * for convenience because it usually has to be set anyways. + */ +void move_gap( + void *array, size_t elem_size, size_t index, + size_t gap, size_t cap, size_t len, + size_t *new_gap_ret +); + +/* + * Resize a generic gap buffer with elements of size 'elem_size' to hold at least + * 'min_size' elements and move the gap to element position 'index'. + * 'old_gap' is the old index of the gap buffer, 'old_cap' is the total length of the + * array (number of elements, not bytes), and 'len' is the number of valid elements. + * 'index' is also written to 'new_gap_ret' if it is not NULL. This is just + * for convenience because it usually has to be set anyways. + * The new total length of the array is also written to 'new_cap_ret', if it is not + * NULL. If it is NULL, you're doing something wrong. + * Returns the final array (might be same as the old one). + */ +void *resize_and_move_gap( + void *array, size_t elem_size, + size_t old_gap, size_t old_cap, size_t len, + size_t min_size, size_t index, + size_t *new_gap_ret, size_t *new_cap_ret +); + +/* FIXME: not sure if this really belongs here */ +void err_overflow(void); diff --git a/pango-compat.c b/pango-compat.c @@ -6,7 +6,11 @@ /* FIXME: This is just copied from the newer version of pango. It *seems* like the structs used are public (they're in the documentation), but it does seem a bit dirty to do this here */ -#if !PANGO_VERSION_CHECK(1, 46, 0) +/* This version check is disabled currently because an older version of pango had a + bug where pango_layout_get_direction didn't work with lines of length 0. + I should actually check when that was changed so I can do a version check for that. */ +//#if !PANGO_VERSION_CHECK(1, 46, 0) +#if 1 static PangoLayoutRun * pango_layout_line_get_run(PangoLayoutLine *line, int index) { GSList *run_list; @@ -44,7 +48,7 @@ pango_layout_line_get_char_direction(PangoLayoutLine *layout_line, int index) { } PangoDirection -pango_layout_get_direction(PangoLayout *layout, int index) { +ledit_pango_layout_get_direction(PangoLayout *layout, int index) { int lineno, x; PangoLayoutLine *line; pango_layout_index_to_line_x(layout, index, 0, &lineno, &x); diff --git a/pango-compat.h b/pango-compat.h @@ -1,3 +1,4 @@ -#if !PANGO_VERSION_CHECK(1, 46, 0) -PangoDirection pango_layout_get_direction(PangoLayout *layout, int index); +//#if !PANGO_VERSION_CHECK(1, 46, 0) +#if 1 +PangoDirection ledit_pango_layout_get_direction(PangoLayout *layout, int index); #endif diff --git a/search.c b/search.c @@ -45,46 +45,46 @@ ledit_set_search_backward(char *pattern) { } static enum ledit_search_state -search_forward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { - *line_ret = buffer->cur_line; - *byte_ret = buffer->cur_index; +search_forward(ledit_view *view, size_t *line_ret, size_t *byte_ret) { + *line_ret = view->cur_line; + *byte_ret = view->cur_index; if (last_search == NULL) return SEARCH_NO_PATTERN; - int line = buffer->cur_line; + size_t line = view->cur_line; /* start one byte later so it doesn't get stuck on a match note: since the string ends with '\0', this is always valid */ - int byte = buffer->cur_index + 1; + size_t byte = view->cur_index + 1; char *res; - ledit_line *lline = ledit_buffer_get_line(buffer, line); + ledit_line *lline = ledit_buffer_get_line(view->buffer, line); ledit_buffer_normalize_line(lline); if ((res = strstr(lline->text + byte, last_search)) != NULL) { *line_ret = line; - *byte_ret = (int)(res - lline->text); + *byte_ret = (size_t)(res - lline->text); return SEARCH_NORMAL; } - for (int i = line + 1; i < buffer->lines_num; i++) { - lline = ledit_buffer_get_line(buffer, i); + for (size_t i = line + 1; i < view->lines_num; i++) { + lline = ledit_buffer_get_line(view->buffer, i); ledit_buffer_normalize_line(lline); if ((res = strstr(lline->text, last_search)) != NULL) { *line_ret = i; - *byte_ret = (int)(res - lline->text); + *byte_ret = (size_t)(res - lline->text); return SEARCH_NORMAL; } } - for (int i = 0; i < line; i++) { - lline = ledit_buffer_get_line(buffer, i); + for (size_t i = 0; i < line; i++) { + lline = ledit_buffer_get_line(view->buffer, i); ledit_buffer_normalize_line(lline); if ((res = strstr(lline->text, last_search)) != NULL) { *line_ret = i; - *byte_ret = (int)(res - lline->text); + *byte_ret = (size_t)(res - lline->text); return SEARCH_WRAPPED; } } - lline = ledit_buffer_get_line(buffer, line); + lline = ledit_buffer_get_line(view->buffer, line); ledit_buffer_normalize_line(lline); if ((res = strstr(lline->text, last_search)) != NULL) { *line_ret = line; - *byte_ret = (int)(res - lline->text); + *byte_ret = (size_t)(res - lline->text); return SEARCH_WRAPPED; } return SEARCH_NOT_FOUND; @@ -93,14 +93,14 @@ search_forward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { /* FIXME: this is insanely inefficient */ /* FIXME: just go backwards char-by-char and compare */ static enum ledit_search_state -search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { - *line_ret = buffer->cur_line; - *byte_ret = buffer->cur_index; +search_backward(ledit_view *view, size_t *line_ret, size_t *byte_ret) { + *line_ret = view->cur_line; + *byte_ret = view->cur_index; if (last_search == NULL) return SEARCH_NO_PATTERN; - int line = buffer->cur_line; - int byte = buffer->cur_index; - ledit_line *lline = ledit_buffer_get_line(buffer, line); + size_t line = view->cur_line; + size_t byte = view->cur_index; + ledit_line *lline = ledit_buffer_get_line(view->buffer, line); ledit_buffer_normalize_line(lline); char *last = NULL, *res = lline->text; while ((res = strstr(res, last_search)) != NULL && res < lline->text + byte) { @@ -110,11 +110,11 @@ search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { if (last != NULL) { *line_ret = line; /* FIXME: check if this is safe */ - *byte_ret = (int)(last - lline->text); + *byte_ret = (size_t)(last - lline->text); return SEARCH_NORMAL; } - for (int i = line - 1; i >= 0; i--) { - lline = ledit_buffer_get_line(buffer, i); + for (size_t i = line; i > 0; i--) { + lline = ledit_buffer_get_line(view->buffer, i-1); ledit_buffer_normalize_line(lline); res = lline->text; while ((res = strstr(res, last_search)) != NULL) { @@ -122,13 +122,13 @@ search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { res++; } if (last != NULL) { - *line_ret = i; - *byte_ret = (int)(last - lline->text); + *line_ret = i-1; + *byte_ret = (size_t)(last - lline->text); return SEARCH_NORMAL; } } - for (int i = buffer->lines_num - 1; i > line; i--) { - lline = ledit_buffer_get_line(buffer, i); + for (size_t i = view->lines_num - 1; i > line; i--) { + lline = ledit_buffer_get_line(view->buffer, i); ledit_buffer_normalize_line(lline); res = lline->text; while ((res = strstr(res, last_search)) != NULL) { @@ -137,11 +137,11 @@ search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { } if (last != NULL) { *line_ret = i; - *byte_ret = (int)(last - lline->text); + *byte_ret = (size_t)(last - lline->text); return SEARCH_WRAPPED; } } - lline = ledit_buffer_get_line(buffer, line); + lline = ledit_buffer_get_line(view->buffer, line); ledit_buffer_normalize_line(lline); res = lline->text + byte; while ((res = strstr(res, last_search)) != NULL) { @@ -150,26 +150,26 @@ search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { } if (last != NULL) { *line_ret = line; - *byte_ret = (int)(last - lline->text); + *byte_ret = (size_t)(last - lline->text); return SEARCH_WRAPPED; } return SEARCH_NOT_FOUND; } enum ledit_search_state -ledit_search_next(ledit_buffer *buffer, int *line_ret, int *byte_ret) { +ledit_search_next(ledit_view *view, size_t *line_ret, size_t *byte_ret) { if (last_dir == FORWARD) - return search_forward(buffer, line_ret, byte_ret); + return search_forward(view, line_ret, byte_ret); else - return search_backward(buffer, line_ret, byte_ret); + return search_backward(view, line_ret, byte_ret); } enum ledit_search_state -ledit_search_prev(ledit_buffer *buffer, int *line_ret, int *byte_ret) { +ledit_search_prev(ledit_view *view, size_t *line_ret, size_t *byte_ret) { if (last_dir == FORWARD) - return search_backward(buffer, line_ret, byte_ret); + return search_backward(view, line_ret, byte_ret); else - return search_forward(buffer, line_ret, byte_ret); + return search_forward(view, line_ret, byte_ret); } char * diff --git a/search.h b/search.h @@ -8,6 +8,6 @@ enum ledit_search_state { void ledit_search_cleanup(void); void ledit_set_search_forward(char *pattern); void ledit_set_search_backward(char *pattern); -enum ledit_search_state ledit_search_next(ledit_buffer *buffer, int *line_ret, int *byte_ret); -enum ledit_search_state ledit_search_prev(ledit_buffer *buffer, int *line_ret, int *byte_ret); +enum ledit_search_state ledit_search_next(ledit_view *view, size_t *line_ret, size_t *byte_ret); +enum ledit_search_state ledit_search_prev(ledit_view *view, size_t *line_ret, size_t *byte_ret); char *ledit_search_state_to_str(enum ledit_search_state state); diff --git a/undo.c b/undo.c @@ -1,6 +1,7 @@ #include <string.h> #include <assert.h> #include <stdlib.h> +#include <stdint.h> #include <X11/Xlib.h> #include <X11/Xutil.h> @@ -69,6 +70,7 @@ push_undo_elem(ledit_undo_stack *undo) { undo->cur++; undo->len = undo->cur + 1; if (undo->len > undo->cap) { + /* FIXME: wait, why is it size_t here already? */ size_t cap = undo->len * 2; undo->stack = ledit_realloc(undo->stack, cap * sizeof(undo_elem)); for (size_t i = undo->cap; i < cap; i++) { @@ -140,7 +142,7 @@ ledit_push_undo_delete( ledit_undo_status ledit_undo(ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data, undo_insert_callback insert_cb, undo_delete_callback delete_cb, - int *cur_line_ret, int *cur_index_ret, int *min_line_ret) { + size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret) { undo_elem *e; /* skip empty elements */ while (undo->cur >= 0 && undo->stack[undo->cur].text->len == 0) { @@ -150,8 +152,10 @@ ledit_undo(ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data, return UNDO_OLDEST_CHANGE; int group = undo->stack[undo->cur].group; int mode_group = undo->stack[undo->cur].mode_group; - int min_line = INT_MAX; + size_t min_line = SIZE_MAX; int mode_group_same = 0; + size_t cur_line = 0; + size_t cur_index = 0; while (undo->cur >= 0 && (undo->stack[undo->cur].group == group || (mode_group_same = ((mode == NORMAL || @@ -190,9 +194,17 @@ ledit_undo(ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data, if (e->op_range.line1 < min_line) min_line = e->op_range.line1; undo->cur--; - *cur_line_ret = e->cursor_range.line1; - *cur_index_ret = e->cursor_range.byte1; + cur_line = e->cursor_range.line1; + cur_index = e->cursor_range.byte1; } + /* these can't be written directly in the while loop because that causes + conflicts - when text is inserted/deleted on the current line of the + view, the cursor is updated, but if the current line was already changed + here before the end of the undo run, it's possible for multiple lines + to have a cursor highlight set in the end (at least when working with + multiple views) */ + *cur_line_ret = cur_line; + *cur_index_ret = cur_index; *min_line_ret = min_line; return UNDO_NORMAL; } @@ -200,7 +212,7 @@ ledit_undo(ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data, ledit_undo_status ledit_redo(ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data, undo_insert_callback insert_cb, undo_delete_callback delete_cb, - int *cur_line_ret, int *cur_index_ret, int *min_line_ret) { + size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret) { undo_elem *e; /* skip elements where no text is changed */ while (undo->cur < undo->len - 1 && undo->stack[undo->cur + 1].text->len == 0) { @@ -211,8 +223,10 @@ ledit_redo(ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data, undo->cur++; int group = undo->stack[undo->cur].group; int mode_group = undo->stack[undo->cur].mode_group; - int min_line = INT_MAX; + size_t min_line = SIZE_MAX; int mode_group_same = 0; + size_t cur_line = 0; + size_t cur_index = 0; while (undo->cur < undo->len && (undo->stack[undo->cur].group == group || (mode_group_same = ((mode == NORMAL || @@ -244,9 +258,11 @@ ledit_redo(ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data, if (e->op_range.line1 < min_line) min_line = e->op_range.line1; undo->cur++; - *cur_line_ret = e->cursor_range.line2; - *cur_index_ret = e->cursor_range.byte2; + cur_line = e->cursor_range.line2; + cur_index = e->cursor_range.byte2; } + *cur_line_ret = cur_line; + *cur_index_ret = cur_index; undo->cur--; *min_line_ret = min_line; return UNDO_NORMAL; diff --git a/undo.h b/undo.h @@ -5,8 +5,8 @@ typedef enum { } ledit_undo_status; typedef struct ledit_undo_stack ledit_undo_stack; -typedef void (*undo_insert_callback)(void *, int, int, char *, int); -typedef void (*undo_delete_callback)(void *, int, int, int, int); +typedef void (*undo_insert_callback)(void *, size_t, size_t, char *, size_t); +typedef void (*undo_delete_callback)(void *, size_t, size_t, size_t, size_t); ledit_undo_stack *ledit_undo_stack_create(void); void ledit_undo_stack_destroy(ledit_undo_stack *undo); @@ -24,10 +24,10 @@ void ledit_push_undo_delete( ledit_undo_status ledit_undo( ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data, undo_insert_callback insert_cb, undo_delete_callback delete_cb, - int *cur_line_ret, int *cur_index_ret, int *min_line_ret + size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret ); ledit_undo_status ledit_redo( ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data, undo_insert_callback insert_cb, undo_delete_callback delete_cb, - int *cur_line_ret, int *cur_index_ret, int *min_line_ret + size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret ); diff --git a/util.h b/util.h @@ -1,3 +1,5 @@ +/* FIXME: rename this to draw_util.h and rename macros.h to util.h */ + typedef struct { XftDraw *xftdraw; Pixmap pixmap; diff --git a/view.c b/view.c @@ -0,0 +1,2096 @@ +/* FIXME: shrink buffers when text length less than a fourth of the size */ +/* FIXME: also cache PangoLayouts since keeping them around isn't really of much use? */ +/* FIXME: handle all undo within buffer to keep it consistent */ + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <assert.h> +#include <limits.h> +#include <stdlib.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> +#include <pango/pangoxft.h> +#include <X11/extensions/Xdbe.h> + +#include "pango-compat.h" +#include "memory.h" +#include "common.h" +#include "txtbuf.h" +#include "undo.h" +#include "cache.h" +#include "theme.h" +#include "window.h" +#include "buffer.h" + +static PangoAttrList *basic_attrs = NULL; + +static void init_line(ledit_view *view, ledit_view_line *line); +static void copy_selection_to_x_primary(ledit_view *view, size_t line1, size_t byte1, size_t line2, size_t byte2); +static void set_pixmap_line_helper(void *data, size_t line, size_t index); +static void invalidate_pixmap_line_helper(void *data, size_t line); +static void set_layout_line_helper(void *data, size_t line, size_t index); +static void invalidate_layout_line_helper(void *data, size_t line); + +/* + * Assign a cache index to line and set text and highlight of the pango layout. + */ +static void set_pango_text_and_highlight(ledit_view *view, size_t 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_view *view, size_t line); + +/* + * Get an attribute list for a text highlight between the given range. + */ +static PangoAttrList *get_pango_attributes(ledit_view *view, size_t start_byte, size_t end_byte); + +/* + * Set the attributes for a PangoLayout belonging to the given line index. + * If the line is part of the view'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_view *view, size_t line, PangoLayout *layout); + +static void swap_sz(size_t *a, size_t *b); + +static int line_visible_callback(void *data, size_t line); +static void set_pixmap_line_helper(void *data, size_t line, size_t index); +static void invalidate_pixmap_line_helper(void *data, size_t line); +static void invalidate_layout_line_helper(void *data, size_t line); + +/* FIXME: This is weird because mode is per-view but the undo mode group + is changed for the entire buffer. */ +void +view_set_mode(ledit_view *view, enum ledit_mode mode) { + view->mode = mode; + ledit_change_mode_group(view->buffer->undo); + ledit_window_set_mode(view->window, mode); +} + +ledit_view * +view_create(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size_t line, size_t pos) { + if (basic_attrs == NULL) { + basic_attrs = pango_attr_list_new(); + #if PANGO_VERSION_CHECK(1, 44, 0) + PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE); + pango_attr_list_insert(basic_attrs, no_hyphens); + #endif + } + + ledit_view *view = ledit_malloc(sizeof(ledit_view)); + view->mode = mode; + view->buffer = buffer; + view->window = ledit_window_create(buffer->common, theme, mode); + view->theme = theme; + view->cache = cache_create(buffer->common->dpy); + view->cur_action = (struct action){ACTION_NONE, NULL}; + ledit_window_set_scroll_callback(view->window, &view_scroll_handler, view); + ledit_window_set_button_callback(view->window, &view_button_handler, view); + ledit_window_set_resize_callback(view->window, &view_resize_textview, view); + + view->lines = ledit_reallocarray(NULL, buffer->lines_cap, sizeof(ledit_view_line)); + view->lines_cap = buffer->lines_cap; + view->lines_gap = buffer->lines_num; + view->lines_num = buffer->lines_num; + for (size_t i = 0; i < view->lines_num; i++) { + init_line(view, &view->lines[i]); + } + view->cur_line = line; + view->cur_index = pos; + view->total_height = 0; + view->display_offset = 0; + view->sel.line1 = view->sel.byte1 = 0; + view->sel.line2 = view->sel.byte2 = 0; + view->selecting = 0; + view->sel_valid = 0; + view->redraw = 1; + ledit_view_line *vl = view_get_line(view, line); + vl->cursor_index = pos; + vl->cursor_index_valid = 1; + + view_recalc_all_lines(view); + + return view; +} + +ledit_view_line * +view_get_line(ledit_view *view, size_t index) { + assert(index < view->lines_num); + return index < view->lines_gap ? + &view->lines[index] : + &view->lines[index + view->lines_cap - view->lines_num]; +} + +static void +move_line_gap(ledit_view *view, size_t index) { + move_gap( + view->lines, sizeof(ledit_view_line), index, + view->lines_gap, view->lines_cap, view->lines_num, + &view->lines_gap + ); +} + +static void +resize_and_move_line_gap(ledit_view *view, size_t min_size, size_t index) { + /* FIXME: Add to common bug list: used sizeof(ledit_line) instead of sizeof(ledit_view_line) */ + view->lines = resize_and_move_gap( + view->lines, sizeof(ledit_view_line), + view->lines_gap, view->lines_cap, view->lines_num, + min_size, index, + &view->lines_gap, &view->lines_cap + ); +} + +void +view_notify_insert_text(ledit_view *view, size_t line, size_t index, size_t len) { + ledit_view_line *vl = view_get_line(view, line); + vl->text_dirty = 1; + if (line == view->cur_line && index < view->cur_index) { + view->cur_index += len; + view_set_line_cursor_attrs(view, line, view->cur_index); + } + if (view->sel_valid) + view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index); +} + +void +view_notify_delete_text(ledit_view *view, size_t line, size_t index, size_t len) { + ledit_view_line *vl = view_get_line(view, line); + vl->text_dirty = 1; + if (line == view->cur_line) { + if (index + len <= view->cur_index) { + view->cur_index -= len; + } else if (index < view->cur_index && index + len > view->cur_index) { + view->cur_index = index; + } + /* just so it isn't stuck at end of line after deletion */ + if (view->mode == NORMAL) { + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, view->cur_index + ); + } + view_set_line_cursor_attrs(view, line, view->cur_index); + } + if (view->sel_valid) + view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index); +} + +void +view_notify_append_line(ledit_view *view, size_t line) { + size_t new_len = view->lines_num + 1; + if (new_len <= view->lines_num) + err_overflow(); + resize_and_move_line_gap(view, new_len, line + 1); + if (line < view->cur_line) + view->cur_line++; + if (view->sel_valid) + view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index); + view->lines_num++; + view->lines_gap++; + ledit_view_line *vl = view_get_line(view, line + 1); + init_line(view, vl); +} + +void +view_notify_delete_lines(ledit_view *view, size_t index1, size_t index2) { + if (index2 < view->cur_line) { + view->cur_line -= index2 - index1 + 1; + } else if (index1 <= view->cur_line) { + /* FIXME: set cur_index properly */ + if (index2 < view->lines_num - 1) { + view->cur_line = index2 + 1; + view->cur_index = 0; + } else if (index1 > 0) { + view->cur_line = index1 - 1; + view->cur_index = 0; + } else { + /* should never happen */ + view->cur_line = 0; + view->cur_index = 0; + } + } + if (view->sel_valid) + view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index); + cache_invalidate_from_line( + view->cache, index1, view, + &invalidate_pixmap_line_helper, &invalidate_layout_line_helper + ); + move_line_gap(view, index1); + view->lines_num -= index2 - index1 + 1; + /* force first entry to offset 0 if first line was deleted */ + if (index1 == 0) { + ledit_view_line *vl = view_get_line(view, 0); + vl->y_offset = 0; + } +} + +void +view_destroy(ledit_view *view) { + cache_destroy(view->cache); + ledit_window_destroy(view->window); + free(view->lines); + free(view); +} + +void +view_cleanup(void) { + if (basic_attrs) + pango_attr_list_unref(basic_attrs); +} + +static PangoAttrList * +get_pango_attributes(ledit_view *view, size_t start_byte, size_t end_byte) { + XRenderColor fg = view->theme->text_fg.color; + XRenderColor bg = view->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_view *view, size_t line, PangoLayout *layout) { + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + ledit_view_line *vl = view_get_line(view, line); + PangoAttrList *list = NULL; + if (view->sel_valid) { + ledit_range sel = view->sel; + view_sort_selection(&sel.line1, &sel.byte1, &sel.line2, &sel.byte2); + if (sel.line1 < line && sel.line2 > line) { + list = get_pango_attributes(view, 0, ll->len); + } else if (sel.line1 == line && sel.line2 == line) { + size_t start = sel.byte1, end = sel.byte2; + if (start > end) + swap_sz(&start, &end); + list = get_pango_attributes(view, start, end); + } else if (sel.line1 == line && sel.line2 > line) { + list = get_pango_attributes(view, sel.byte1, ll->len); + } else if (sel.line1 < line && sel.line2 == line) { + list = get_pango_attributes(view, 0, sel.byte2); + } + } else if (vl->cursor_index_valid) { + /* FIXME: does just adding one really do the right thing? */ + list = get_pango_attributes(view, vl->cursor_index, vl->cursor_index + 1); + } + if (list != NULL) { + pango_layout_set_attributes(layout, list); + pango_attr_list_unref(list); + } else { + pango_layout_set_attributes(layout, basic_attrs); + } + vl->highlight_dirty = 0; + vl->dirty = 1; +} + +void +view_set_line_cursor_attrs(ledit_view *view, size_t line, size_t index) { + ledit_view_line *ll = view_get_line(view, line); + ll->cursor_index = index; + ll->cursor_index_valid = 1; + ll->highlight_dirty = 1; + ll->dirty = 1; + view->redraw = 1; +} + +void +view_wipe_line_cursor_attrs(ledit_view *view, size_t line) { + ledit_view_line *vl = view_get_line(view, line); + vl->cursor_index = 0; + vl->cursor_index_valid = 0; + vl->highlight_dirty = 1; + vl->dirty = 1; + view->redraw = 1; +} + +static int +line_visible_callback(void *data, size_t line) { + return view_line_visible((ledit_view*)data, line); +} + +/* FIXME: standardize variable names (line/line_index, etc.) */ +void +view_render_line(ledit_view *view, size_t line_index) { + /* FIXME: check for <= 0 on size */ + ledit_view_line *ll = view_get_line(view, line_index); + assert(!ll->h_dirty); /* FIXME */ + PangoLayout *layout = get_pango_layout(view, line_index); + if (!ll->cache_pixmap_valid) { + cache_assign_pixmap_index( + view->cache, line_index, view, + &line_visible_callback, &set_pixmap_line_helper, + &invalidate_pixmap_line_helper + ); + } + cache_pixmap *pix = cache_get_pixmap(view->cache, ll->cache_pixmap_index); + /* FIXME: fail on too large pixmap size (e.g. way too long line) */ + /* FIXME: sensible default pixmap sizes here */ + /* FIXME: handle this in cache */ + if (pix->pixmap == None || pix->draw == NULL) { + pix->pixmap = XCreatePixmap( + view->buffer->common->dpy, view->window->drawable, + ll->w + 10, ll->h + 10, view->buffer->common->depth + ); + pix->w = ll->w + 10; + pix->h = ll->h + 10; + pix->draw = XftDrawCreate( + view->buffer->common->dpy, pix->pixmap, + view->buffer->common->vis, view->buffer->common->cm + ); + } 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(view->buffer->common->dpy, pix->pixmap); + pix->pixmap = XCreatePixmap( + view->buffer->common->dpy, view->window->drawable, + new_w, new_h, view->buffer->common->depth + ); + pix->w = new_w; + pix->h = new_h; + XftDrawChange(pix->draw, pix->pixmap); + } + XftDrawRect(pix->draw, &view->theme->text_bg, 0, 0, ll->w, ll->h); + pango_xft_render_layout(pix->draw, &view->theme->text_fg, layout, 0, 0); + ll->dirty = 0; +} + +static void +init_line(ledit_view *view, ledit_view_line *line) { + int text_w, text_h; + ledit_window_get_textview_size(view->window, &text_w, &text_h); + line->view = view; + line->w = text_w; + line->h = 0; + line->y_offset = 0; + line->cache_pixmap_index = 0; + line->cache_layout_index = 0; + line->softlines = 0; + line->cursor_index = 0; + line->cursor_index_valid = 0; + line->cache_pixmap_valid = 0; + line->cache_layout_valid = 0; + line->dirty = 1; + line->text_dirty = 1; + line->highlight_dirty = 1; + line->h_dirty = 1; +} + +static void +set_pixmap_line_helper(void *data, size_t line, size_t index) { + ledit_view_line *vl = view_get_line((ledit_view *)data, line); + vl->cache_pixmap_index = index; + vl->cache_pixmap_valid = 1; +} + +static void +invalidate_pixmap_line_helper(void *data, size_t line) { + ledit_view_line *vl = view_get_line((ledit_view *)data, line); + vl->cache_pixmap_valid = 0; +} + +static void +set_layout_line_helper(void *data, size_t line, size_t index) { + ledit_view_line *vl = view_get_line((ledit_view *)data, line); + vl->cache_layout_index = index; + vl->cache_layout_valid = 1; +} + +static void +invalidate_layout_line_helper(void *data, size_t line) { + ledit_view_line *vl = view_get_line((ledit_view *)data, line); + vl->cache_layout_valid = 0; +} + +/* set text of pango layout if dirty and recalculate height of line + * - if height hasn't changed, nothing further is done + * - if height has changed, offset of all following lines is changed */ +void +view_recalc_line(ledit_view *view, size_t line) { + ledit_view_line *l = view_get_line(view, line); + if (l->text_dirty) + set_pango_text_and_highlight(view, line); + + int text_w, text_h; + ledit_window_get_textview_size(view->window, &text_w, &text_h); + /* if height changed, set height of current line + * and adjust offsets of all lines following it */ + if (l->h_dirty) { + l->h_dirty = 0; + long off = l->y_offset + l->h; + for (size_t i = line + 1; i < view->lines_num; i++) { + l = view_get_line(view, i); + l->y_offset = off; + off += l->h; + } + view->total_height = off; + if (l->y_offset < view->display_offset + text_h) + view->redraw = 1; + } + l = view_get_line(view, line); + if (l->y_offset < view->display_offset + text_h && + l->y_offset + l->h >= view->display_offset) { + view->redraw = 1; + } + ledit_window_set_scroll_max(view->window, view->total_height); + view_scroll(view, view->display_offset); +} + +/* set text of pango layout and recalculate height + * and offset for all lines starting at 'line' */ +void +view_recalc_from_line(ledit_view *view, size_t line) { + ledit_view_line *l = view_get_line(view, line); + int text_w, text_h; + ledit_window_get_textview_size(view->window, &text_w, &text_h); + long off = l->y_offset; + if (off < view->display_offset + text_h) + view->redraw = 1; + for (size_t i = line; i < view->lines_num; i++) { + l = view_get_line(view, i); + if (l->text_dirty) + set_pango_text_and_highlight(view, i); + l->h_dirty = 0; + l->y_offset = off; + off += l->h; + } + view->total_height = off; + ledit_window_set_scroll_max(view->window, view->total_height); + view_scroll(view, view->display_offset); +} + +void +view_recalc_all_lines(ledit_view *view) { + /* force first line to offset 0 */ + ledit_view_line *l = view_get_line(view, 0); + l->y_offset = 0; + view_recalc_from_line(view, 0); +} + +int +view_line_visible(ledit_view *view, size_t index) { + int text_w, text_h; + ledit_window_get_textview_size(view->window, &text_w, &text_h); + ledit_view_line *l = view_get_line(view, index); + return l->y_offset < view->display_offset + text_h && + l->y_offset + l->h > view->display_offset; +} + +/* FIXME: these functions are only here because they need the PangoLayouts to + determine grapheme boundaries. Maybe use a separate library for that? */ +size_t +view_next_cursor_pos(ledit_view *view, size_t line, size_t byte, int num) { + int nattrs; + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + size_t c = line_byte_to_char(ll, byte); + size_t cur_byte = byte; + PangoLayout *layout = get_pango_layout(view, line); + const PangoLogAttr *attrs = + pango_layout_get_log_attrs_readonly(layout, &nattrs); + if (num < 0) + return 0; /* FIXME: error */ + for (size_t i = 0; i < (size_t)num; i++) { + cur_byte = ledit_line_next_utf8(ll, byte); + for (c++; c < (size_t)nattrs; c++) { + if (attrs[c].is_cursor_position) + break; + cur_byte = ledit_line_next_utf8(ll, cur_byte); + } + if (cur_byte >= ll->len) + break; + } + return cur_byte <= ll->len ? cur_byte : ll->len; +} + +size_t +view_prev_cursor_pos(ledit_view *view, size_t line, size_t byte, int num) { + int nattrs; + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + size_t c = line_byte_to_char(ll, byte); + size_t cur_byte = byte; + PangoLayout *layout = get_pango_layout(view, line); + const PangoLogAttr *attrs = + pango_layout_get_log_attrs_readonly(layout, &nattrs); + if (num < 0) + return 0; /* FIXME: error */ + for (int i = 0; i < num; i++) { + cur_byte = ledit_line_prev_utf8(ll, cur_byte); + for (; c > 0; c--) { + if (attrs[c-1].is_cursor_position) + break; + cur_byte = ledit_line_prev_utf8(ll, cur_byte); + } + if (cur_byte <= 0) + break; + } + return cur_byte > 0 ? cur_byte : 0; +} + +static int +line_next_word( + ledit_view *view, + size_t line, size_t byte, size_t char_index, int wrapped_line, + size_t *char_ret, size_t *byte_ret, size_t *real_byte_ret) { + int nattrs; + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + int cur_byte = wrapped_line ? byte : ledit_line_next_utf8(ll, byte); + PangoLayout *layout = get_pango_layout(view, line); + const PangoLogAttr *attrs = + pango_layout_get_log_attrs_readonly(layout, &nattrs); + for (size_t i = wrapped_line ? char_index : char_index + 1; i < (size_t)nattrs; i++) { + if (attrs[i].is_word_start) { + *char_ret = i; + *real_byte_ret = cur_byte; + *byte_ret = cur_byte; + return 0; + } + cur_byte = ledit_line_next_utf8(ll, cur_byte); + } + return -1; +} + +static int +line_prev_word( + ledit_view *view, + size_t line, size_t byte, size_t char_index, + size_t *char_ret, size_t *byte_ret) { + int nattrs; + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + size_t cur_byte = ledit_line_prev_utf8(ll, byte); + PangoLayout *layout = get_pango_layout(view, line); + const PangoLogAttr *attrs = + pango_layout_get_log_attrs_readonly(layout, &nattrs); + if (char_index > (size_t)nattrs) + return -1; + /* this is a bit weird because size_t can't be negative */ + for (size_t i = char_index; i > 0; i--) { + if (attrs[i-1].is_word_start) { + *char_ret = i-1; + *byte_ret = cur_byte; + return 0; + } + cur_byte = ledit_line_prev_utf8(ll, cur_byte); + } + return -1; +} + +static int +line_prev_bigword( + ledit_view *view, + size_t line, size_t byte, size_t char_index, + size_t *char_ret, size_t *byte_ret) { + int nattrs; + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + size_t cur_byte = ledit_line_prev_utf8(ll, byte); + PangoLayout *layout = get_pango_layout(view, line); + const PangoLogAttr *attrs = + pango_layout_get_log_attrs_readonly(layout, &nattrs); + size_t next_cursorb = byte; + size_t next_cursorc = char_index; + int found_word = 0; + /* this is a bit weird because size_t can't be negative */ + for (size_t i = char_index; i > 0; i--) { + if (!found_word && !attrs[i-1].is_white) { + found_word = 1; + } else if (found_word && attrs[i-1].is_white) { + *char_ret = next_cursorc; + *byte_ret = next_cursorb; + return 0; + } + if (found_word && i-1 == 0) { + *char_ret = 0; + *byte_ret = 0; + return 0; + } + if (attrs[i-1].is_cursor_position) { + next_cursorc = i-1; + next_cursorb = cur_byte; + } + cur_byte = ledit_line_prev_utf8(ll, cur_byte); + } + return -1; +} + +static int +line_next_bigword_end( + ledit_view *view, + size_t line, size_t byte, size_t char_index, int wrapped_line, + size_t *char_ret, size_t *byte_ret, size_t *real_byte_ret) { + int nattrs; + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + PangoLayout *layout = get_pango_layout(view, line); + const PangoLogAttr *attrs = + pango_layout_get_log_attrs_readonly(layout, &nattrs); + size_t last_cursorb = 0, last_cursorc = 0; + int last_cursor_valid; + if (wrapped_line) { + last_cursorb = byte; + last_cursorc = char_index; + last_cursor_valid = 1; + } else { + last_cursor_valid = 0; + } + int found_word = 0; + size_t cur_byte = byte; + for (size_t i = char_index; i < (size_t)nattrs; i++) { + if (last_cursor_valid && !found_word && !attrs[i].is_white) { + found_word = 1; + } else if (found_word && attrs[i].is_white) { + *char_ret = last_cursorc; + *real_byte_ret = cur_byte; + *byte_ret = last_cursorb; + return 0; + } + if (attrs[i].is_cursor_position) { + last_cursorc = i; + last_cursorb = cur_byte; + last_cursor_valid = 1; + } + cur_byte = ledit_line_next_utf8(ll, cur_byte); + } + return -1; +} + +static int +line_next_word_end( + ledit_view *view, + size_t line, size_t byte, size_t char_index, int wrapped_line, + size_t *char_ret, size_t *byte_ret, size_t *real_byte_ret) { + int nattrs; + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + size_t cur_byte = ledit_line_next_utf8(ll, byte); + PangoLayout *layout = get_pango_layout(view, line); + const PangoLogAttr *attrs = + pango_layout_get_log_attrs_readonly(layout, &nattrs); + size_t last_cursorb = 0, last_cursorc = 0; + int last_cursor_valid; + if (wrapped_line) { + last_cursorb = byte; + last_cursorc = char_index; + last_cursor_valid = 1; + } else { + last_cursor_valid = 0; + } + for (size_t i = char_index + 1; i < (size_t)nattrs; i++) { + if (last_cursor_valid && attrs[i].is_word_end) { + *char_ret = last_cursorc; + *real_byte_ret = cur_byte; + *byte_ret = last_cursorb; + return 0; + } + if (attrs[i].is_cursor_position) { + last_cursorc = i; + last_cursorb = cur_byte; + last_cursor_valid = 1; + } + cur_byte = ledit_line_next_utf8(ll, cur_byte); + } + return -1; +} + +static int +line_next_bigword( + ledit_view *view, + size_t line, size_t byte, size_t char_index, int wrapped_line, + size_t *char_ret, size_t *byte_ret, size_t *real_byte_ret) { + int nattrs; + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + size_t cur_byte = byte; + PangoLayout *layout = get_pango_layout(view, line); + const PangoLogAttr *attrs = + pango_layout_get_log_attrs_readonly(layout, &nattrs); + int found_ws = wrapped_line; + for (size_t i = char_index; i < (size_t)nattrs; i++) { + if (!found_ws && attrs[i].is_white) { + found_ws = 1; + } else if (found_ws && !attrs[i].is_white) { + *char_ret = i; + *real_byte_ret = cur_byte; + *byte_ret = cur_byte; + return 0; + } + cur_byte = ledit_line_next_utf8(ll, cur_byte); + } + return -1; +} + +size_t +view_line_next_non_whitespace(ledit_view *view, size_t line, size_t byte) { + int nattrs; + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + size_t c = line_byte_to_char(ll, byte); + size_t cur_byte = byte; + PangoLayout *layout = get_pango_layout(view, line); + const PangoLogAttr *attrs = + pango_layout_get_log_attrs_readonly(layout, &nattrs); + for (; c < (size_t)nattrs; c++) { + if (!attrs[c].is_white) + return cur_byte; + cur_byte = ledit_line_next_utf8(ll, cur_byte); + } + return ll->len; +} + +/* FIXME: document that word and bigword are a bit weird because word uses unicode semantics */ + +#define GEN_NEXT_WORD(name, func) \ +void \ +view_next_##name( \ + ledit_view *view, \ + size_t line, size_t byte, int num_repeat, \ + size_t *line_ret, size_t *byte_ret, size_t *real_byte_ret) { \ + size_t cur_line = line; \ + size_t cur_byte = byte; \ + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); \ + size_t cur_char = line_byte_to_char(ll, byte); \ + size_t real_byte = 0; \ + int last_ret = -1; \ + int wrapped_line; \ + for (int i = 0; i < num_repeat; i++) { \ + wrapped_line = 0; \ + while ((last_ret = func(view, cur_line, cur_byte, cur_char, \ + wrapped_line, &cur_char, &cur_byte, &real_byte)) == -1 && \ + cur_line < view->lines_num - 1) { \ + cur_line++; \ + cur_byte = 0; \ + wrapped_line = 1; \ + } \ + if (last_ret == -1 && cur_line == view->lines_num - 1) \ + break; \ + } \ + if (last_ret == -1) { \ + *line_ret = view->lines_num - 1; \ + ledit_line *ll = ledit_buffer_get_line(view->buffer, view->lines_num - 1); \ + *byte_ret = view_get_legal_normal_pos(view, view->lines_num - 1, ll->len); \ + *real_byte_ret = ll->len; \ + } else { \ + *line_ret = cur_line; \ + *byte_ret = cur_byte; \ + *real_byte_ret = real_byte; \ + } \ +} + +#define GEN_PREV_WORD(name, func) \ +void \ +view_prev_##name( \ + ledit_view *view, \ + size_t line, size_t byte, int num_repeat, \ + size_t *line_ret, size_t *byte_ret, size_t *real_byte_ret) { \ + size_t cur_line = line; \ + size_t cur_byte = byte; \ + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); \ + size_t cur_char = line_byte_to_char(ll, byte); \ + int last_ret = -1; \ + for (int i = 0; i < num_repeat; i++) { \ + while ((last_ret = func(view, cur_line, cur_byte, cur_char, \ + &cur_char, &cur_byte)) == -1 && cur_line > 0) { \ + cur_line--; \ + ll = ledit_buffer_get_line(view->buffer, cur_line); \ + cur_byte = ll->len; \ + } \ + if (last_ret == -1 && cur_line == 0) \ + break; \ + } \ + if (last_ret == -1) { \ + *line_ret = 0; \ + *byte_ret = 0; \ + *real_byte_ret = 0; \ + } else { \ + *line_ret = cur_line; \ + *byte_ret = cur_byte; \ + *real_byte_ret = cur_byte; \ + } \ +} + +GEN_NEXT_WORD(word, line_next_word) +GEN_NEXT_WORD(word_end, line_next_word_end) +GEN_NEXT_WORD(bigword, line_next_bigword) +GEN_NEXT_WORD(bigword_end, line_next_bigword_end) +GEN_PREV_WORD(word, line_prev_word) +GEN_PREV_WORD(bigword, line_prev_bigword) + +void +view_get_pos_softline_bounds( + ledit_view *view, size_t line, size_t pos, + size_t *start_byte_ret, size_t *end_byte_ret) { + assert(line < view->lines_num); + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + assert(pos <= ll->len); + PangoLayout *layout = get_pango_layout(view, line); + int x, sli; + if (pos > INT_MAX) + err_overflow(); + pango_layout_index_to_line_x(layout, (int)pos, 0, &sli, &x); + PangoLayoutLine *pl = pango_layout_get_line_readonly(layout, sli); + *start_byte_ret = (size_t)pl->start_index; + *end_byte_ret = (size_t)(pl->start_index + pl->length); +} + +void +view_get_softline_bounds( + ledit_view *view, size_t line, int softline, + size_t *start_byte_ret, size_t *end_byte_ret) { + assert(line < view->lines_num); + ledit_view_line *vl = view_get_line(view, line); + PangoLayout *layout = get_pango_layout(view, line); + assert(softline < vl->softlines); + PangoLayoutLine *pl = pango_layout_get_line_readonly(layout, softline); + *start_byte_ret = (size_t)pl->start_index; + *end_byte_ret = (size_t)(pl->start_index + pl->length); +} + +int +view_get_softline_count(ledit_view *view, size_t line) { + assert(line < view->lines_num); + ledit_view_line *vl = view_get_line(view, line); + if (vl->text_dirty) + set_pango_text_and_highlight(view, line); + return vl->softlines; +} + +int +view_pos_to_softline(ledit_view *view, size_t line, size_t pos) { + assert(line < view->lines_num); + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + assert(pos <= ll->len); + PangoLayout *layout = get_pango_layout(view, line); + int x, sli; + if (pos > INT_MAX) + err_overflow(); + pango_layout_index_to_line_x(layout, (int)pos, 0, &sli, &x); + return sli; +} + +void +view_get_cursor_pixel_pos(ledit_view *view, size_t line, size_t pos, int *x_ret, int *y_ret, int *h_ret) { + assert(line < view->lines_num); + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + assert(pos <= ll->len); + PangoLayout *layout = get_pango_layout(view, line); + PangoRectangle strong, weak; + if (pos > INT_MAX) + err_overflow(); + pango_layout_get_cursor_pos(layout, (int)pos, &strong, &weak); + *x_ret = strong.x / PANGO_SCALE; + *y_ret = strong.y / PANGO_SCALE; + *h_ret = strong.height / PANGO_SCALE; +} + +/* prev_index_ret is used instead of just calling get_legal_normal_pos + because weird things happen otherwise + -> in certain cases, this is still weird because prev_index_ret sometimes + is not at the end of the line, but this is the best I could come up + with for now */ +size_t +view_move_cursor_visually(ledit_view *view, size_t line, size_t pos, int movement, size_t *prev_index_ret) { + if (pos > INT_MAX) + err_overflow(); + /* FIXME: trailing */ + int trailing = 0; + ledit_line *cur_line = ledit_buffer_get_line(view->buffer, line); + PangoLayout *layout = get_pango_layout(view, line); + int tmp_index; + int new_index = (int)pos, last_index = (int)pos; + int dir = 1; + int num = movement; + if (movement < 0) { + dir = -1; + num = -movement; + } + /* FIXME: This is stupid. Anything outside the range of int won't work + anyways because of pango (and because everything else would break + anyways with such long lines), so it's stupid to do all this weird + casting. */ + if (cur_line->len > INT_MAX) + err_overflow(); + while (num > 0) { + tmp_index = new_index; + pango_layout_move_cursor_visually( + layout, TRUE, + new_index, trailing, dir, + &new_index, &trailing + ); + /* for some reason, this is necessary */ + if (new_index < 0) + new_index = 0; + else if (new_index > (int)cur_line->len) + new_index = (int)cur_line->len; + num--; + if (tmp_index != new_index) + last_index = tmp_index; + } + /* FIXME: Allow cursor to be at end of soft line */ + /* we don't currently support a difference between the cursor being at + the end of a soft line and the beginning of the next line */ + /* FIXME: spaces at end of softlines are weird in normal mode */ + while (trailing > 0) { + trailing--; + new_index = ledit_line_next_utf8(cur_line, new_index); + } + if (new_index < 0) + new_index = 0; + if (prev_index_ret) + *prev_index_ret = (size_t)last_index; + return (size_t)new_index; +} + +/* FIXME: implement */ +/* +int +ledit_line_nearest_cursor_pos(ledit_line *line, int byte) { +} + +void +ledit_line_word_boundaries(ledit_line *line, int byte, int *start_ret, int *end_ret) { +} +*/ + +static void +set_pango_text_and_highlight(ledit_view *view, size_t line) { + cache_layout *cl; + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + ledit_view_line *vl = view_get_line(view, line); + char old_valid = vl->cache_layout_valid; + if (!vl->cache_layout_valid) { + cache_assign_layout_index( + view->cache, line, + view, &set_layout_line_helper, &invalidate_layout_line_helper + ); + cl = cache_get_layout(view->cache, vl->cache_layout_index); + } else { + cl = cache_get_layout(view->cache, vl->cache_layout_index); + } + if (cl->layout == NULL) { + cl->layout = pango_layout_new(view->window->context); + pango_layout_set_font_description(cl->layout, view->window->font); + pango_layout_set_wrap(cl->layout, PANGO_WRAP_WORD_CHAR); + } + if (vl->text_dirty || !old_valid) { + ledit_buffer_normalize_line(ll); + if (ll->len > INT_MAX) + err_overflow(); + pango_layout_set_text(cl->layout, ll->text, (int)ll->len); + set_line_layout_attrs(view, line, cl->layout); + /* FIXME: is this guard necessary? */ + vl->softlines = ll->len > 0 ? pango_layout_get_line_count(cl->layout) : 1; + pango_layout_set_width(cl->layout, vl->w * PANGO_SCALE); + int w, h; + pango_layout_get_pixel_size(cl->layout, &w, &h); + if (h != vl->h) { + vl->h = h; + vl->h_dirty = 1; + } + vl->text_dirty = 0; + vl->dirty = 1; + } else if (vl->highlight_dirty) { + set_line_layout_attrs(view, line, cl->layout); + } + vl->highlight_dirty = 0; +} + +static PangoLayout * +get_pango_layout(ledit_view *view, size_t line) { + set_pango_text_and_highlight(view, line); + ledit_view_line *vl = view_get_line(view, line); + cache_layout *cl = cache_get_layout( + view->cache, vl->cache_layout_index + ); + return cl->layout; +} + +/* FIXME: document what works with pango units and what not */ +void +ledit_pos_to_x_softline(ledit_view *view, size_t line, size_t pos, int *x_ret, int *softline_ret) { + ledit_view_line *vl = view_get_line(view, line); + PangoLayout *layout = get_pango_layout(view, line); + if (pos > INT_MAX) + err_overflow(); + pango_layout_index_to_line_x(layout, (int)pos, 0, softline_ret, x_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 += (vl->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 (view->mode == NORMAL) { + PangoRectangle rect; + pango_layout_index_to_pos(layout, (int)pos, &rect); + *x_ret += rect.width / 2; + } +} + +/* FIXME: change this to return pos_ret directly */ +void +ledit_x_softline_to_pos(ledit_view *view, size_t line, int x, int softline, size_t *pos_ret) { + int trailing = 0; + int x_relative = x; + ledit_view_line *vl = view_get_line(view, line); + PangoLayout *layout = get_pango_layout(view, line); + PangoLayoutLine *pango_line = + 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 -= (vl->w * PANGO_SCALE - rect.width); + } + int tmp_pos; + pango_layout_line_x_to_index( + pango_line, x_relative, &tmp_pos, &trailing + ); + *pos_ret = (size_t)tmp_pos; + /* if in insert mode, snap to the nearest border between graphemes */ + /* FIXME: add parameter for this instead of checking mode */ + if (view->mode == INSERT) { + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + while (trailing > 0) { + trailing--; + *pos_ret = ledit_line_next_utf8(ll, *pos_ret); + } + } +} + +size_t +view_get_legal_normal_pos(ledit_view *view, size_t line, size_t pos) { + /* move back one grapheme if at end of line */ + size_t ret = pos; + ledit_line *ll = ledit_buffer_get_line(view->buffer, line); + if (pos == ll->len && pos > 0) { + int nattrs; + PangoLayout *layout = get_pango_layout(view, line); + const PangoLogAttr *attrs = + pango_layout_get_log_attrs_readonly(layout, &nattrs); + if (nattrs < 2) + return 0; + size_t cur = nattrs - 2; + ret = ledit_line_prev_utf8(ll, ret); + while (ret > 0 && cur > 0 && !attrs[cur].is_cursor_position) { + cur--; + ret = ledit_line_prev_utf8(ll, ret); + } + } + return ret; +} + +void +view_delete_range( + ledit_view *view, enum delete_mode delmode, + size_t line_index1, size_t byte_index1, + size_t line_index2, size_t byte_index2, + size_t *new_line_ret, size_t *new_byte_ret, + ledit_range *final_range_ret, txtbuf *text_ret) { + view_delete_range_base( + view, delmode, + line_index1, byte_index1, + line_index2, byte_index2, + new_line_ret, new_byte_ret, + final_range_ret, text_ret + ); + /* need to start recalculating one line before in case first + line was deleted and offset is now wrong */ + size_t min = line_index1 < line_index2 ? line_index1 : line_index2; + /* FIXME: a bit ugly to do this here */ + for (size_t i = 0; i < view->buffer->views_num; i++) { + ledit_buffer_recalc_all_views_from_line( + view->buffer, min > 0 ? min - 1 : min + ); + } +} + +/* Note: line_index* and byte_index* don't need to be sorted */ +/* line_index1, byte_index1 are used as the cursor position in order + to determine the new cursor position */ +/* FIXME: use at least somewhat sensible variable names */ +/* FIXME: I once noticed a bug where using 'dG' to delete to the end of + the file caused a line index way larger than buffer->lines_num to be + given, but I couldn't reproduce this bug */ +void +view_delete_range_base( + ledit_view *view, enum delete_mode delmode, + size_t line_index1, size_t byte_index1, + size_t line_index2, size_t byte_index2, + size_t *new_line_ret, size_t *new_byte_ret, + ledit_range *final_range_ret, txtbuf *text_ret) { + /* FIXME: Oh boy, this is nasty */ + /* range line x, range byte x */ + size_t rgl1 = 0, rgb1 = 0, rgl2 = 0, rgb2 = 0; + size_t new_line = 0, new_byte = 0; + assert(line_index1 < view->lines_num); + assert(line_index2 < view->lines_num); + /* FIXME: could this be simplified by just calculating the range and then using + the non-line-based version? */ + if (delmode == DELETE_HARDLINE) { + int x, sl_useless; + size_t l1 = line_index1, l2 = line_index2; + if (line_index1 > line_index2) { + l1 = line_index2; + l2 = line_index1; + } + size_t dell1 = l1, dell2 = l2; + ledit_line *ll = ledit_buffer_get_line(view->buffer, line_index1); + ledit_pos_to_x_softline(view, line_index1, byte_index1, &x, &sl_useless); + if (l1 > 0 && l2 < view->lines_num - 1) { + rgl1 = l1; + rgb1 = 0; + rgl2 = l2 + 1; + rgb2 = 0; + } else if (l1 > 0) { + rgl1 = l1 - 1; + ll = ledit_buffer_get_line(view->buffer, rgl1); + rgb1 = ll->len; + rgl2 = l2; + ll = ledit_buffer_get_line(view->buffer, rgl2); + rgb2 = ll->len; + } else if (l2 < view->lines_num - 1) { + rgl1 = l1; + rgb1 = 0; + rgl2 = l2 + 1; + rgb2 = 0; + } else { + rgl1 = l1; + rgb1 = 0; + rgl2 = l2; + ll = ledit_buffer_get_line(view->buffer, rgl2); + rgb2 = ll->len; + } + if (text_ret) { + ledit_buffer_copy_text_to_txtbuf( + view->buffer, text_ret, + rgl1, rgb1, rgl2, rgb2 + ); + } + /* default is dell1 = l1, dell2 = l2 */ + if (l2 < view->lines_num - 1) { + new_line = l1; + ledit_x_softline_to_pos( + view, l2 + 1, + x, 0, &new_byte + ); + } else if (l1 > 0) { + new_line = l1 - 1; + ledit_x_softline_to_pos( + view, l1 - 1, + x, 0, &new_byte + ); + } else { + dell1 = l1 + 1; + dell2 = l2; + new_line = l1; + new_byte = 0; + /* happens when all lines are deleted, so one line has to be cleared */ + ll = ledit_buffer_get_line(view->buffer, l1); + ledit_buffer_delete_line_section_base( + view->buffer, l1, 0, ll->len + ); + } + if (dell1 <= dell2) { + ledit_buffer_delete_line_entries_base(view->buffer, dell1, dell2); + } + } else if (delmode == DELETE_SOFTLINE) { + int x, softline1, softline2; + ledit_line *line1 = ledit_buffer_get_line(view->buffer, line_index1); + ledit_view_line *vline1 = view_get_line(view, line_index1); + ledit_pos_to_x_softline(view, line_index1, byte_index1, &x, &softline1); + if (line_index1 == line_index2) { + int x_useless; + PangoLayout *layout = get_pango_layout(view, 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; + 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 == vline1->softlines - 1 && view->lines_num > 1) { + if (line_index1 < view->lines_num - 1) { + /* cursor can be moved to next hard line */ + new_line = line_index1; + size_t tmp_byte; + ledit_x_softline_to_pos( + view, line_index1 + 1, + x, 0, &tmp_byte + ); + new_byte = (size_t)tmp_byte; + rgl1 = line_index1; + rgb1 = 0; + rgl2 = line_index1 + 1; + rgb2 = 0; + } else { + /* cursor has to be be moved to previous hard line + because last line in buffer is deleted */ + /* note: logically, line_index1 - 1 must be >= 0 because + view->lines_num > 1 && line_index1 >= view->lines_num - 1 */ + new_line = line_index1 - 1; + ledit_line *prevline = ledit_buffer_get_line(view->buffer, new_line); + ledit_view_line *vprevline = view_get_line(view, new_line); + if (vprevline->text_dirty) + set_pango_text_and_highlight(view, new_line); + ledit_x_softline_to_pos(view, new_line, x, vprevline->softlines - 1, &new_byte); + rgl1 = line_index1 - 1; + rgb1 = prevline->len; + rgl2 = line_index1; + rgb2 = line1->len; + } + if (text_ret) { + ledit_buffer_copy_text_to_txtbuf( + view->buffer, text_ret, + rgl1, rgb1, + rgl2, rgb2 + ); + } + ledit_buffer_delete_line_entry_base(view->buffer, line_index1); + } else { + assert(pl2->start_index + pl2->length >= pl1->start_index); + rgl1 = rgl2 = line_index1; + rgb1 = (size_t)pl1->start_index; + rgb2 = (size_t)(pl2->start_index + pl2->length); + if (text_ret) { + ledit_buffer_copy_text_to_txtbuf( + view->buffer, text_ret, + rgl1, rgb1, + rgl2, rgb2 + ); + } + ledit_buffer_delete_line_section_base( + view->buffer, line_index1, rgb1, rgb2 - rgb1 + ); + if (l2 == vline1->softlines - 1 && line_index1 < view->lines_num - 1) { + new_line = line_index1 + 1; + size_t tmp_byte; + ledit_x_softline_to_pos( + view, line_index1 + 1, + x, 0, &tmp_byte + ); + new_byte = (size_t)tmp_byte; + } else if (l2 < vline1->softlines - 1) { + new_line = line_index1; + size_t tmp_byte; + ledit_x_softline_to_pos( + view, line_index1, + x, l1, &tmp_byte + ); + new_byte = (size_t)tmp_byte; + } else if (l1 > 0) { + new_line = line_index1; + size_t tmp_byte; + ledit_x_softline_to_pos( + view, line_index1, + x, l1 - 1, &tmp_byte + ); + new_byte = (size_t)tmp_byte; + } else { + /* the line has been emptied and is the last line remaining */ + new_line = 0; + new_byte = 0; + } + } + } else { + int x_useless, sl1, sl2; + size_t l1, l2, b1, b2; + if (line_index1 < line_index2) { + l1 = line_index1; + b1 = byte_index1; + l2 = line_index2; + b2 = byte_index2; + } else { + l1 = line_index2; + b1 = byte_index2; + l2 = line_index1; + b2 = byte_index1; + } + ledit_line *ll1 = ledit_buffer_get_line(view->buffer, l1); + ledit_line *ll2 = ledit_buffer_get_line(view->buffer, l2); + ledit_view_line *vl1 = view_get_line(view, l1); + ledit_view_line *vl2 = view_get_line(view, l2); + PangoLayout *layout1 = get_pango_layout(view, l1); + PangoLayout *layout2 = get_pango_layout(view, 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 == vl2->softlines - 1) { + if (l1 == 0 && l2 == view->lines_num - 1) { + rgl1 = l1; + rgl2 = l2; + rgb1 = 0; + rgb2 = ll2->len; + if (text_ret) { + ledit_buffer_copy_text_to_txtbuf( + view->buffer, text_ret, + rgl1, rgb1, + rgl2, rgb2 + ); + } + ledit_buffer_delete_line_section_base(view->buffer, l1, 0, ll1->len); + ledit_buffer_delete_line_entries_base(view->buffer, l1 + 1, l2); + new_line = 0; + new_byte = 0; + } else { + if (l2 == view->lines_num - 1) { + new_line = l1 - 1; + ledit_line *new_lline = ledit_buffer_get_line(view->buffer, new_line); + ledit_view_line *new_vline = view_get_line(view, new_line); + if (new_vline->text_dirty) + set_pango_text_and_highlight(view, new_line); + ledit_x_softline_to_pos(view, new_line, x, new_vline->softlines - 1, &new_byte); + rgl1 = l1 - 1; + rgb1 = new_lline->len; + rgl2 = l2; + rgb2 = ll2->len; + } else { + new_line = l1; + ledit_x_softline_to_pos( + view, l2 + 1, x, 0, &new_byte + ); + rgl1 = l1; + rgb1 = 0; + rgl2 = l2 + 1; + rgb2 = 0; + } + if (text_ret) { + ledit_buffer_copy_text_to_txtbuf( + view->buffer, text_ret, + rgl1, rgb1, + rgl2, rgb2 + ); + } + ledit_buffer_delete_line_entries_base(view->buffer, l1, l2); + } + } else if (sl1 == 0) { + rgl1 = l1; + rgb1 = 0; + rgl2 = l2; + rgb2 = (size_t)(pl2->start_index + pl2->length); + if (text_ret) { + ledit_buffer_copy_text_to_txtbuf( + view->buffer, text_ret, + rgl1, rgb1, + rgl2, rgb2 + ); + } + ledit_buffer_delete_line_section_base(view->buffer, l2, 0, rgb2); + new_line = l1; + ledit_x_softline_to_pos(view, l2, x, 0, &new_byte); + ledit_buffer_delete_line_entries_base(view->buffer, l1, l2 - 1); + } else if (sl2 == vl2->softlines - 1) { + rgl1 = l1; + rgb1 = (size_t)pl1->start_index; + rgl2 = l2; + rgb2 = ll2->len; + if (l2 + 1 == view->lines_num) { + new_line = l1; + ledit_x_softline_to_pos(view, l1, x, sl1 - 1, &new_byte); + } else { + new_line = l1 + 1; + ledit_x_softline_to_pos( + view, l2 + 1, + x, 0, &new_byte + ); + } + if (text_ret) { + ledit_buffer_copy_text_to_txtbuf( + view->buffer, text_ret, + rgl1, rgb1, + rgl2, rgb2 + ); + } + ledit_buffer_delete_line_section_base(view->buffer, l1, rgb1, ll1->len - rgb1); + ledit_buffer_delete_line_entries_base(view->buffer, l1 + 1, l2); + } else { + /* FIXME: this could be made nicer by just using the range to + delete all in one go at the end */ + rgl1 = l1; + rgb1 = (size_t)pl1->start_index; + rgl2 = l2; + rgb2 = (size_t)(pl2->start_index + pl2->length); + if (text_ret) { + ledit_buffer_copy_text_to_txtbuf( + view->buffer, text_ret, + rgl1, rgb1, + rgl2, rgb2 + ); + } + ledit_buffer_delete_line_section_base(view->buffer, l1, rgb1, ll1->len - rgb1); + ledit_buffer_insert_text_from_line_base( + view->buffer, + l1, rgb1, l2, rgb2, + ll2->len - rgb2, NULL + ); + ledit_buffer_delete_line_entries_base(view->buffer, l1 + 1, l2); + new_line = l1; + set_pango_text_and_highlight(view, 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 + a bit weird because the cursor will seem to stay on the + 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( + view, l1, x, sl1 + 1 < vl1->softlines ? sl1 + 1 : sl1, &new_byte + ); + } + } + } else { + if (line_index1 == line_index2) { + rgl1 = rgl2 = line_index1; + if (byte_index1 < byte_index2) { + rgb1 = byte_index1; + rgb2 = byte_index2; + } else { + rgb1 = byte_index2; + rgb2 = byte_index1; + } + if (text_ret) { + ledit_buffer_copy_text_to_txtbuf( + view->buffer, text_ret, + rgl1, rgb1, + rgl2, rgb2 + ); + } + ledit_buffer_delete_line_section_base(view->buffer, line_index1, rgb1, rgb2 - rgb1); + new_line = line_index1; + new_byte = rgb1; + } else { + if (line_index1 < line_index2) { + rgl1 = line_index1; + rgb1 = byte_index1; + rgl2 = line_index2; + rgb2 = byte_index2; + } else { + rgl1 = line_index2; + rgb1 = byte_index2; + rgl2 = line_index1; + rgb2 = byte_index1; + } + if (text_ret) { + ledit_buffer_copy_text_to_txtbuf( + view->buffer, text_ret, + rgl1, rgb1, + rgl2, rgb2 + ); + } + ledit_line *line1 = ledit_buffer_get_line(view->buffer, rgl1); + ledit_line *line2 = ledit_buffer_get_line(view->buffer, rgl2); + ledit_buffer_delete_line_section_base(view->buffer, rgl1, rgb1, line1->len - rgb1); + ledit_buffer_insert_text_from_line_base( + view->buffer, rgl1, rgb1, rgl2, rgb2, line2->len - rgb2, NULL + ); + new_line = rgl1; + new_byte = rgb1; + ledit_buffer_delete_line_entries_base(view->buffer, rgl1 + 1, rgl2); + } + /* FIXME: too much magic - maybe don't include this here */ + if (view->mode == NORMAL) + new_byte = view_get_legal_normal_pos(view, new_line, new_byte); + } + if (final_range_ret) { + final_range_ret->line1 = rgl1; + final_range_ret->byte1 = rgb1; + final_range_ret->line2 = rgl2; + final_range_ret->byte2 = rgb2; + } + if (new_line_ret) + *new_line_ret = new_line; + if (new_byte_ret) + *new_byte_ret = new_byte; +} + +/* FIXME: any way to make this more efficient? */ +void +view_resize_textview(void *data) { + ledit_view *view = (ledit_view *)data; + view->total_height = 0; + int text_w, text_h; + ledit_window_get_textview_size(view->window, &text_w, &text_h); + for (size_t i = 0; i < view->lines_num; i++) { + ledit_view_line *line = view_get_line(view, i); + line->w = text_w; + line->text_dirty = 1; /* it's a bit weird to set this, it should rather be something like 'w_dirty' */ + set_pango_text_and_highlight(view, i); + line->y_offset = view->total_height; + line->dirty = 1; + line->h_dirty = 0; + view->total_height += line->h; + } + ledit_window_set_scroll_max(view->window, view->total_height); + if (view->display_offset > 0 && + view->display_offset + text_h > view->total_height) { + view_scroll(view, view->total_height - text_h); + } +} + +void +view_scroll(ledit_view *view, long new_offset) { + int text_w, text_h; + ledit_window_get_textview_size(view->window, &text_w, &text_h); + if (new_offset + text_h > view->total_height) + new_offset = view->total_height - text_h; + if (new_offset < 0) + new_offset = 0; + view->display_offset = new_offset; + ledit_window_set_scroll_pos(view->window, view->display_offset); +} + +/* FIXME: there's gotta be a better/more efficient way to do this... */ +/* FIXME: make sure h_dirty is not set here */ +void +view_get_nearest_legal_pos( + ledit_view *view, + size_t line, size_t byte, + /*int snap_to_nearest, int snap_middle, FIXME: take these parameters */ + size_t *line_ret, size_t *byte_ret) { + PangoRectangle strong, weak; + int text_w, text_h; + int x, sl_useless; + ledit_window_get_textview_size(view->window, &text_w, &text_h); + ledit_view_line *vline = view_get_line(view, line); + PangoLayout *layout = get_pango_layout(view, line); + if (byte > INT_MAX) + err_overflow(); + pango_layout_get_cursor_pos(layout, (int)byte, &strong, &weak); + ledit_pos_to_x_softline(view, line, byte, &x, &sl_useless); + long cursor_y = strong.y / PANGO_SCALE + vline->y_offset; + PangoRectangle ink, log; + if (cursor_y < view->display_offset) { + /* search for the hard line covering the top of the screen */ + size_t hline = line; + while (vline->y_offset + vline->h <= view->display_offset && hline < view->lines_num - 1) { + vline = view_get_line(view, ++hline); + } + /* the current hard line is now the one at the very top of the screen*/ + layout = get_pango_layout(view, hline); + int num_sl = vline->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(layout, i); + if (cur_y_off + vline->y_offset >= view->display_offset) { + sl_index = i; + break; + } + pango_layout_line_get_pixel_extents(sl, &ink, &log); + cur_y_off += log.height; + } + if (sl_index >= 0) { + /* we found the correct soft line */ + *line_ret = hline; + ledit_x_softline_to_pos(view, hline, x, sl_index, byte_ret); + } else if (hline < view->lines_num - 1) { + /* need to move to next hard line */ + *line_ret = hline + 1; + ledit_x_softline_to_pos(view, 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(view, hline, x, num_sl - 1, byte_ret); + } + } else if (cursor_y + strong.height / PANGO_SCALE > + view->display_offset + text_h) { + /* search for the hard line covering the bottom of the screen */ + size_t hline = line; + while (vline->y_offset > view->display_offset + text_h && hline > 0) { + vline = view_get_line(view, --hline); + } + /* the current hard line is now the one at the very bottom of the screen*/ + layout = get_pango_layout(view, hline); + int num_sl = vline->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(layout, i); + if (vline->y_offset + vline->h - cur_y_off < view->display_offset + text_h) { + sl_index = i; + break; + } + pango_layout_line_get_pixel_extents(sl, &ink, &log); + cur_y_off += log.height; + } + if (sl_index >= 0) { + /* we found the correct soft line */ + *line_ret = hline; + ledit_x_softline_to_pos(view, hline, x, sl_index, byte_ret); + } else if (hline > 0) { + /* need to move to previous hard line */ + *line_ret = hline - 1; + vline = view_get_line(view, hline - 1); + num_sl = vline->softlines; + ledit_x_softline_to_pos(view, 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(view, hline, x, 0, byte_ret); + } + } +} + +void +ledit_xy_to_line_byte(ledit_view *view, int x, int y, int snap_to_nearest, size_t *line_ret, size_t *byte_ret) { + /* FIXME: store current line offset to speed this up */ + /* FIXME: use y_offset in lines */ + long h = 0; + long pos = view->display_offset + y; + for (size_t i = 0; i < view->lines_num; i++) { + ledit_view_line *vline = view_get_line(view, i); + if ((h <= pos && h + vline->h > pos) || i == view->lines_num - 1) { + int index, trailing; + PangoLayout *layout = get_pango_layout(view, i); + /* FIXME: what if i == view->lines_num - 1 but pos - h < 0? */ + pango_layout_xy_to_index( + layout, + x * PANGO_SCALE, (int)(pos - h) * PANGO_SCALE, + &index, &trailing + ); + *byte_ret = (size_t)index; + if (snap_to_nearest) { + ledit_line *ll = ledit_buffer_get_line(view->buffer, i); + while (trailing > 0) { + trailing--; + *byte_ret = ledit_line_next_utf8(ll, *byte_ret); + } + } + *line_ret = i; + break; + } + h += vline->h; + } +} + +static void +scroll_to_pos(ledit_view *view, size_t line, size_t byte, int top) { + PangoRectangle strong, weak; + int text_w, text_h; + ledit_window_get_textview_size(view->window, &text_w, &text_h); + ledit_view_line *vl = view_get_line(view, line); + PangoLayout *layout = get_pango_layout(view, line); + if (byte > INT_MAX) + err_overflow(); + pango_layout_get_cursor_pos(layout, (int)byte, &strong, &weak); + long cursor_y = strong.y / PANGO_SCALE + vl->y_offset; + if (top) { + view_scroll(view, cursor_y); + } else { + view_scroll(view, cursor_y - text_h + strong.height / PANGO_SCALE); + } +} + +void +view_scroll_to_pos_top(ledit_view *view, size_t line, size_t byte) { + scroll_to_pos(view, line, byte, 1); +} + +void +view_scroll_to_pos_bottom(ledit_view *view, size_t line, size_t byte) { + scroll_to_pos(view, line, byte, 0); +} + +void +view_ensure_cursor_shown(ledit_view *view) { + PangoRectangle strong, weak; + int text_w, text_h; + ledit_window_get_textview_size(view->window, &text_w, &text_h); + ledit_view_line *vline = view_get_line(view, view->cur_line); + PangoLayout *layout = get_pango_layout(view, view->cur_line); + if (view->cur_index > INT_MAX) + err_overflow(); + pango_layout_get_cursor_pos( + layout, (int)view->cur_index, &strong, &weak + ); + long cursor_y = strong.y / PANGO_SCALE + vline->y_offset; + if (cursor_y < view->display_offset) { + view_scroll(view, cursor_y); + } else if (cursor_y + strong.height / PANGO_SCALE > + view->display_offset + text_h) { + view_scroll(view, cursor_y - text_h + strong.height / PANGO_SCALE); + } +} + +static void +swap_sz(size_t *a, size_t *b) { + size_t tmp = *a; + *a = *b; + *b = tmp; +} + +/* FIXME: this is generic, so it doesn't need to be in view.c */ +void +view_sort_selection(size_t *line1, size_t *byte1, size_t *line2, size_t *byte2) { + if (*line1 > *line2) { + swap_sz(line1, line2); + swap_sz(byte1, byte2); + } else if (*line1 == *line2 && *byte1 > *byte2) { + swap_sz(byte1, byte2); + } +} + +/* FIXME: don't reset selection when selection is clicked away */ +/* FIXME: when selecting with mouse, only call this when button is released */ +/* lines and bytes need to be sorted already! */ +static void +copy_selection_to_x_primary(ledit_view *view, size_t line1, size_t byte1, size_t line2, size_t byte2) { + /* FIXME: let window handle this */ + txtbuf *primary = ledit_window_get_primary_clipboard_buffer(); + ledit_buffer_copy_text_to_txtbuf(view->buffer, primary, line1, byte1, line2, byte2); + XSetSelectionOwner(view->buffer->common->dpy, XA_PRIMARY, view->window->xwin, CurrentTime); + /* + FIXME + if (XGetSelectionOwner(state.dpy, XA_PRIMARY) != state.win) + selclear(); + */ +} + +void +view_wipe_selection(ledit_view *view) { + if (view->sel_valid) { + if (view->sel.line1 > view->sel.line2) + swap_sz(&view->sel.line1, &view->sel.line2); + for (size_t i = view->sel.line1; i <= view->sel.line2; i++) { + view_wipe_line_cursor_attrs(view, i); + } + } + view->sel_valid = 0; + view->sel.line1 = view->sel.line2 = 0; + view->sel.byte1 = view->sel.byte2 = 0; + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); +} + +void +view_set_selection(ledit_view *view, size_t line1, size_t byte1, size_t line2, size_t byte2) { + if (view->sel_valid && + line1 == view->sel.line1 && line2 == view->sel.line2 && + byte1 == view->sel.byte1 && byte2 == view->sel.byte2) { + return; + } + size_t l1_new = line1, l2_new = line2; + size_t b1_new = byte1, b2_new = byte2; + view_sort_selection(&l1_new, &b1_new, &l2_new, &b2_new); + view_sort_selection(&view->sel.line1, &view->sel.byte1, &view->sel.line2, &view->sel.byte2); + /* FIXME: make this a bit nicer and optimize it */ + if (view->sel.line1 > l2_new || view->sel.line2 < l1_new) { + for (size_t i = view->sel.line1; i <= view->sel.line2; i++) { + view_wipe_line_cursor_attrs(view, i); + } + } else { + for (size_t i = view->sel.line1; i < l1_new; i++) { + view_wipe_line_cursor_attrs(view, i); + } + for (size_t i = view->sel.line2; i > l2_new; i--) { + view_wipe_line_cursor_attrs(view, i); + } + } + for (size_t i = l1_new; i <= l2_new; i++) { + /* only change the ones that were not already selected */ + if (i <= view->sel.line1 || i >= view->sel.line2) { + ledit_view_line *vl = view_get_line(view, i); + vl->highlight_dirty = 1; + } + } + if (l1_new != l2_new || b1_new != b2_new) + copy_selection_to_x_primary(view, l1_new, b1_new, l2_new, b2_new); + view->sel.line1 = line1; + view->sel.byte1 = byte1; + view->sel.line2 = line2; + view->sel.byte2 = byte2; + view->sel_valid = 1; +} + +void +view_scroll_handler(void *view, long pos) { + ((ledit_view *)view)->display_offset = pos; +} + +void +view_button_handler(void *data, XEvent *event) { + size_t l, b; + ledit_view *view= (ledit_view *)data; + int x = event->xbutton.x; + int y = event->xbutton.y; + int snap; + switch (event->type) { + case ButtonPress: + snap = view->mode == NORMAL ? 0 : 1; + ledit_xy_to_line_byte(view, x, y, snap, &l, &b); + view->selecting = 1; + view_wipe_line_cursor_attrs(view, view->cur_line); + view->cur_line = l; + view->cur_index = b; + /* don't set selection yet because the mouse may not be + dragged, so we don't want to switch to visual (this + allows setting just the cursor position in normal mode + without always switching to visual) */ + if (view->mode == NORMAL) + view_set_line_cursor_attrs(view, l, b); + else if (view->mode == VISUAL) + view_set_selection(view, l, b, l, b); + break; + case ButtonRelease: + view->selecting = 0; + break; + case MotionNotify: + if (view->selecting) { + y = y >= 0 ? y : 0; + ledit_xy_to_line_byte(view, x, y, 1, &l, &b); + if (view->mode == NORMAL) { + view_wipe_line_cursor_attrs(view, view->cur_line); + /* FIXME: return to old mode afterwards? */ + /* should change_mode_group even be called here? */ + view_set_mode(view, VISUAL); + } + if (!view->sel_valid) { + /* the selection has just started, so the current + position is already set to the beginning of the + selection (see case ButtonPress above) */ + view_set_selection( + view, + view->cur_line, view->cur_index, l, b + ); + } else { + view_set_selection( + view, + view->sel.line1, view->sel.byte1, l, b + ); + } + view->cur_line = l; + view->cur_index = b; + } + break; + } +} + +static void +view_redraw_text(ledit_view *view) { + int h = 0; + int cur_line_y = 0; + int cursor_displayed = 0; + int text_w, text_h; + ledit_window_get_textview_size(view->window, &text_w, &text_h); + for (size_t i = 0; i < view->lines_num; i++) { + ledit_view_line *vline = view_get_line(view, i); + if (h + vline->h > view->display_offset) { + /* FIXME: vline->text_dirty should not happen here */ + if (vline->text_dirty || vline->highlight_dirty) + set_pango_text_and_highlight(view, i); + if (vline->dirty || !vline->cache_pixmap_valid) { + view_render_line(view, i); + } + int final_y = 0; + int dest_y = h - view->display_offset; + int final_h = vline->h; + if (h < view->display_offset) { + dest_y = 0; + final_y = view->display_offset - h; + final_h -= view->display_offset - h; + } + if (dest_y + final_h > text_h) { + final_h -= final_y + final_h - + view->display_offset - text_h; + } + cache_pixmap *pix = cache_get_pixmap( + view->cache, vline->cache_pixmap_index + ); + XCopyArea( + view->buffer->common->dpy, pix->pixmap, + view->window->drawable, view->window->gc, + 0, final_y, vline->w, final_h, 0, dest_y + ); + if (i == view->cur_line) { + cur_line_y = h - view->display_offset; + cursor_displayed = 1; + } + if (h + vline->h >= view->display_offset + text_h) + break; + } + h += vline->h; + } + + XSetForeground(view->buffer->common->dpy, view->window->gc, view->theme->text_fg.pixel); + PangoRectangle strong, weak; + ledit_line *cur_line = ledit_buffer_get_line(view->buffer, view->cur_line); + PangoLayout *layout = get_pango_layout(view, view->cur_line); + pango_layout_get_cursor_pos( + layout, (int)view->cur_index, &strong, &weak + ); + /* FIXME: long, int, etc. */ + int cursor_y = strong.y / PANGO_SCALE + cur_line_y; + if (cursor_displayed && cursor_y >= 0) { + if (view->mode == NORMAL) { + /* FIXME: figure out if there's a better way to do this */ + /* Seriously, which of the pango folks though it would be a good idea to + not highlight spaces at the end of soft lines? That is an utterly + horrible idea. Or am I just too stupid to use it properly? */ + /* FIXME: properly document what is happening here */ + + int box_x = strong.x / PANGO_SCALE; + int box_w = 10; + /* determine where the box should be drawn */ + PangoDirection dir = PANGO_DIRECTION_LTR; + size_t tmp_index = view->cur_index; + if (view->cur_index >= cur_line->len && cur_line->len > 0) + tmp_index = cur_line->len - 1; + else if (view->cur_index >= cur_line->len) + tmp_index = 0; + dir = ledit_pango_layout_get_direction(layout, (int)tmp_index); + + int x, sli; + pango_layout_index_to_line_x(layout, (int)view->cur_index, 0, &sli, &x); + PangoLayoutLine *sl = pango_layout_get_line_readonly(layout, sli); + if (dir != sl->resolved_dir) { + box_w = 3; + } + if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL) { + box_x = box_x - box_w; + } + + if (view->cur_index == cur_line->len || + (cur_line->text[view->cur_index] == ' ' && + view->cur_index == (size_t)(sl->start_index + sl->length - 1))) { + XFillRectangle( + view->buffer->common->dpy, view->window->drawable, view->window->gc, + box_x, cursor_y, + box_w, strong.height / PANGO_SCALE + ); + } + } else if (view->mode == INSERT || view->mode == VISUAL) { + XDrawLine( + view->buffer->common->dpy, view->window->drawable, view->window->gc, + strong.x / PANGO_SCALE, cursor_y, + strong.x / PANGO_SCALE, + (strong.y + strong.height) / PANGO_SCALE + cur_line_y + ); + } + } + /* move input method position */ + if (!ledit_window_bottom_bar_text_shown(view->window)) { + xximspot( + view->window, + strong.x / PANGO_SCALE, + (strong.y + strong.height) / PANGO_SCALE + cur_line_y + ); + } + view->redraw = 0; +} + +void +view_redraw(ledit_view *view) { + if (view->redraw || view->window->redraw) { + ledit_window_clear(view->window); + view_redraw_text(view); + ledit_window_redraw(view->window); + } +} + +static void +undo_insert_helper(void *data, size_t line, size_t byte, char *text, size_t text_len) { + ledit_view *view = (ledit_view *)data; + ledit_buffer_insert_text_with_newlines_base(view->buffer, line, byte, text, text_len, NULL, NULL); +} + +static void +undo_delete_helper(void *data, size_t line1, size_t byte1, size_t line2, size_t byte2) { + ledit_view *view = (ledit_view *)data; + view_delete_range_base(view, DELETE_CHAR, line1, byte1, line2, byte2, NULL, NULL, NULL, NULL); +} + +void +view_undo(ledit_view *view) { + size_t min_line; + size_t old_line = view->cur_line; + ledit_undo( + view->buffer->undo, view->mode, view, &undo_insert_helper, + &undo_delete_helper, &view->cur_line, &view->cur_index, &min_line + ); + if (view->mode == NORMAL) { + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, view->cur_index + ); + } + view_wipe_line_cursor_attrs(view, old_line); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + if (min_line < view->lines_num) { + ledit_buffer_recalc_all_views_from_line( + view->buffer, min_line > 0 ? min_line - 1 : min_line + ); + } + /* FIXME: show undo message */ +} + +void +view_redo(ledit_view *view) { + size_t min_line; + size_t old_line = view->cur_line; + ledit_redo( + view->buffer->undo, view->mode, view, &undo_insert_helper, + &undo_delete_helper, &view->cur_line, &view->cur_index, &min_line + ); + if (view->mode == NORMAL) { + view->cur_index = view_get_legal_normal_pos( + view, view->cur_line, view->cur_index + ); + } + view_wipe_line_cursor_attrs(view, old_line); + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + if (min_line < view->lines_num) { + ledit_buffer_recalc_all_views_from_line( + view->buffer, min_line > 0 ? min_line - 1 : min_line + ); + } + /* FIXME: show undo message */ +} + +static void +paste_callback(void *data, char *text, size_t len) { + ledit_view *view = (ledit_view *)data; + txtbuf ins_buf = {.text = text, .len = len, .cap = len}; + ledit_range cur_range, ins_range; + cur_range.line1 = ins_range.line1 = view->cur_line; + cur_range.byte1 = ins_range.byte1 = view->cur_index; + ledit_buffer_insert_text_with_newlines( + view->buffer, view->cur_line, view->cur_index, + text, len, &view->cur_line, &view->cur_index + ); + cur_range.line2 = ins_range.line2 = view->cur_line; + cur_range.byte2 = ins_range.byte2 = view->cur_index; + ledit_push_undo_insert( + view->buffer->undo, &ins_buf, ins_range, cur_range, 1, view->mode + ); +} + +/* FIXME: guard against buffer being destroyed before paste callback is nulled */ + +void +view_paste_clipboard(ledit_view *view) { + ledit_window_set_paste_callback(view->window, &paste_callback, view); + clipboard_paste_clipboard(view->window); +} + +void +view_paste_primary(ledit_view *view) { + ledit_window_set_paste_callback(view->window, &paste_callback, view); + clipboard_paste_primary(view->window); +} diff --git a/view.h b/view.h @@ -0,0 +1,158 @@ +#ifndef _LEDIT_VIEW_H_ +#define _LEDIT_VIEW_H_ + +typedef struct ledit_view ledit_view; + +#include "buffer.h" + +enum action_type { + ACTION_NONE, /* pass next key to basic key handler */ + ACTION_GRABKEY /* pass next key to given callback */ +}; + +struct action { + enum action_type type; + struct action (*callback)(ledit_view *view, XEvent *event, int lang_index); +}; + +typedef struct { + ledit_view *view; /* parent view */ + int w; /* width in pixels */ + int h; /* height in pixels */ + long y_offset; /* pixel offset starting at the top of the file */ + size_t cache_pixmap_index; /* index of pixmap in cache, or -1 if not assigned */ + size_t cache_layout_index; /* index of pango layout in cache, or -1 if not assigned */ + int softlines; /* number of softlines - cached from PangoLayout */ + size_t cursor_index; /* cursor index (for highlight in normal mode) */ + char cursor_index_valid; /* whether cursor index is valid */ + char cache_pixmap_valid; /* whether cache_pixmap_index is valid */ + char cache_layout_valid; /* whether cache_layout_index is valid */ + 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_view_line; + +/* FIXME: It's kind of ugly to put this here instead of keys_command.h, + but it has to be per-view, so I don't know any other option. */ +enum ledit_command_type { + CMD_EDIT, + CMD_EDITSEARCH, + CMD_EDITSEARCHB, + CMD_SEARCH, + CMD_SEARCHB, + CMD_SUBSTITUTE +}; + +struct ledit_view { + ledit_buffer *buffer; /* parent buffer */ + ledit_window *window; /* window showing this view */ + ledit_theme *theme; /* current theme in use */ + ledit_cache *cache; /* cache for pixmaps and pango layouts */ + ledit_view_line *lines; /* array of lines, stored as gap buffer */ + /* current command type */ + enum ledit_command_type cur_command_type; + struct action cur_action; /* current action to execute on key press */ + size_t lines_cap; /* size of lines array */ + size_t lines_gap; /* position of gap for line gap buffer */ + size_t lines_num; /* number of lines */ + size_t cur_line; /* current line */ + size_t cur_index; /* current byte index in line */ + long total_height; /* total pixel height of all lines */ + long display_offset; /* current pixel offset of viewport */ + ledit_range sel; /* current selection */ + enum ledit_mode mode; /* current mode of this view */ + char selecting; /* whether user is currently selecting text with mouse */ + char sel_valid; /* whether there is currently a valid selection */ + char redraw; /* whether something has changed so the view needs to be redrawn */ +}; + +enum delete_mode { + DELETE_CHAR, + DELETE_SOFTLINE, + DELETE_HARDLINE +}; + +void view_set_mode(ledit_view *view, enum ledit_mode mode); +ledit_view *view_create(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size_t line, size_t pos); +ledit_view_line *view_get_line(ledit_view *view, size_t index); +void view_notify_insert_text(ledit_view *view, size_t line, size_t index, size_t len); +void view_notify_delete_text(ledit_view *view, size_t line, size_t index, size_t len); +void view_notify_append_line(ledit_view *view, size_t line); +void view_notify_delete_lines(ledit_view *view, size_t index1, size_t index2); +void view_destroy(ledit_view *view); +void view_cleanup(void); +void view_set_line_cursor_attrs(ledit_view *view, size_t line, size_t index); +void view_wipe_line_cursor_attrs(ledit_view *view, size_t line); +void view_render_line(ledit_view *view, size_t line_index); +void view_recalc_line(ledit_view *view, size_t line); +void view_recalc_from_line(ledit_view *view, size_t line); +void view_recalc_all_lines(ledit_view *view); +int view_line_visible(ledit_view *view, size_t index); +size_t view_next_cursor_pos(ledit_view *view, size_t line, size_t byte, int num); +size_t view_prev_cursor_pos(ledit_view *view, size_t line, size_t byte, int num); +void view_next_word(ledit_view *view, size_t line, size_t byte, int num_repeat, size_t *line_ret, size_t *byte_ret, size_t *real_byte_ret); +void view_next_word_end(ledit_view *view, size_t line, size_t byte, int num_repeat, size_t *line_ret, size_t *byte_ret, size_t *real_byte_ret); +void view_next_bigword(ledit_view *view, size_t line, size_t byte, int num_repeat, size_t *line_ret, size_t *byte_ret, size_t *real_byte_ret); +void view_next_bigword_end(ledit_view *view, size_t line, size_t byte, int num_repeat, size_t *line_ret, size_t *byte_ret, size_t *real_byte_ret); +void view_prev_word(ledit_view *view, size_t line, size_t byte, int num_repeat, size_t *line_ret, size_t *byte_ret, size_t *real_byte_ret); +void view_prev_bigword(ledit_view *view, size_t line, size_t byte, int num_repeat, size_t *line_ret, size_t *byte_ret, size_t *real_byte_ret); +size_t view_line_next_non_whitespace(ledit_view *view, size_t line, size_t byte); +void view_get_pos_softline_bounds( + ledit_view *view, size_t line, size_t pos, + size_t *start_byte_ret, size_t *end_byte_ret +); +void view_get_softline_bounds( + ledit_view *view, size_t line, int softline, + size_t *start_byte_ret, size_t *end_byte_ret +); +int view_get_softline_count(ledit_view *view, size_t line); +int view_pos_to_softline(ledit_view *view, size_t line, size_t pos); +void view_get_cursor_pixel_pos(ledit_view *view, size_t line, size_t pos, int *x_ret, int *y_ret, int *h_ret); +size_t view_move_cursor_visually(ledit_view *view, size_t line, size_t pos, int movement, size_t *prev_index_ret); +void ledit_pos_to_x_softline(ledit_view *view, size_t line, size_t pos, int *x_ret, int *softline_ret); +void ledit_x_softline_to_pos(ledit_view *view, size_t line, int x, int softline, size_t *pos_ret); +size_t view_get_legal_normal_pos(ledit_view *view, size_t line, size_t pos); +void view_delete_range( + ledit_view *view, enum delete_mode delmode, + size_t line_index1, size_t byte_index1, + size_t line_index2, size_t byte_index2, + size_t *new_line_ret, size_t *new_byte_ret, + ledit_range *final_range_ret, txtbuf *text_ret +); +void view_delete_range_base( + ledit_view *view, enum delete_mode delmode, + size_t line_index1, size_t byte_index1, + size_t line_index2, size_t byte_index2, + size_t *new_line_ret, size_t *new_byte_ret, + ledit_range *final_range_ret, txtbuf *text_ret +); +void view_resize_textview(void *data); +void view_scroll(ledit_view *view, long new_offset); +void view_get_nearest_legal_pos( + ledit_view *view, + size_t line, size_t byte, + /*int snap_to_nearest, int snap_middle, FIXME: take these parameters */ + size_t *line_ret, size_t *byte_ret +); +/* x and y are in pixels, if snap_to_nearest is nonzero, the returned byte + is at the nearest grapheme boundary, if it is zero, the byte is always + the beginning of the grapheme under the position */ +void ledit_xy_to_line_byte(ledit_view *view, int x, int y, int snap_to_nearest, size_t *line_ret, size_t *byte_ret); +void view_scroll_to_pos_top(ledit_view *view, size_t line, size_t byte); +void view_scroll_to_pos_bottom(ledit_view *view, size_t line, size_t byte); +void view_ensure_cursor_shown(ledit_view *view); +void view_sort_selection(size_t *line1, size_t *byte1, size_t *line2, size_t *byte2); +void view_wipe_selection(ledit_view *view); +void view_set_selection(ledit_view *view, size_t line1, size_t byte1, size_t line2, size_t byte2); +void view_scroll_handler(void *view, long pos); +void view_button_handler(void *data, XEvent *event); +void view_redraw(ledit_view *view); +void view_undo(ledit_view *view); +void view_redo(ledit_view *view); +void view_paste_clipboard(ledit_view *view); +void view_paste_primary(ledit_view *view); + +#endif diff --git a/window.c b/window.c @@ -1,3 +1,5 @@ +/* FIXME: check if xim handling still works with multiple windows */ +#include <time.h> #include <math.h> #include <stdio.h> #include <assert.h> @@ -17,6 +19,8 @@ #include "theme.h" #include "window.h" #include "util.h" +#include "macros.h" +#include "config.h" /* FIXME: Everything to do with the bottom bar is extremely hacky */ struct bottom_bar { @@ -101,6 +105,7 @@ ledit_window_insert_bottom_bar_text(ledit_window *window, char *text, int len) { XftDrawRect(window->bb->line_draw->xftdraw, &window->theme->text_bg, 0, 0, window->bb->line_w, window->bb->line_h); pango_xft_render_layout(window->bb->line_draw->xftdraw, &window->theme->text_fg, window->bb->line, 0, 0); recalc_text_size(window); + window->redraw = 1; } void @@ -126,6 +131,7 @@ ledit_window_move_bottom_bar_cursor(ledit_window *window, int movement) { if (new_index > window->bb->line_len) new_index = window->bb->line_len; window->bb->line_cur_pos = new_index; + window->redraw = 1; } void @@ -136,11 +142,13 @@ ledit_window_set_bottom_bar_min_pos(ledit_window *window, int pos) { void ledit_window_bottom_bar_cursor_to_beginning(ledit_window *window) { window->bb->line_cur_pos = window->bb->min_pos; + window->redraw = 1; } void ledit_window_bottom_bar_cursor_to_end(ledit_window *window) { window->bb->line_cur_pos = window->bb->line_len; + window->redraw = 1; } /* FIXME: respect PangoLogAttr.backspace_deletes_character */ @@ -185,12 +193,14 @@ ledit_window_delete_bottom_bar_char(ledit_window *window, int dir) { XftDrawRect(window->bb->line_draw->xftdraw, &window->theme->text_bg, 0, 0, window->bb->line_w, window->bb->line_h); pango_xft_render_layout(window->bb->line_draw->xftdraw, &window->theme->text_fg, window->bb->line, 0, 0); recalc_text_size(window); + window->redraw = 1; } void ledit_window_set_bottom_bar_cursor(ledit_window *window, int byte_pos) { /* FIXME: check if valid? */ window->bb->line_cur_pos = byte_pos; + window->redraw = 1; } int @@ -201,11 +211,13 @@ ledit_window_get_bottom_bar_cursor(ledit_window *window) { void ledit_window_set_bottom_bar_text_shown(ledit_window *window, int shown) { window->bottom_text_shown = shown; + window->redraw = 1; } int ledit_window_bottom_bar_text_shown(ledit_window *window) { return window->bottom_text_shown; + window->redraw = 1; } void @@ -213,6 +225,7 @@ ledit_window_set_bottom_bar_text(ledit_window *window, char *text, int len) { window->bb->line_len = 0; window->bb->line_cur_pos = 0; ledit_window_insert_bottom_bar_text(window, text, len); + window->redraw = 1; } char * @@ -226,6 +239,7 @@ ledit_window_show_message(ledit_window *window, char *text, int len) { /* FIXME: rename these */ window->bottom_text_shown = 0; window->message_shown = 1; + window->redraw = 1; } int @@ -236,10 +250,12 @@ ledit_window_message_shown(ledit_window *window) { void ledit_window_hide_message(ledit_window *window) { window->message_shown = 0; + window->redraw = 1; } void ledit_window_set_mode(ledit_window *window, enum ledit_mode mode) { + window->mode = mode; char *text; switch (mode) { case NORMAL: @@ -263,12 +279,14 @@ ledit_window_set_mode(ledit_window *window, enum ledit_mode mode) { XftDrawRect(window->bb->mode_draw->xftdraw, &window->theme->text_bg, 0, 0, window->bb->mode_w, window->bb->mode_h); pango_xft_render_layout(window->bb->mode_draw->xftdraw, &window->theme->text_fg, window->bb->mode, 0, 0); recalc_text_size(window); + window->redraw = 1; } void ledit_window_set_mode_extra_text(ledit_window *window, char *text) { window->mode_extra_text = ledit_strdup(text); - ledit_window_set_mode(window, window->common->mode); + ledit_window_set_mode(window, window->mode); + window->redraw = 1; } /* FIXME: give these functions more sensible names */ @@ -289,21 +307,19 @@ set_scroll_pos(ledit_window *window, double pos) { /* FIXME: check for overflow */ if (window->scroll_callback) window->scroll_callback(window->scroll_cb_data, (long)window->scroll_offset); + window->redraw = 1; } void ledit_window_set_scroll_max(ledit_window *window, long max) { window->scroll_max = max; - /* FIXME: set offset if too large */ - /* actually not, because buffer does that already */ - /* FIXME: SHOULD THIS EVEN BE USED? */ - window->common->redraw = 1; + window->redraw = 1; } void ledit_window_set_scroll_pos(ledit_window *window, long pos) { window->scroll_offset = pos; - window->common->redraw = 1; + window->redraw = 1; } void @@ -313,7 +329,7 @@ ledit_window_set_scroll_callback(ledit_window *window, void (*cb)(void *, long), } void -ledit_window_set_paste_callback(ledit_window *window, void (*cb)(void *, char *, int), void *data) { +ledit_window_set_paste_callback(ledit_window *window, void (*cb)(void *, char *, size_t), void *data) { window->paste_callback = cb; window->paste_cb_data = data; } @@ -324,6 +340,12 @@ ledit_window_set_button_callback(ledit_window *window, void (*cb)(void *, XEvent window->button_cb_data = data; } +void +ledit_window_set_resize_callback(ledit_window *window, void (*cb)(void *), void *data) { + window->resize_callback = cb; + window->resize_cb_data = data; +} + /* FIXME: Change naming convention to fit the rest of ledit */ /* FIXME: It's a bit weird when an input box pops up during normal mode. Can/should this be disabled? */ @@ -411,17 +433,26 @@ xximspot(ledit_window *window, int x, int y) { } ledit_window * -ledit_window_create(ledit_common *common, ledit_theme *theme) { +ledit_window_create(ledit_common *common, ledit_theme *theme, enum ledit_mode mode) { XSetWindowAttributes attrs; XGCValues gcv; ledit_window *window = ledit_malloc(sizeof(ledit_window)); + window->mode = mode; window->scroll_dragging = 0; window->scroll_grab_handle = 0; window->w = 500; window->h = 500; window->mode_extra_text = NULL; + window->paste_callback = NULL; + window->scroll_callback = NULL; + window->button_callback = NULL; + window->resize_callback = NULL; + window->paste_cb_data = NULL; + window->scroll_cb_data = NULL; + window->button_cb_data = NULL; + window->resize_cb_data = NULL; memset(&window->wattrs, 0, sizeof(attrs)); window->wattrs.background_pixel = BlackPixel(common->dpy, common->screen); @@ -507,26 +538,41 @@ ledit_window_create(ledit_common *common, ledit_theme *theme) { /* FIXME: maybe delay this (i.e. move to different function)? */ XMapWindow(common->dpy, window->xwin); + window->redraw = 1; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + window->last_scroll = now; + window->last_motion = now; + window->last_resize = now; + window->last_scroll_valid = 0; + window->last_motion_valid = 0; + window->last_resize_valid = 0; + window->scroll_num = 0; + recalc_text_size(window); + return window; } void ledit_window_destroy(ledit_window *window) { - /* FIXME: cleanup everything else */ - /* FIXME: This doesn't seem to be correct - g_object_unref sometimes - causes segfault */ + /* FIXME: check what's still missing */ + g_object_unref(window->bb->mode); + /*g_object_unref(window->bb->ruler);*/ /* FIXME: implement ruler */ + g_object_unref(window->bb->line); + ledit_draw_destroy(window, window->bb->mode_draw); + ledit_draw_destroy(window, window->bb->line_draw); + pango_font_description_free(window->font); + /* FIXME: The pango documentation says that the context must be freed, + but the program segfaults when that is done. */ + /*g_object_unref(window->context);*/ g_object_unref(window->fontmap); - g_object_unref(window->context); + /* FIXME: is gc, etc. destroyed automatically when destroying window? */ if (window->spotlist) XFree(window->spotlist); XDestroyWindow(window->common->dpy, window->xwin); - g_object_unref(window->bb->mode); - /*g_object_unref(window->bb->ruler);*/ /* FIXME: implement ruler */ - g_object_unref(window->bb->line); - ledit_draw_destroy(window, window->bb->mode_draw); - ledit_draw_destroy(window, window->bb->line_draw); + if (window->mode_extra_text) free(window->mode_extra_text); free(window->bb->line_text); @@ -630,6 +676,7 @@ ledit_window_redraw(ledit_window *window) { if (!XdbeSwapBuffers(window->common->dpy, &swap_info, 1)) exit(1); XFlush(window->common->dpy); + window->redraw = 0; } void @@ -639,11 +686,50 @@ ledit_window_get_textview_size(ledit_window *window, int *w_ret, int *h_ret) { } void +ledit_window_handle_filtered_events(ledit_window *window) { + struct timespec now, elapsed; + if (window->last_motion_valid) { + clock_gettime(CLOCK_MONOTONIC, &now); + ledit_timespecsub(&now, &window->last_motion, &elapsed); + if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= MOUSE_TICK) { + ledit_window_drag_motion(window, &window->last_motion_event); + window->last_motion = now; + window->last_motion_valid = 0; + } + } + if (window->last_scroll_valid) { + clock_gettime(CLOCK_MONOTONIC, &now); + ledit_timespecsub(&now, &window->last_scroll, &elapsed); + if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= MOUSE_TICK) { + ledit_window_button_press(window, &window->last_scroll_event, window->scroll_num); + window->last_scroll = now; + window->last_scroll_valid = 0; + } + } + if (window->last_resize_valid) { + clock_gettime(CLOCK_MONOTONIC, &now); + ledit_timespecsub(&now, &window->last_resize, &elapsed); + if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= RESIZE_TICK) { + ledit_window_resize( + window, + window->last_resize_event.xconfigure.width, + window->last_resize_event.xconfigure.height + ); + window->last_resize = now; + window->last_resize_valid = 0; + window->redraw = 1; + } + } +} + +void ledit_window_resize(ledit_window *window, int w, int h) { window->w = w; window->h = h; recalc_text_size(window); - window->common->redraw = 1; + if (window->resize_callback) + window->resize_callback(window->resize_cb_data); + window->redraw = 1; } void @@ -748,8 +834,9 @@ clipboard_selnotify(ledit_window *window, XEvent *e) { } /* FIXME: XGetWindowProperty takes data as unsigned char, so it is casted here. Why? */ + /* FIXME: buffer all pasted text and only send it in one go in the end */ if (window->paste_callback) - window->paste_callback(window->paste_cb_data, (char *)data, (int)(nitems * format / 8)); + window->paste_callback(window->paste_cb_data, (char *)data, (size_t)(nitems * format / 8)); XFree(data); /* number of 32-bit chunks returned */ ofs += nitems * format / 32; @@ -820,8 +907,38 @@ clipboard_selrequest(ledit_window *window, XEvent *e) fprintf(stderr, "Error sending SelectionNotify event\n"); } +void +ledit_window_register_button_press(ledit_window *window, XEvent *event) { + int scroll_delta; + if (event->xbutton.button == Button4 || + event->xbutton.button == Button5) { + scroll_delta = event->xbutton.button == Button4 ? -1 : 1; + if (window->last_scroll_valid) { + window->scroll_num += scroll_delta; + } else { + window->last_scroll_event = *event; + window->last_scroll_valid = 1; + window->scroll_num = scroll_delta; + } + } else { + ledit_window_button_press(window, event, 0); + } +} + +void +ledit_window_register_resize(ledit_window *window, XEvent *event) { + window->last_resize_event = *event; + window->last_resize_valid = 1; +} + +void +ledit_window_register_motion(ledit_window *window, XEvent *event) { + window->last_motion_event = *event; + window->last_motion_valid = 1; +} + /* FIXME: improve set_scroll_pos; make it a bit clearer */ -int +void ledit_window_button_press(ledit_window *window, XEvent *event, int scroll_num) { int x, y; double scroll_h, scroll_y; @@ -837,11 +954,11 @@ ledit_window_button_press(ledit_window *window, XEvent *event, int scroll_num) { double new_scroll_y = y - scroll_h / 2; set_scroll_pos(window, new_scroll_y); } - return 1; + window->redraw = 1; } else if (y < window->text_h) { if (window->button_callback) window->button_callback(window->button_cb_data, event); - return 1; + window->redraw = 1; } break; case Button4: @@ -854,23 +971,21 @@ ledit_window_button_press(ledit_window *window, XEvent *event, int scroll_num) { } if (window->scroll_callback) window->scroll_callback(window->scroll_cb_data, (long)window->scroll_offset); - return 1; + window->redraw = 1; } - return 0; } -int +void ledit_window_button_release(ledit_window *window, XEvent *event) { if (event->xbutton.button == Button1) { window->scroll_dragging = 0; if (window->button_callback) window->button_callback(window->button_cb_data, event); - return 1; + window->redraw = 1; } - return 0; } -int +void ledit_window_drag_motion(ledit_window *window, XEvent *event) { if (window->scroll_dragging) { double scroll_h, scroll_y; @@ -878,16 +993,15 @@ ledit_window_drag_motion(ledit_window *window, XEvent *event) { scroll_y += event->xbutton.y - window->scroll_grab_handle; window->scroll_grab_handle = event->xbutton.y; set_scroll_pos(window, scroll_y); - return 1; + window->redraw = 1; } else { if (window->button_callback) window->button_callback(window->button_cb_data, event); - return 1; + window->redraw = 1; } - return 0; } -int +void ledit_window_clipboard_event(ledit_window *window, XEvent *event) { /* FIXME: paste in bottom bar */ switch (event->type) { @@ -903,5 +1017,4 @@ ledit_window_clipboard_event(ledit_window *window, XEvent *event) { default: break; } - return 0; } diff --git a/window.h b/window.h @@ -1,6 +1,7 @@ typedef struct bottom_bar bottom_bar; typedef struct { + /* FIXME: maybe move to common? */ PangoFontMap *fontmap; PangoContext *context; PangoFontDescription *font; @@ -23,6 +24,21 @@ typedef struct { Atom xtarget; bottom_bar *bb; int bb_msg_shown; + int redraw; + enum ledit_mode mode; /* a bit ugly to duplicate this here... */ + + /* filtered events */ + struct timespec last_scroll; + struct timespec last_motion; + struct timespec last_resize; + XEvent last_scroll_event; + XEvent last_motion_event; + XEvent last_resize_event; + int last_scroll_valid; + int last_motion_valid; + int last_resize_valid; + int scroll_num; + int scroll_delta; /* IME stuff */ XIM xim; @@ -32,16 +48,18 @@ typedef struct { ledit_common *common; ledit_theme *theme; - void (*paste_callback)(void *, char *, int); + void (*paste_callback)(void *, char *, size_t); void (*scroll_callback)(void *, long); void (*button_callback)(void *, XEvent *); + void (*resize_callback)(void *); void *paste_cb_data; void *scroll_cb_data; void *button_cb_data; + void *resize_cb_data; char *mode_extra_text; } ledit_window; -ledit_window *ledit_window_create(ledit_common *common, ledit_theme *theme); +ledit_window *ledit_window_create(ledit_common *common, ledit_theme *theme, enum ledit_mode mode); void ledit_window_destroy(ledit_window *window); void ledit_window_cleanup(void); @@ -68,8 +86,13 @@ void ledit_window_set_mode_extra_text(ledit_window *window, char *text); void ledit_window_set_scroll_max(ledit_window *window, long max); void ledit_window_set_scroll_pos(ledit_window *window, long pos); void ledit_window_set_scroll_callback(ledit_window *window, void (*cb)(void *, long), void *data); -void ledit_window_set_paste_callback(ledit_window *window, void (*cb)(void *, char *, int), void *data); +void ledit_window_set_paste_callback(ledit_window *window, void (*cb)(void *, char *, size_t), void *data); void ledit_window_set_button_callback(ledit_window *window, void (*cb)(void *, XEvent *), void *data); +void ledit_window_set_resize_callback(ledit_window *window, void (*cb)(void *), void *data); +void ledit_window_register_resize(ledit_window *window, XEvent *event); +void ledit_window_register_button_press(ledit_window *window, XEvent *event); +void ledit_window_register_motion(ledit_window *window, XEvent *event); +void ledit_window_handle_filtered_events(ledit_window *window); void ledit_window_clear(ledit_window *window); void ledit_window_redraw(ledit_window *window); @@ -81,8 +104,8 @@ void clipboard_paste_clipboard(ledit_window *window); void clipboard_paste_primary(ledit_window *window); txtbuf *ledit_window_get_primary_clipboard_buffer(void); -int ledit_window_button_press(ledit_window *window, XEvent *event, int scroll_num); -int ledit_window_button_release(ledit_window *window, XEvent *event); -int ledit_window_drag_motion(ledit_window *window, XEvent *event); -int ledit_window_clipboard_event(ledit_window *window, XEvent *event); +void ledit_window_button_press(ledit_window *window, XEvent *event, int scroll_num); +void ledit_window_button_release(ledit_window *window, XEvent *event); +void ledit_window_drag_motion(ledit_window *window, XEvent *event); +void ledit_window_clipboard_event(ledit_window *window, XEvent *event); void xximspot(ledit_window *window, int x, int y);