tAdd documentation for view - 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 <[email protected]> | |
Date: Wed, 1 Dec 2021 10:53:40 +0100 | |
Add documentation for view | |
Diffstat: | |
M QUIRKS | 5 +++++ | |
M TODO | 3 +++ | |
M buffer.c | 3 ++- | |
M buffer.h | 7 ++++++- | |
M keys_basic.c | 12 ++++++------ | |
M keys_command.c | 7 ++++--- | |
M ledit.c | 2 +- | |
M view.c | 178 +++++++++++++++++------------… | |
M view.h | 369 ++++++++++++++++++++++++++++-… | |
9 files changed, 467 insertions(+), 119 deletions(-) | |
--- | |
diff --git a/QUIRKS b/QUIRKS | |
t@@ -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 | |
t@@ -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 | |
t@@ -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… | |
+buffer_add_view(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode… | |
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(ledi… | |
buffer->views[buffer->views_num] = view_create(buffer, theme, mode, li… | |
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 | |
t@@ -56,8 +56,13 @@ void buffer_set_hard_line_based(ledit_buffer *buffer, int h… | |
/* | |
* 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… | |
+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 | |
t@@ -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_inde… | |
/* get the middle position of char */ | |
- ledit_pos_to_x_softline(view, view->cur_line, view->cur_index,… | |
+ view_pos_to_x_softline(view, view->cur_line, view->cur_index, … | |
long abs_pos = vl->y_offset + y; | |
window_get_textview_size(view->window, &text_w, &text_h); | |
if (lines > 0) | |
t@@ -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, &e… | |
vl = view_get_line(view, view->cur_line); | |
- ledit_x_softline_to_pos(view, view->cur_line, x, sli, &view->c… | |
+ view->cur_index = view_x_softline_to_pos(view, view->cur_line,… | |
view_get_cursor_pixel_pos(view, view->cur_line, view->cur_inde… | |
long new_abs_pos = vl->y_offset + y; | |
view_scroll(view, view->display_offset + (new_abs_pos - abs_po… | |
t@@ -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, &so… | |
- ledit_xy_to_line_byte( | |
+ view_pos_to_x_softline(view, view->cur_line, view->cur_index, &x, &sof… | |
+ view_xy_to_line_byte( | |
view, x / PANGO_SCALE, y, 0, | |
&view->cur_line, &view->cur_index | |
); | |
t@@ -1636,8 +1636,8 @@ move_cursor_up_down(ledit_view *view, int dir) { | |
/* FIXME: when selecting on last line, moving down moves the c… | |
one (when it stays on the same line because it's the last o… | |
int lineno, x; | |
- ledit_pos_to_x_softline(view, view->cur_line, view->cur_index,… | |
- ledit_x_softline_to_pos(view, new_line, x, new_softline, &view… | |
+ view_pos_to_x_softline(view, view->cur_line, view->cur_index, … | |
+ view->cur_index = view_x_softline_to_pos(view, new_line, x, ne… | |
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 | |
t@@ -68,7 +68,7 @@ create_view(ledit_view *view, char *cmd, size_t l1, size_t l… | |
(void)cmd; | |
(void)l1; | |
(void)l2; | |
- buffer_add_view(view->buffer, view->theme, view->mode, view->cur_line,… | |
+ buffer_add_view(view->buffer, view->theme, view->mode, view->cur_line,… | |
return 0; | |
} | |
t@@ -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 | |
t@@ -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 | |
t@@ -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 mu… | |
/* FIXME: handle all undo within buffer to keep it consistent */ | |
#include <stdio.h> | |
t@@ -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… | |
+ | |
+/* 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); | |
/* | |
t@@ -45,9 +63,7 @@ static void set_pango_text_and_highlight(ledit_view *view, s… | |
*/ | |
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… | |
/* | |
t@@ -59,12 +75,17 @@ static PangoAttrList *get_pango_attributes(ledit_view *vie… | |
*/ | |
static void set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *… | |
+/* 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… | |
/* FIXME: This is weird because mode is per-view but the undo mode group | |
is changed for the entire buffer. */ | |
t@@ -105,6 +126,11 @@ view_create(ledit_buffer *buffer, ledit_theme *theme, enu… | |
} | |
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; | |
t@@ -244,6 +270,7 @@ void | |
view_cleanup(void) { | |
if (basic_attrs) | |
pango_attr_list_unref(basic_attrs); | |
+ basic_attrs = NULL; | |
} | |
static PangoAttrList * | |
t@@ -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 */ | |
t@@ -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); | |
t@@ -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; | |
t@@ -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); | |
t@@ -537,10 +559,10 @@ view_prev_cursor_pos(ledit_view *view, size_t line, size… | |
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 | |
t@@ -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,… | |
+view_pos_to_x_softline(ledit_view *view, size_t line, size_t pos, int *x_ret, … | |
ledit_view_line *vl = view_get_line(view, line); | |
PangoLayout *layout = get_pango_layout(view, line); | |
if (pos > INT_MAX) | |
t@@ -1041,9 +1063,8 @@ ledit_pos_to_x_softline(ledit_view *view, size_t line, s… | |
} | |
} | |
-/* FIXME: change this to return pos_ret directly */ | |
-void | |
-ledit_x_softline_to_pos(ledit_view *view, size_t line, int x, int softline, si… | |
+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); | |
t@@ -1060,16 +1081,17 @@ ledit_x_softline_to_pos(ledit_view *view, size_t line,… | |
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 | |
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 */ | |
t@@ -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, &s… | |
+ view_pos_to_x_softline(view, line_index1, byte_index1, &x, &sl… | |
if (l1 > 0 && l2 < view->lines_num - 1) { | |
rgl1 = l1; | |
rgb1 = 0; | |
t@@ -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; | |
t@@ -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, &s… | |
+ view_pos_to_x_softline(view, line_index1, byte_index1, &x, &so… | |
if (line_index1 == line_index2) { | |
int x_useless; | |
PangoLayout *layout = get_pango_layout(view, line_inde… | |
t@@ -1227,9 +1245,8 @@ view_delete_range_base( | |
/* cursor can be moved to next hard li… | |
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; | |
t@@ -1246,7 +1263,7 @@ view_delete_range_base( | |
ledit_view_line *vprevline = view_get_… | |
if (vprevline->text_dirty) | |
set_pango_text_and_highlight(v… | |
- ledit_x_softline_to_pos(view, new_line… | |
+ new_byte = view_x_softline_to_pos(view… | |
rgl1 = line_index1 - 1; | |
rgb1 = prevline->len; | |
rgl2 = line_index1; | |
t@@ -1278,25 +1295,22 @@ view_delete_range_base( | |
if (l2 == vline1->softlines - 1 && line_index1… | |
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 { | |
t@@ -1353,15 +1367,15 @@ view_delete_range_base( | |
ledit_view_line *new_vline = v… | |
if (new_vline->text_dirty) | |
set_pango_text_and_hig… | |
- ledit_x_softline_to_pos(view, … | |
+ new_byte = view_x_softline_to_… | |
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_b… | |
+ new_byte = view_x_softline_to_… | |
+ view, l2 + 1, x, 0 | |
); | |
rgl1 = l1; | |
rgb1 = 0; | |
t@@ -1391,7 +1405,7 @@ view_delete_range_base( | |
} | |
buffer_delete_line_section_base(view->buffer, … | |
new_line = l1; | |
- ledit_x_softline_to_pos(view, l2, x, 0, &new_b… | |
+ new_byte = view_x_softline_to_pos(view, l2, x,… | |
buffer_delete_line_entries_base(view->buffer, … | |
} else if (sl2 == vl2->softlines - 1) { | |
rgl1 = l1; | |
t@@ -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, s… | |
+ new_byte = view_x_softline_to_pos(view… | |
} 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) { | |
t@@ -1446,8 +1459,8 @@ view_delete_range_base( | |
a bit weird because the cursor will seem to… | |
same line, but it now includes the rest of … | |
(FIXME: this is probably not the best thing… | |
- ledit_x_softline_to_pos( | |
- view, l1, x, sl1 + 1 < vl1->softlines ? sl… | |
+ new_byte = view_x_softline_to_pos( | |
+ view, l1, x, sl1 + 1 < vl1->softlines ? sl… | |
); | |
} | |
} | |
t@@ -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) { | |
t@@ -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… | |
+ *byte_ret = view_x_softline_to_pos(view, hline, x, sl_… | |
} 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_re… | |
+ *byte_ret = view_x_softline_to_pos(view, hline + 1, x,… | |
} 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, by… | |
+ *byte_ret = view_x_softline_to_pos(view, hline, x, num… | |
} | |
} else if (cursor_y + strong.height / PANGO_SCALE > | |
view->display_offset + text_h) { | |
t@@ -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… | |
+ *byte_ret = view_x_softline_to_pos(view, hline, x, sl_… | |
} 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 = view_x_softline_to_pos(view, hline - 1, x,… | |
} 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, siz… | |
+view_xy_to_line_byte(ledit_view *view, int x, int y, int snap_to_nearest, size… | |
/* FIXME: store current line offset to speed this up */ | |
/* FIXME: use y_offset in lines */ | |
long h = 0; | |
t@@ -1811,6 +1827,9 @@ view_set_selection(ledit_view *view, size_t line1, size_… | |
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_n… | |
view->sel.line1 = line1; | |
t@@ -1820,12 +1839,13 @@ view_set_selection(ledit_view *view, size_t line1, siz… | |
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; | |
t@@ -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; | |
t@@ -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_li… | |
/* FIXME: return to old mode afterwards? */ | |
t@@ -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; | |
t@@ -2020,6 +2040,7 @@ undo_delete_helper(void *data, size_t line1, size_t byte… | |
void | |
view_undo(ledit_view *view) { | |
+ /* FIXME: maybe wipe selection */ | |
size_t min_line; | |
size_t old_line = view->cur_line; | |
ledit_undo( | |
t@@ -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 | |
t@@ -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_ | |
t@@ -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_in… | |
t@@ -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 { | |
t@@ -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 */ | |
t@@ -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_m… | |
+ | |
+/* | |
+ * 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… | |
+ | |
+/* | |
+ * 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… | |
+ | |
+/* | |
+ * 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 nu… | |
+ | |
+/* | |
+ * 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 nu… | |
-void view_next_word(ledit_view *view, size_t line, size_t byte, int num_repeat… | |
-void view_next_word_end(ledit_view *view, size_t line, size_t byte, int num_re… | |
-void view_next_bigword(ledit_view *view, size_t line, size_t byte, int num_rep… | |
-void view_next_bigword_end(ledit_view *view, size_t line, size_t byte, int num… | |
-void view_prev_word(ledit_view *view, size_t line, size_t byte, int num_repeat… | |
-void view_prev_bigword(ledit_view *view, size_t line, size_t byte, int num_rep… | |
+ | |
+/* | |
+ * 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 byt… | |
+ | |
+/* | |
+ * 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 … | |
+ | |
+/* | |
+ * 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, in… | |
-void ledit_pos_to_x_softline(ledit_view *view, size_t line, size_t pos, int *x… | |
-void ledit_x_softline_to_pos(ledit_view *view, size_t line, int x, int softlin… | |
+ | |
+/* | |
+ * 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_… | |
+ | |
+/* | |
+ * 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 softli… | |
+ | |
+/* | |
+ * 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… | |
+ | |
+/* | |
+ * 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 positi… | |
+ * is returned. | |
+ */ | |
+void view_xy_to_line_byte(ledit_view *view, int x, int y, int snap_to_nearest,… | |
+ | |
+/* | |
+ * 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 && *byt… | |
+ */ | |
void view_sort_selection(size_t *line1, size_t *byte1, size_t *line2, size_t *… | |
+ | |
+/* | |
+ * 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 l… | |
-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 |