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 7ce0fd78813ad0ebd4292b7dd471528f09417d35
parent f0abd591c8ef4c52c3649aba5b68d460a1c78f47
Author: lumidify <nobody@lumidify.org>
Date:   Wed,  1 Dec 2021 10:53:40 +0100

Add documentation for view

Diffstat:
MQUIRKS | 5+++++
MTODO | 3+++
Mbuffer.c | 3++-
Mbuffer.h | 7++++++-
Mkeys_basic.c | 12++++++------
Mkeys_command.c | 7++++---
Mledit.c | 2+-
Mview.c | 178++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mview.h | 369++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
9 files changed, 467 insertions(+), 119 deletions(-)

diff --git a/QUIRKS b/QUIRKS @@ -7,3 +7,8 @@ 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. + +* Scroll offset is stored as pixel value, so a view may scroll when text is + added or deleted in another view. Additionally, when a new view is created, + the scroll offset from the old view is taken, which may be weird if the + window of the new view is a different size. diff --git a/TODO b/TODO @@ -1,2 +1,5 @@ * Load file in background so text is already shown while still loading the rest of a big file. +* Try to copy vi behavior where the cursor jumps back to the original + column after moving over a line that was not as long as the original + cursor position. diff --git a/buffer.c b/buffer.c @@ -158,13 +158,14 @@ buffer_set_hard_line_based(ledit_buffer *buffer, int hl) { } void -buffer_add_view(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size_t line, size_t pos) { +buffer_add_view(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size_t line, size_t pos, long scroll_offset) { 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]); + view_scroll(buffer->views[buffer->views_num], scroll_offset); buffer->views_num = new_num; } diff --git a/buffer.h b/buffer.h @@ -56,8 +56,13 @@ void buffer_set_hard_line_based(ledit_buffer *buffer, int hl); /* * Add a new view to the buffer. + * 'line' and 'pos' are the initial line and byte position of the cursor. + * 'scroll_offset' is the initial pixel scroll offset. */ -void buffer_add_view(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size_t line, size_t pos); +void buffer_add_view( + ledit_buffer *buffer, ledit_theme *theme, + enum ledit_mode mode, size_t line, size_t pos, long scroll_offset +); /* * Remove the given view from the buffer. diff --git a/keys_basic.c b/keys_basic.c @@ -657,7 +657,7 @@ scroll_lines(ledit_view *view, int lines, int dir) { 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(view, view->cur_line, view->cur_index, &x, &sli); + view_pos_to_x_softline(view, view->cur_line, view->cur_index, &x, &sli); long abs_pos = vl->y_offset + y; window_get_textview_size(view->window, &text_w, &text_h); if (lines > 0) @@ -673,7 +673,7 @@ scroll_lines(ledit_view *view, int lines, int dir) { 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->cur_index = view_x_softline_to_pos(view, view->cur_line, x, sli); 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)); @@ -809,8 +809,8 @@ move_half_screen(ledit_view *view, int movement) { /* try to keep current x position of cursor */ int x, softline; /* FIXME: properly document what uses PANGO_SCALE and what not */ - ledit_pos_to_x_softline(view, view->cur_line, view->cur_index, &x, &softline); - ledit_xy_to_line_byte( + view_pos_to_x_softline(view, view->cur_line, view->cur_index, &x, &softline); + view_xy_to_line_byte( view, x / PANGO_SCALE, y, 0, &view->cur_line, &view->cur_index ); @@ -1636,8 +1636,8 @@ move_cursor_up_down(ledit_view *view, int dir) { /* 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(view, view->cur_line, view->cur_index, &x, &lineno); - ledit_x_softline_to_pos(view, new_line, x, new_softline, &view->cur_index); + view_pos_to_x_softline(view, view->cur_line, view->cur_index, &x, &lineno); + view->cur_index = view_x_softline_to_pos(view, new_line, x, new_softline); if (view->cur_line != new_line) view_wipe_line_cursor_attrs(view, view->cur_line); view->cur_line = new_line; diff --git a/keys_command.c b/keys_command.c @@ -68,7 +68,7 @@ create_view(ledit_view *view, char *cmd, size_t l1, size_t l2) { (void)cmd; (void)l1; (void)l2; - buffer_add_view(view->buffer, view->theme, view->mode, view->cur_line, view->cur_index); + buffer_add_view(view->buffer, view->theme, view->mode, view->cur_line, view->cur_index, view->display_offset); return 0; } @@ -79,8 +79,9 @@ close_view(ledit_view *view, char *cmd, size_t l1, size_t l2) { (void)l2; /* FIXME: This will lead to problems if I add something that requires access to the view after the command is handled. */ - buffer_remove_view(view->buffer, view); - if (view->buffer->views_num == 0) { + ledit_buffer *buffer = view->buffer; + buffer_remove_view(buffer, view); + if (buffer->views_num == 0) { ledit_cleanup(); exit(0); } diff --git a/ledit.c b/ledit.c @@ -245,7 +245,7 @@ setup(int argc, char *argv[]) { /* FIXME: encapsulate */ buffer->filename = ledit_strdup(argv[1]); } - buffer_add_view(buffer, theme, NORMAL, 0, 0); + buffer_add_view(buffer, theme, NORMAL, 0, 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); diff --git a/view.c b/view.c @@ -1,5 +1,4 @@ /* 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> @@ -25,18 +24,37 @@ #include "window.h" #include "buffer.h" +/* Basic attributes set for all text. */ static PangoAttrList *basic_attrs = NULL; +/* Initialize line with default values. */ static void init_line(ledit_view *view, ledit_view_line *line); + +/* Copy given selection to x primary selection - must 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); + +/* Callbacks for cache handling */ 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. - */ +/* line_visible_callback just converts void *data to ledit_view *view */ +static int view_line_visible(ledit_view *view, size_t index); +static int line_visible_callback(void *data, size_t line); + +/* Redraw just the text. */ +static void view_redraw_text(ledit_view *view); + +/* Callbacks */ +static void view_button_handler(void *data, XEvent *event); +static void view_scroll_handler(void *view, long pos); +static void paste_callback(void *data, char *text, size_t len); + +/* Render a line onto a pixmap that is assigned from the cache. */ +static void render_line(ledit_view *view, size_t line_index); + +/* 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); /* @@ -45,9 +63,7 @@ static void set_pango_text_and_highlight(ledit_view *view, size_t line); */ static PangoLayout *get_pango_layout(ledit_view *view, size_t line); -/* - * Get an attribute list for a text highlight between the given range. - */ +/* 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); /* @@ -59,12 +75,17 @@ static PangoAttrList *get_pango_attributes(ledit_view *view, size_t start_byte, */ static void set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *layout); +/* Swap size_t values. */ 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); +/* Move the gap of the line gap buffer to index 'index'. */ +static void move_line_gap(ledit_view *view, size_t index); + +/* + * Resize the line gap buffer so it can hold at least 'min_size' lines and + * move the gap to line at position 'index'. + */ +static void resize_and_move_line_gap(ledit_view *view, size_t min_size, size_t index); /* FIXME: This is weird because mode is per-view but the undo mode group is changed for the entire buffer. */ @@ -105,6 +126,11 @@ view_create(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size } view->cur_line = line; view->cur_index = pos; + if (line >= buffer->lines_num) + view->cur_line = buffer->lines_num - 1; + ledit_line *ll = buffer_get_line(buffer, view->cur_line); + if (pos > ll->len) + pos = 0; /* should never happen anyways */ view->total_height = 0; view->display_offset = 0; view->sel.line1 = view->sel.byte1 = 0; @@ -244,6 +270,7 @@ void view_cleanup(void) { if (basic_attrs) pango_attr_list_unref(basic_attrs); + basic_attrs = NULL; } static PangoAttrList * @@ -328,7 +355,7 @@ line_visible_callback(void *data, size_t line) { /* FIXME: standardize variable names (line/line_index, etc.) */ void -view_render_line(ledit_view *view, size_t line_index) { +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 */ @@ -419,9 +446,6 @@ invalidate_layout_line_helper(void *data, size_t 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); @@ -453,11 +477,12 @@ view_recalc_line(ledit_view *view, size_t line) { 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); + /* force first line to offset 0 */ + if (line == 0) + l->y_offset = 0; int text_w, text_h; window_get_textview_size(view->window, &text_w, &text_h); long off = l->y_offset; @@ -478,13 +503,10 @@ view_recalc_from_line(ledit_view *view, size_t line) { 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 +static int view_line_visible(ledit_view *view, size_t index) { int text_w, text_h; window_get_textview_size(view->window, &text_w, &text_h); @@ -537,10 +559,10 @@ view_prev_cursor_pos(ledit_view *view, size_t line, size_t byte, int num) { break; cur_byte = line_prev_utf8(ll, cur_byte); } - if (cur_byte <= 0) + if (cur_byte == 0) break; } - return cur_byte > 0 ? cur_byte : 0; + return cur_byte; } static int @@ -1017,7 +1039,7 @@ get_pango_layout(ledit_view *view, size_t line) { /* 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) { +view_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) @@ -1041,9 +1063,8 @@ ledit_pos_to_x_softline(ledit_view *view, size_t line, size_t pos, int *x_ret, i } } -/* 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) { +size_t +view_x_softline_to_pos(ledit_view *view, size_t line, int x, int softline) { int trailing = 0; int x_relative = x; ledit_view_line *vl = view_get_line(view, line); @@ -1060,16 +1081,17 @@ ledit_x_softline_to_pos(ledit_view *view, size_t line, int x, int softline, size pango_layout_line_x_to_index( pango_line, x_relative, &tmp_pos, &trailing ); - *pos_ret = (size_t)tmp_pos; + size_t pos = (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 = buffer_get_line(view->buffer, line); while (trailing > 0) { trailing--; - *pos_ret = line_next_utf8(ll, *pos_ret); + pos = line_next_utf8(ll, pos); } } + return pos; } size_t @@ -1112,11 +1134,9 @@ view_delete_range( 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++) { - buffer_recalc_all_views_from_line( - view->buffer, min > 0 ? min - 1 : min - ); - } + 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 */ @@ -1150,7 +1170,7 @@ view_delete_range_base( } size_t dell1 = l1, dell2 = l2; ledit_line *ll = buffer_get_line(view->buffer, line_index1); - ledit_pos_to_x_softline(view, line_index1, byte_index1, &x, &sl_useless); + view_pos_to_x_softline(view, line_index1, byte_index1, &x, &sl_useless); if (l1 > 0 && l2 < view->lines_num - 1) { rgl1 = l1; rgb1 = 0; @@ -1184,15 +1204,13 @@ view_delete_range_base( /* 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 + new_byte = view_x_softline_to_pos( + view, l2 + 1, x, 0 ); } else if (l1 > 0) { new_line = l1 - 1; - ledit_x_softline_to_pos( - view, l1 - 1, - x, 0, &new_byte + new_byte = view_x_softline_to_pos( + view, l1 - 1, x, 0 ); } else { dell1 = l1 + 1; @@ -1212,7 +1230,7 @@ view_delete_range_base( int x, softline1, softline2; ledit_line *line1 = 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); + view_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); @@ -1227,9 +1245,8 @@ view_delete_range_base( /* 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 + tmp_byte = view_x_softline_to_pos( + view, line_index1 + 1, x, 0 ); new_byte = (size_t)tmp_byte; rgl1 = line_index1; @@ -1246,7 +1263,7 @@ view_delete_range_base( 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); + new_byte = view_x_softline_to_pos(view, new_line, x, vprevline->softlines - 1); rgl1 = line_index1 - 1; rgb1 = prevline->len; rgl2 = line_index1; @@ -1278,25 +1295,22 @@ view_delete_range_base( 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 + tmp_byte = view_x_softline_to_pos( + view, line_index1 + 1, x, 0 ); 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 + tmp_byte = view_x_softline_to_pos( + view, line_index1, x, l1 ); 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 + tmp_byte = view_x_softline_to_pos( + view, line_index1, x, l1 - 1 ); new_byte = (size_t)tmp_byte; } else { @@ -1353,15 +1367,15 @@ view_delete_range_base( 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); + new_byte = view_x_softline_to_pos(view, new_line, x, new_vline->softlines - 1); 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 + new_byte = view_x_softline_to_pos( + view, l2 + 1, x, 0 ); rgl1 = l1; rgb1 = 0; @@ -1391,7 +1405,7 @@ view_delete_range_base( } buffer_delete_line_section_base(view->buffer, l2, 0, rgb2); new_line = l1; - ledit_x_softline_to_pos(view, l2, x, 0, &new_byte); + new_byte = view_x_softline_to_pos(view, l2, x, 0); buffer_delete_line_entries_base(view->buffer, l1, l2 - 1); } else if (sl2 == vl2->softlines - 1) { rgl1 = l1; @@ -1400,12 +1414,11 @@ view_delete_range_base( rgb2 = ll2->len; if (l2 + 1 == view->lines_num) { new_line = l1; - ledit_x_softline_to_pos(view, l1, x, sl1 - 1, &new_byte); + new_byte = view_x_softline_to_pos(view, l1, x, sl1 - 1); } else { new_line = l1 + 1; - ledit_x_softline_to_pos( - view, l2 + 1, - x, 0, &new_byte + new_byte = view_x_softline_to_pos( + view, l2 + 1, x, 0 ); } if (text_ret) { @@ -1446,8 +1459,8 @@ view_delete_range_base( 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 + new_byte = view_x_softline_to_pos( + view, l1, x, sl1 + 1 < vl1->softlines ? sl1 + 1 : sl1 ); } } @@ -1569,7 +1582,7 @@ view_get_nearest_legal_pos( 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); + view_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) { @@ -1597,16 +1610,16 @@ view_get_nearest_legal_pos( 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); + *byte_ret = view_x_softline_to_pos(view, hline, x, sl_index); } 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); + *byte_ret = view_x_softline_to_pos(view, hline + 1, x, 0); } 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); + *byte_ret = view_x_softline_to_pos(view, hline, x, num_sl - 1); } } else if (cursor_y + strong.height / PANGO_SCALE > view->display_offset + text_h) { @@ -1634,24 +1647,27 @@ view_get_nearest_legal_pos( 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); + *byte_ret = view_x_softline_to_pos(view, hline, x, sl_index); } 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); + *byte_ret = view_x_softline_to_pos(view, hline - 1, x, num_sl - 1); } 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); + *byte_ret = view_x_softline_to_pos(view, hline, x, 0); } + } else { + *line_ret = line; + *byte_ret = byte; } } 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) { +view_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; @@ -1811,6 +1827,9 @@ view_set_selection(ledit_view *view, size_t line1, size_t byte1, size_t line2, s vl->highlight_dirty = 1; } } + /* force current line to recalculate in case it wasn't covered above */ + ledit_view_line *vl = view_get_line(view, line2); + 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; @@ -1820,12 +1839,13 @@ view_set_selection(ledit_view *view, size_t line1, size_t byte1, size_t line2, s view->sel_valid = 1; } -void +static void view_scroll_handler(void *view, long pos) { + /* FIXME: check for invalid pos? */ ((ledit_view *)view)->display_offset = pos; } -void +static void view_button_handler(void *data, XEvent *event) { size_t l, b; ledit_view *view= (ledit_view *)data; @@ -1835,7 +1855,7 @@ view_button_handler(void *data, XEvent *event) { switch (event->type) { case ButtonPress: snap = view->mode == NORMAL ? 0 : 1; - ledit_xy_to_line_byte(view, x, y, snap, &l, &b); + view_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; @@ -1855,7 +1875,7 @@ view_button_handler(void *data, XEvent *event) { case MotionNotify: if (view->selecting) { y = y >= 0 ? y : 0; - ledit_xy_to_line_byte(view, x, y, 1, &l, &b); + view_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? */ @@ -1898,7 +1918,7 @@ view_redraw_text(ledit_view *view) { 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); + render_line(view, i); } int final_y = 0; int dest_y = h - view->display_offset; @@ -2020,6 +2040,7 @@ undo_delete_helper(void *data, size_t line1, size_t byte1, size_t line2, size_t void view_undo(ledit_view *view) { + /* FIXME: maybe wipe selection */ size_t min_line; size_t old_line = view->cur_line; ledit_undo( @@ -2033,6 +2054,7 @@ view_undo(ledit_view *view) { } view_wipe_line_cursor_attrs(view, old_line); view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + /* FIXME: why is this check here? */ if (min_line < view->lines_num) { buffer_recalc_all_views_from_line( view->buffer, min_line > 0 ? min_line - 1 : min_line diff --git a/view.h b/view.h @@ -1,3 +1,8 @@ +/* + * A view consists of a window and everything necessary for displaying + * the contents of the associated buffer in that window. + */ + #ifndef _LEDIT_VIEW_H_ #define _LEDIT_VIEW_H_ @@ -10,6 +15,8 @@ enum action_type { ACTION_GRABKEY /* pass next key to given callback */ }; +/* struct that is returned by key handlers to tell the + main event manager what key handler to call next */ struct action { enum action_type type; struct action (*callback)(ledit_view *view, XEvent *event, int lang_index); @@ -38,12 +45,10 @@ typedef struct { /* 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 + CMD_EDIT, /* edit command */ + CMD_EDITSEARCH, /* edit search term */ + CMD_EDITSEARCHB, /* edit search term for backwards search */ + CMD_SUBSTITUTE /* confirm substitution */ }; struct ledit_view { @@ -52,7 +57,7 @@ struct ledit_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 */ + /* current command type - used by key handler in keys_command.c */ 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 */ @@ -70,89 +75,395 @@ struct ledit_view { }; enum delete_mode { - DELETE_CHAR, - DELETE_SOFTLINE, - DELETE_HARDLINE + DELETE_CHAR, /* delete an exact line and byte range */ + DELETE_SOFTLINE, /* delete a range of complete softlines */ + DELETE_HARDLINE /* delete a range of complete hardlines */ }; +/* + * Set the mode of the view. + * This changes the mode group of the associated buffer's + * undo stack and changes the mode display in the window. + */ 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); + +/* + * Create a view with associated buffer 'buffer' and theme 'theme'. + * The initial mode, line, and byte position are given, respectively, + * by 'mode', 'line', and 'pos'. + */ +ledit_view *view_create( + ledit_buffer *buffer, ledit_theme *theme, + enum ledit_mode mode, size_t line, size_t pos +); + +/* + * Get the view line at the given index. + */ ledit_view_line *view_get_line(ledit_view *view, size_t index); + +/* + * These notification functions are called by the buffer when text + * is changed in order to keep all views in sync. + */ + +/* + * Notify the view that 'len' bytes of text have been inserted at + * line 'line' and byte position 'index'. + * This marks the line as dirty, adjusts the cursor position, if it + * is on the same line, and sets the selection to just the current + * cursor position, if there was a valid selection. + * The line heights and offsets are not recalculated. + */ void view_notify_insert_text(ledit_view *view, size_t line, size_t index, size_t len); + +/* + * Notify the view that 'len' bytes of text have been deleted + * starting at line 'line' and byte position 'index'. + * This marks the line as dirty, adjusts the cursor position, if it + * is on the same line, and sets the selection to just the current + * cursor position, if there was a valid selection. + * The line heights and offsets are not recalculated. + */ void view_notify_delete_text(ledit_view *view, size_t line, size_t index, size_t len); + +/* + * Notify the view that a line has been appended after line 'line'. + * This adjusts the cursor position if necessary and sets the selection + * to just the current cursor position, if there was a valid selection. + * The line heights and offsets are not recalculated. + */ void view_notify_append_line(ledit_view *view, size_t line); + +/* + * Notify the view that all lines from 'index1' to 'index2' (inclusive) + * have been deleted. + * This adjusts the cursor position if necessary and sets the selection + * to just the current cursor position, if there was a valid selection. + * The line heights and offsets are not recalculated. + */ void view_notify_delete_lines(ledit_view *view, size_t index1, size_t index2); + +/* + * Destroy a view and its window. + */ void view_destroy(ledit_view *view); + +/* + * Perform cleanup of global data. + */ void view_cleanup(void); + +/* + * Set a cursor highlight on the character at line 'line' and + * byte position 'index'. + */ void view_set_line_cursor_attrs(ledit_view *view, size_t line, size_t index); + +/* + * Remove cursor highlight from line 'line'. + */ void view_wipe_line_cursor_attrs(ledit_view *view, size_t line); -void view_render_line(ledit_view *view, size_t line_index); + +/* + * Recalculate the height of line 'line'. + * If it has changed, the offsets of all following lines are recalculated. + */ void view_recalc_line(ledit_view *view, size_t line); + +/* + * Recalculate the height and offset of all lines starting at 'line'. + * If 'line' is 0, the offset is set to 0. + */ void view_recalc_from_line(ledit_view *view, size_t line); + +/* + * Shortcut for recalculating all lines starting at 0. + */ void view_recalc_all_lines(ledit_view *view); -int view_line_visible(ledit_view *view, size_t index); + +/* + * The cursor movement functions here logically belong to the buffer, + * but they use various unicode attributes that pango exposes, so + * they have to be here as long as no separate library for unicode + * processing is used. + */ + +/* + * Get the byte position of the cursor position 'num' positions after + * the position at line 'line' and byte position 'byte'. + * If it would be past the end of the line, the length of the line is returned. + */ size_t view_next_cursor_pos(ledit_view *view, size_t line, size_t byte, int num); + +/* + * Get the byte position of the cursor position 'num' positions before + * the position at line 'line' and byte position 'byte'. + * If it would be past the beginning of the line, 0 is returned. + */ 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); + +/* + * The next 6 functions all return a line, a byte position, and a + * "real byte position". In the case of the "*prev*" functions, it + * is actually the same as the normal byte position (it is just + * returned to keep the interface the same), but in the case of the + * "*next*" functions, it can be different. For instance, when + * moving forward to the end of a word, the normal byte index will + * be before the last cursor of the word (i.e. the position of the + * cursor highlight in normal mode), while the real byte index is + * right after the word, so it can be used for deleting to the end + * of the word. + */ + +/* + * Words are defined by unicode semantics (as interpreted by pango), + * while bigwords are simply blocks of non-whitespace characters. + */ + +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 +); + +/* + * Get the byte position of the next non-whitespace character starting at + * 'byte' (including 'byte' itself), or the length of the line if there is + * no further non-whitespace character. + */ size_t view_line_next_non_whitespace(ledit_view *view, size_t line, size_t byte); + +/* + * Get the byte boundary of the softline at line 'line' and byte position 'pos'. + */ 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 ); + +/* + * Get the byte boundary of the softline with index 'softline' + * in the hardline 'line'. + */ void view_get_softline_bounds( ledit_view *view, size_t line, int softline, size_t *start_byte_ret, size_t *end_byte_ret ); + +/* + * Get the number of softlines in line 'line'. + */ int view_get_softline_count(ledit_view *view, size_t line); + +/* + * Get the softline index at hardline 'line' and byte position 'pos'. + */ int view_pos_to_softline(ledit_view *view, size_t line, size_t pos); + +/* + * Get the pixel position and height of the cursor on hardline 'line' + * at byte position '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); + +/* + * Get the byte index of the cursor if it is moved visually 'movement' + * positions from the position as line 'line' and byte position 'pos', + * where a negative number is to the left and a positive number to the + * right. + * If 'prev_index_ret' is not NULL, the previous valid cursor position + * is written to it - this is used in normal mode to move back one + * position if the cursor is at the end of the line. For some reason, + * using 'view_get_legal_normal_pos' doesn't work here. I still need + * to figure out why. + */ 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); + +/* + * Convert a line index and byte position to an x position and softline + * index. The x position is in pango units, not pixels. + * In normal mode, the middle of the character is returned instead of the + * beginning. + */ +void view_pos_to_x_softline(ledit_view *view, size_t line, size_t pos, int *x_ret, int *softline_ret); + +/* + * Convert a line index, softline index, and x position (in pango units, + * not pixels) to a byte position. + * In insert mode, the returned byte position is the closest cursor + * position. In normal mode, it is simply the beginning of the character + * (or, rather, grapheme) that the x position was on, regardless of where + * on that character the position was. + */ +size_t view_x_softline_to_pos(ledit_view *view, size_t line, int x, int softline); + +/* + * Get a legal normal mode position, i.e. move back one cursor position + * if 'pos' is at the very end of the line. + */ size_t view_get_legal_normal_pos(ledit_view *view, size_t line, size_t pos); -void view_delete_range( + +/* + * Delete a range accorxing to a delete_mode. + * The line and byte indeces do not need to be sorted (in fact, they often + * shouldn't be, as shown in the next sentence). + * If 'delmode' is DELETE_HARDLINE or DELETE_SOFTLINE, 'line_index1' and + * 'byte_index1' are used to determine where the cursor should be after + * the deletion. This new position is written to 'new_line_ret' and + * 'new_byte_ret'. + * If 'delmode' is DELETE_SOFTLINE, the byte indeces are additionally used + * to determine which softlines the range bounds are on. + * Both line deletion modes make sure that there is at least one line left + * in the buffer afterwards, although it may have its text deleted. + * If 'delmode' is DELETE_CHAR, the exact specified range is deleted, and + * the new line and byte are simply the beginning of the range. + * In all cases, the final deletion range is written to 'final_range_ret', + * and the deleted text is written to 'text_ret'. + * In normal mode, the new cursor index is always at a valid normal mode + * position. + * All return arguments may be NULL. + * This function does not recalculate the line heights or offsets. + */ +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_delete_range_base( + +/* + * Same as 'view_delete_range_base', but the line heights and offsets of + * all views are recalculated afterwards. + */ +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 ); + +/* + * Resize the size of the textview, i.e. resize the line widths + * and update all scrolling related data. + * 'data' is the view, but is given as a void pointer so the + * function can be used as a callback. + */ void view_resize_textview(void *data); + +/* + * Scroll to the given pixel offset. + * The given offset is sanity-checked so new illegal positions can result. + */ void view_scroll(ledit_view *view, long new_offset); + +/* + * Get the position nearest to 'line' and 'byte' that is currently shown + * on screen. + */ 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); + +/* + * Convert pixel coordinates to line and byte indeces. The pixel coordinates + * are relative to the current textview. + * If 'snap_to_nearest' is set, but grapheme boundary nearest to the position + * is returned. Otherwise, the start position of the grapheme under the position + * is returned. + */ +void view_xy_to_line_byte(ledit_view *view, int x, int y, int snap_to_nearest, size_t *line_ret, size_t *byte_ret); + +/* + * Scroll so that the given cursor position is at the very top of the view. + * Note that this may not be entirely true since the final position is + * sanity-checked to be within the scroll bounds. + */ void view_scroll_to_pos_top(ledit_view *view, size_t line, size_t byte); + +/* + * Scroll so that the given cursor position is at the very bottom of the view. + * Note that this may not be entirely true since the final position is + * sanity-checked to be within the scroll bounds. + */ void view_scroll_to_pos_bottom(ledit_view *view, size_t line, size_t byte); + +/* + * Scroll so that the current cursor position is visible on screen. + */ void view_ensure_cursor_shown(ledit_view *view); + +/* + * Sort the given range so that (*line1 < *line2) or (*line1 == *line2 && *byte1 <= *byte2). + */ void view_sort_selection(size_t *line1, size_t *byte1, size_t *line2, size_t *byte2); + +/* + * Clear the selection. + */ void view_wipe_selection(ledit_view *view); + +/* + * Set the selection to the given range. + * The range does not need to be sorted. + */ 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); + +/* + * Redraw the view. + * This only redraws if the redraw bit of the view or window are set. + * This should all be set automatically. + */ void view_redraw(ledit_view *view); + +/* + * Perform an undo step. + * The cursor position of the view is set to the stored position + * in the undo stack. + * The line heights and offsets are recalculated. + */ void view_undo(ledit_view *view); + +/* + * Perform a redo step. + * The cursor position of the view is set to the stored position + * in the undo stack. + * The line heights and offsets are recalculated. + */ void view_redo(ledit_view *view); + +/* + * Paste the X11 clipboard at the current cursor position. + */ void view_paste_clipboard(ledit_view *view); + +/* + * Paste the X11 primary selection at the current cursor position. + */ void view_paste_primary(ledit_view *view); #endif