view.c - ledit - Text editor (WIP) | |
git clone git://lumidify.org/ledit.git (fast, but not encrypted) | |
git clone https://lumidify.org/ledit.git (encrypted, but very slow) | |
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/… | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
view.c (71581B) | |
--- | |
1 #include <stdio.h> | |
2 #include <errno.h> | |
3 #include <string.h> | |
4 #include <limits.h> | |
5 #include <stdlib.h> | |
6 | |
7 #include <X11/Xlib.h> | |
8 #include <X11/Xutil.h> | |
9 #include <X11/Xatom.h> | |
10 #include <pango/pangoxft.h> | |
11 #include <pango/pango-utils.h> /* for PANGO_VERSION_CHECK */ | |
12 #include <X11/extensions/Xdbe.h> | |
13 | |
14 #include "util.h" | |
15 #include "pango-compat.h" | |
16 #include "memory.h" | |
17 #include "common.h" | |
18 #include "clipboard.h" | |
19 #include "txtbuf.h" | |
20 #include "undo.h" | |
21 #include "cache.h" | |
22 #include "window.h" | |
23 #include "buffer.h" | |
24 #include "assert.h" | |
25 #include "configparser.h" | |
26 | |
27 /* FIXME: handle selections better - it can happen that the cursor is mo… | |
28 the selection, leading to weird "jumping selection" - this should be … | |
29 | |
30 /* Basic attributes set for all text. */ | |
31 static PangoAttrList *basic_attrs = NULL; | |
32 | |
33 /* Initialize line with default values. */ | |
34 static void init_line(ledit_view *view, ledit_view_line *line); | |
35 | |
36 /* Copy given selection to x primary selection - must be sorted already.… | |
37 static void copy_selection_to_x_primary(ledit_view *view, size_t line1, … | |
38 | |
39 /* Callbacks for cache handling */ | |
40 static void set_pixmap_line_helper(void *data, size_t line, size_t index… | |
41 static void invalidate_pixmap_line_helper(void *data, size_t line); | |
42 static void set_layout_line_helper(void *data, size_t line, size_t index… | |
43 static void invalidate_layout_line_helper(void *data, size_t line); | |
44 | |
45 /* line_visible_callback just converts void *data to ledit_view *view */ | |
46 static int view_line_visible(ledit_view *view, size_t index); | |
47 static int line_visible_callback(void *data, size_t line); | |
48 | |
49 /* Redraw just the text. */ | |
50 static void view_redraw_text(ledit_view *view); | |
51 | |
52 /* Callbacks */ | |
53 static void view_button_handler(void *data, XEvent *event); | |
54 static void view_scroll_handler(void *view, long pos); | |
55 | |
56 /* Render a line onto a pixmap that is assigned from the cache. */ | |
57 static void render_line(ledit_view *view, size_t line_index); | |
58 | |
59 /* Assign a cache index to line and set text and highlight of the pango … | |
60 static void set_pango_text_and_highlight(ledit_view *view, size_t line); | |
61 | |
62 /* | |
63 * Get the pango layout for line. | |
64 * This first assigns a cache index (by calling set_pango_text_and_highl… | |
65 */ | |
66 static PangoLayout *get_pango_layout(ledit_view *view, size_t line); | |
67 | |
68 /* Get an attribute list for a text highlight between the given range. */ | |
69 static PangoAttrList *get_pango_attributes( | |
70 size_t start_byte, size_t end_byte, | |
71 XRenderColor fg, XRenderColor bg | |
72 ); | |
73 | |
74 /* | |
75 * Set the attributes for a PangoLayout belonging to the given line inde… | |
76 * If the line is part of the view's selection, the selection is set. | |
77 * If that is not the case but cursor_index is set for the line, the cha… | |
78 * at that position is highlighted (this is used for the normal mode cur… | |
79 * Otherwise, the default attributes (basic_attrs) are set. | |
80 */ | |
81 static void set_line_layout_attrs(ledit_view *view, size_t line, PangoLa… | |
82 | |
83 /* Move the gap of the line gap buffer to index 'index'. */ | |
84 static void move_line_gap(ledit_view *view, size_t index); | |
85 | |
86 /* | |
87 * Resize the line gap buffer so it can hold at least 'min_size' lines a… | |
88 * move the gap to line at position 'index'. | |
89 */ | |
90 static void resize_and_move_line_gap(ledit_view *view, size_t min_size, … | |
91 | |
92 /* FIXME: This is weird because mode is per-view but the undo mode group | |
93 is changed for the entire buffer. */ | |
94 void | |
95 view_set_mode(ledit_view *view, ledit_mode mode) { | |
96 view->mode = mode; | |
97 undo_change_mode_group(view->buffer->undo); | |
98 } | |
99 | |
100 ledit_view * | |
101 view_create(ledit_buffer *buffer, ledit_mode mode, size_t line, size_t p… | |
102 if (basic_attrs == NULL) { | |
103 basic_attrs = pango_attr_list_new(); | |
104 #if PANGO_VERSION_CHECK(1, 44, 0) | |
105 PangoAttribute *no_hyphens = pango_attr_insert_hyphens_n… | |
106 pango_attr_list_insert(basic_attrs, no_hyphens); | |
107 #endif | |
108 } | |
109 | |
110 ledit_view *view = ledit_malloc(sizeof(ledit_view)); | |
111 view->mode = mode; | |
112 view->buffer = buffer; | |
113 view->window = window_create(buffer->common, buffer->clipboard); | |
114 view->cache = cache_create(buffer->common->dpy); | |
115 view->lock_text = NULL; | |
116 view->cur_action = (struct action){ACTION_NONE, NULL}; | |
117 window_set_scroll_callback(view->window, &view_scroll_handler, v… | |
118 window_set_button_callback(view->window, &view_button_handler, v… | |
119 window_set_resize_callback(view->window, &view_resize_textview, … | |
120 | |
121 view->lines = ledit_reallocarray(NULL, buffer->lines_cap, sizeof… | |
122 view->lines_cap = buffer->lines_cap; | |
123 view->lines_gap = buffer->lines_num; | |
124 view->lines_num = buffer->lines_num; | |
125 for (size_t i = 0; i < view->lines_num; i++) { | |
126 init_line(view, &view->lines[i]); | |
127 } | |
128 view->cur_line = line; | |
129 view->cur_index = pos; | |
130 if (line >= buffer->lines_num) | |
131 view->cur_line = buffer->lines_num - 1; | |
132 ledit_line *ll = buffer_get_line(buffer, view->cur_line); | |
133 if (pos > ll->len) | |
134 pos = 0; /* should never happen anyways */ | |
135 view->total_height = 0; | |
136 view->display_offset = 0; | |
137 view->sel.line1 = view->sel.byte1 = 0; | |
138 view->sel.line2 = view->sel.byte2 = 0; | |
139 view->destroy = 0; | |
140 view->button2_pressed = 0; | |
141 view->selecting = 0; | |
142 view->sel_valid = 0; | |
143 view->redraw = 1; | |
144 ledit_view_line *vl = view_get_line(view, line); | |
145 vl->cursor_index = pos; | |
146 vl->cursor_index_valid = 1; | |
147 | |
148 view_recalc_all_lines(view); | |
149 | |
150 return view; | |
151 } | |
152 | |
153 void | |
154 view_lock(ledit_view *view, char *lock_text) { | |
155 free(view->lock_text); | |
156 view->lock_text = ledit_strdup(lock_text); | |
157 } | |
158 | |
159 void | |
160 view_unlock(ledit_view *view) { | |
161 free(view->lock_text); | |
162 view->lock_text = NULL; | |
163 } | |
164 | |
165 ledit_view_line * | |
166 view_get_line_impl(ledit_view *view, size_t index, const char *file, int… | |
167 ledit_assert_manual(index < view->lines_num, file, line, func); | |
168 return index < view->lines_gap ? | |
169 &view->lines[index] : | |
170 &view->lines[index + view->lines_cap - view->lines_num]; | |
171 } | |
172 | |
173 static void | |
174 move_line_gap(ledit_view *view, size_t index) { | |
175 move_gap( | |
176 view->lines, sizeof(ledit_view_line), index, | |
177 view->lines_gap, view->lines_cap, view->lines_num, | |
178 &view->lines_gap | |
179 ); | |
180 } | |
181 | |
182 static void | |
183 resize_and_move_line_gap(ledit_view *view, size_t min_size, size_t index… | |
184 view->lines = resize_and_move_gap( | |
185 view->lines, sizeof(ledit_view_line), | |
186 view->lines_gap, view->lines_cap, view->lines_num, | |
187 min_size, index, | |
188 &view->lines_gap, &view->lines_cap | |
189 ); | |
190 } | |
191 | |
192 /* Checking vl->cursor_index_valid in these notify functions is needed | |
193 to avoid re-setting the cursor index for lines that were wiped but | |
194 where the line/index of the view hasn't been updated yet (e.g. when | |
195 the current line is wiped, then view_delete_range is called to delete | |
196 a part, which may cause these notification functions to be called) */ | |
197 void | |
198 view_notify_insert_text(ledit_view *view, size_t line, size_t index, siz… | |
199 int sel_valid = view->sel_valid; | |
200 view_wipe_selection(view); | |
201 ledit_view_line *vl = view_get_line(view, line); | |
202 vl->text_dirty = 1; | |
203 if (line == view->cur_line && index < view->cur_index) { | |
204 view->cur_index += len; | |
205 ledit_view_line *vl = view_get_line(view, line); | |
206 if (vl->cursor_index_valid) | |
207 view_set_line_cursor_attrs(view, line, view->cur… | |
208 } | |
209 if (sel_valid) | |
210 view_set_selection(view, view->cur_line, view->cur_index… | |
211 } | |
212 | |
213 void | |
214 view_notify_delete_text(ledit_view *view, size_t line, size_t index, siz… | |
215 int sel_valid = view->sel_valid; | |
216 view_wipe_selection(view); | |
217 ledit_view_line *vl = view_get_line(view, line); | |
218 vl->text_dirty = 1; | |
219 if (line == view->cur_line) { | |
220 if (index + len <= view->cur_index) { | |
221 view->cur_index -= len; | |
222 } else if (index < view->cur_index && index + len > view… | |
223 view->cur_index = index; | |
224 } | |
225 /* just so it isn't stuck at end of line after deletion … | |
226 if (view->mode == NORMAL) { | |
227 view->cur_index = view_get_legal_normal_pos( | |
228 view, view->cur_line, view->cur_index | |
229 ); | |
230 } | |
231 ledit_view_line *vl = view_get_line(view, line); | |
232 if (vl->cursor_index_valid) | |
233 view_set_line_cursor_attrs(view, line, view->cur… | |
234 } | |
235 if (sel_valid) | |
236 view_set_selection(view, view->cur_line, view->cur_index… | |
237 } | |
238 | |
239 void | |
240 view_notify_append_line(ledit_view *view, size_t line) { | |
241 int sel_valid = view->sel_valid; | |
242 view_wipe_selection(view); | |
243 cache_invalidate_from_line( | |
244 view->cache, line + 1, view, | |
245 &invalidate_pixmap_line_helper, &invalidate_layout_line_help… | |
246 ); | |
247 resize_and_move_line_gap(view, add_sz(view->lines_num, 1), line … | |
248 if (line < view->cur_line) | |
249 view->cur_line++; | |
250 view->lines_num++; | |
251 view->lines_gap++; | |
252 ledit_view_line *vl = view_get_line(view, line + 1); | |
253 init_line(view, vl); | |
254 if (sel_valid) | |
255 view_set_selection(view, view->cur_line, view->cur_index… | |
256 } | |
257 | |
258 void | |
259 view_notify_delete_lines(ledit_view *view, size_t index1, size_t index2)… | |
260 /* FIXME: this is needed to avoid some bugs, but maybe check if … | |
261 int sel_valid = view->sel_valid; | |
262 view_wipe_selection(view); | |
263 if (index2 < view->cur_line) { | |
264 view->cur_line -= index2 - index1 + 1; | |
265 } else if (index1 <= view->cur_line) { | |
266 /* FIXME: set cur_index properly */ | |
267 if (index2 < view->lines_num - 1) { | |
268 view->cur_line = index1; | |
269 view->cur_index = 0; | |
270 } else if (index1 > 0) { | |
271 view->cur_line = index1 - 1; | |
272 view->cur_index = 0; | |
273 } else { | |
274 /* should never happen */ | |
275 view->cur_line = 0; | |
276 view->cur_index = 0; | |
277 } | |
278 ledit_view_line *vl = view_get_line(view, view->cur_line… | |
279 if (vl->cursor_index_valid) | |
280 view_set_line_cursor_attrs(view, view->cur_line,… | |
281 } | |
282 cache_invalidate_from_line( | |
283 view->cache, index1, view, | |
284 &invalidate_pixmap_line_helper, &invalidate_layout_line_help… | |
285 ); | |
286 move_line_gap(view, index1); | |
287 view->lines_num -= index2 - index1 + 1; | |
288 /* possibly decrease size of array - this needs to be after | |
289 actually deleting the lines so the length is already less */ | |
290 size_t min_size = ideal_array_size(view->lines_cap, view->lines_… | |
291 if (min_size != view->lines_cap) | |
292 resize_and_move_line_gap(view, view->lines_num, view->li… | |
293 /* force first entry to offset 0 if first line was deleted */ | |
294 if (index1 == 0) { | |
295 ledit_view_line *vl = view_get_line(view, 0); | |
296 vl->y_offset = 0; | |
297 } | |
298 if (sel_valid) | |
299 view_set_selection(view, view->cur_line, view->cur_index… | |
300 } | |
301 | |
302 void | |
303 view_destroy(ledit_view *view) { | |
304 cache_destroy(view->cache); | |
305 window_destroy(view->window); | |
306 free(view->lock_text); | |
307 free(view->lines); | |
308 free(view); | |
309 } | |
310 | |
311 void | |
312 view_cleanup(void) { | |
313 if (basic_attrs) | |
314 pango_attr_list_unref(basic_attrs); | |
315 basic_attrs = NULL; | |
316 } | |
317 | |
318 static PangoAttrList * | |
319 get_pango_attributes(size_t start_byte, size_t end_byte, XRenderColor fg… | |
320 PangoAttribute *attr0 = pango_attr_foreground_new(fg.red, fg.gre… | |
321 PangoAttribute *attr1 = pango_attr_background_new(bg.red, bg.gre… | |
322 attr0->start_index = start_byte; | |
323 attr0->end_index = end_byte; | |
324 attr1->start_index = start_byte; | |
325 attr1->end_index = end_byte; | |
326 PangoAttrList *list = pango_attr_list_new(); | |
327 pango_attr_list_insert(list, attr0); | |
328 pango_attr_list_insert(list, attr1); | |
329 #if PANGO_VERSION_CHECK(1, 44, 0) | |
330 PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE); | |
331 pango_attr_list_insert(list, attr2); | |
332 #endif | |
333 return list; | |
334 } | |
335 | |
336 /* this takes layout directly to possibly avoid infinite recursion */ | |
337 static void | |
338 set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *layout… | |
339 ledit_theme *theme = config_get_theme(); | |
340 ledit_line *ll = buffer_get_line(view->buffer, line); | |
341 ledit_view_line *vl = view_get_line(view, line); | |
342 PangoAttrList *list = NULL; | |
343 if (view->sel_valid) { | |
344 XRenderColor fg = theme->selection_fg.color; | |
345 XRenderColor bg = theme->selection_bg.color; | |
346 ledit_range sel = view->sel; | |
347 sort_range(&sel.line1, &sel.byte1, &sel.line2, &sel.byte… | |
348 if (sel.line1 < line && sel.line2 > line) { | |
349 list = get_pango_attributes(0, ll->len, fg, bg); | |
350 } else if (sel.line1 == line && sel.line2 == line) { | |
351 size_t start = sel.byte1, end = sel.byte2; | |
352 if (start > end) | |
353 swap_sz(&start, &end); | |
354 list = get_pango_attributes(start, end, fg, bg); | |
355 } else if (sel.line1 == line && sel.line2 > line) { | |
356 list = get_pango_attributes(sel.byte1, ll->len, … | |
357 } else if (sel.line1 < line && sel.line2 == line) { | |
358 list = get_pango_attributes(0, sel.byte2, fg, bg… | |
359 } | |
360 } else if (vl->cursor_index_valid) { | |
361 XRenderColor fg = theme->cursor_fg.color; | |
362 XRenderColor bg = theme->cursor_bg.color; | |
363 /* FIXME: does just adding one really do the right thing… | |
364 list = get_pango_attributes(vl->cursor_index, vl->cursor… | |
365 } | |
366 if (list != NULL) { | |
367 pango_layout_set_attributes(layout, list); | |
368 pango_attr_list_unref(list); | |
369 } else { | |
370 pango_layout_set_attributes(layout, basic_attrs); | |
371 } | |
372 vl->highlight_dirty = 0; | |
373 vl->dirty = 1; | |
374 } | |
375 | |
376 void | |
377 view_set_line_cursor_attrs(ledit_view *view, size_t line, size_t index) { | |
378 ledit_view_line *ll = view_get_line(view, line); | |
379 ll->cursor_index = index; | |
380 ll->cursor_index_valid = 1; | |
381 ll->highlight_dirty = 1; | |
382 ll->dirty = 1; | |
383 view->redraw = 1; | |
384 } | |
385 | |
386 void | |
387 view_wipe_line_cursor_attrs(ledit_view *view, size_t line) { | |
388 ledit_view_line *vl = view_get_line(view, line); | |
389 vl->cursor_index = 0; | |
390 vl->cursor_index_valid = 0; | |
391 vl->highlight_dirty = 1; | |
392 vl->dirty = 1; | |
393 view->redraw = 1; | |
394 } | |
395 | |
396 static int | |
397 line_visible_callback(void *data, size_t line) { | |
398 return view_line_visible((ledit_view*)data, line); | |
399 } | |
400 | |
401 /* FIXME: standardize variable names (line/line_index, etc.) */ | |
402 void | |
403 render_line(ledit_view *view, size_t line_index) { | |
404 ledit_theme *theme = config_get_theme(); | |
405 /* FIXME: check for <= 0 on size */ | |
406 ledit_view_line *ll = view_get_line(view, line_index); | |
407 ledit_assert(!ll->h_dirty); /* FIXME */ | |
408 PangoLayout *layout = get_pango_layout(view, line_index); | |
409 if (!ll->cache_pixmap_valid) { | |
410 cache_assign_pixmap_index( | |
411 view->cache, line_index, view, | |
412 &line_visible_callback, &set_pixmap_line_helper, | |
413 &invalidate_pixmap_line_helper | |
414 ); | |
415 } | |
416 cache_pixmap *pix = cache_get_pixmap(view->cache, ll->cache_pixm… | |
417 /* FIXME: fail on too large pixmap size (e.g. way too long line)… | |
418 /* FIXME: sensible default pixmap sizes here */ | |
419 /* FIXME: handle this in cache */ | |
420 if (pix->pixmap == None || pix->draw == NULL) { | |
421 pix->pixmap = XCreatePixmap( | |
422 view->buffer->common->dpy, view->window->drawable, | |
423 ll->w + 10, ll->h + 10, view->buffer->common->depth | |
424 ); | |
425 pix->w = ll->w + 10; | |
426 pix->h = ll->h + 10; | |
427 pix->draw = XftDrawCreate( | |
428 view->buffer->common->dpy, pix->pixmap, | |
429 view->buffer->common->vis, view->buffer->common->cm | |
430 ); | |
431 } else if (pix->w < ll->w || pix->h < ll->h) { | |
432 int new_w = ll->w > pix->w ? ll->w + 10 : pix->w + 10; | |
433 int new_h = ll->h > pix->h ? ll->h + 10 : pix->h + 10; | |
434 XFreePixmap(view->buffer->common->dpy, pix->pixmap); | |
435 pix->pixmap = XCreatePixmap( | |
436 view->buffer->common->dpy, view->window->drawable, | |
437 new_w, new_h, view->buffer->common->depth | |
438 ); | |
439 pix->w = new_w; | |
440 pix->h = new_h; | |
441 XftDrawChange(pix->draw, pix->pixmap); | |
442 } | |
443 XftDrawRect(pix->draw, &theme->text_bg, 0, 0, ll->w, ll->h); | |
444 pango_xft_render_layout(pix->draw, &theme->text_fg, layout, 0, 0… | |
445 ll->dirty = 0; | |
446 } | |
447 | |
448 static void | |
449 init_line(ledit_view *view, ledit_view_line *line) { | |
450 int text_w, text_h; | |
451 window_get_textview_size(view->window, &text_w, &text_h); | |
452 line->view = view; | |
453 line->w = text_w; | |
454 line->h = 0; | |
455 line->y_offset = 0; | |
456 line->cache_pixmap_index = 0; | |
457 line->cache_layout_index = 0; | |
458 line->softlines = 0; | |
459 line->cursor_index = 0; | |
460 line->cursor_index_valid = 0; | |
461 line->cache_pixmap_valid = 0; | |
462 line->cache_layout_valid = 0; | |
463 line->dirty = 1; | |
464 line->text_dirty = 1; | |
465 line->highlight_dirty = 1; | |
466 line->h_dirty = 1; | |
467 } | |
468 | |
469 static void | |
470 set_pixmap_line_helper(void *data, size_t line, size_t index) { | |
471 ledit_view_line *vl = view_get_line((ledit_view *)data, line); | |
472 vl->cache_pixmap_index = index; | |
473 vl->cache_pixmap_valid = 1; | |
474 } | |
475 | |
476 static void | |
477 invalidate_pixmap_line_helper(void *data, size_t line) { | |
478 ledit_view_line *vl = view_get_line((ledit_view *)data, line); | |
479 vl->cache_pixmap_valid = 0; | |
480 } | |
481 | |
482 static void | |
483 set_layout_line_helper(void *data, size_t line, size_t index) { | |
484 ledit_view_line *vl = view_get_line((ledit_view *)data, line); | |
485 vl->cache_layout_index = index; | |
486 vl->cache_layout_valid = 1; | |
487 } | |
488 | |
489 static void | |
490 invalidate_layout_line_helper(void *data, size_t line) { | |
491 ledit_view_line *vl = view_get_line((ledit_view *)data, line); | |
492 vl->cache_layout_valid = 0; | |
493 } | |
494 | |
495 void | |
496 view_recalc_line(ledit_view *view, size_t line) { | |
497 ledit_theme *theme = config_get_theme(); | |
498 ledit_view_line *l = view_get_line(view, line); | |
499 if (l->text_dirty) | |
500 set_pango_text_and_highlight(view, line); | |
501 | |
502 int text_w, text_h; | |
503 window_get_textview_size(view->window, &text_w, &text_h); | |
504 /* if height changed, set height of current line | |
505 * and adjust offsets of all lines following it */ | |
506 if (l->h_dirty) { | |
507 l->h_dirty = 0; | |
508 /* FIXME: maybe also check overflow for offset? */ | |
509 long off = l->y_offset + l->h + theme->extra_line_spacin… | |
510 for (size_t i = line + 1; i < view->lines_num; i++) { | |
511 l = view_get_line(view, i); | |
512 l->y_offset = off; | |
513 off += l->h + theme->extra_line_spacing; | |
514 } | |
515 view->total_height = off - theme->extra_line_spacing; | |
516 if (l->y_offset < view->display_offset + text_h) | |
517 view->redraw = 1; | |
518 } | |
519 l = view_get_line(view, line); | |
520 if (l->y_offset < view->display_offset + text_h && | |
521 l->y_offset + l->h >= view->display_offset) { | |
522 view->redraw = 1; | |
523 } | |
524 window_set_scroll_max(view->window, view->total_height); | |
525 view_scroll(view, view->display_offset); | |
526 } | |
527 | |
528 void | |
529 view_recalc_from_line(ledit_view *view, size_t line) { | |
530 ledit_theme *theme = config_get_theme(); | |
531 ledit_view_line *l = view_get_line(view, line); | |
532 /* force first line to offset 0 */ | |
533 if (line == 0) | |
534 l->y_offset = 0; | |
535 int text_w, text_h; | |
536 window_get_textview_size(view->window, &text_w, &text_h); | |
537 long off = l->y_offset; | |
538 if (off < view->display_offset + text_h) | |
539 view->redraw = 1; | |
540 for (size_t i = line; i < view->lines_num; i++) { | |
541 l = view_get_line(view, i); | |
542 if (l->text_dirty) | |
543 set_pango_text_and_highlight(view, i); | |
544 l->h_dirty = 0; | |
545 l->y_offset = off; | |
546 off += l->h + theme->extra_line_spacing; | |
547 } | |
548 view->total_height = off - theme->extra_line_spacing; | |
549 window_set_scroll_max(view->window, view->total_height); | |
550 view_scroll(view, view->display_offset); | |
551 } | |
552 | |
553 void | |
554 view_recalc_all_lines(ledit_view *view) { | |
555 view_recalc_from_line(view, 0); | |
556 } | |
557 | |
558 static int | |
559 view_line_visible(ledit_view *view, size_t index) { | |
560 int text_w, text_h; | |
561 window_get_textview_size(view->window, &text_w, &text_h); | |
562 ledit_view_line *l = view_get_line(view, index); | |
563 return l->y_offset < view->display_offset + text_h && | |
564 l->y_offset + l->h > view->display_offset; | |
565 } | |
566 | |
567 /* FIXME: these functions are only here because they need the PangoLayou… | |
568 determine grapheme boundaries. Maybe use a separate library for that?… | |
569 void | |
570 view_next_cursor_pos( | |
571 ledit_view *view, | |
572 size_t line, size_t byte, | |
573 int num, int multiline, | |
574 size_t *line_ret, size_t *byte_ret) { | |
575 int nattrs; | |
576 ledit_line *ll = buffer_get_line(view->buffer, line); | |
577 size_t c = line_byte_to_char(ll, byte); | |
578 size_t cur_byte = byte; | |
579 PangoLayout *layout = get_pango_layout(view, line); | |
580 const PangoLogAttr *attrs = | |
581 pango_layout_get_log_attrs_readonly(layout, &nattrs); | |
582 for (int i = 0; i < num; i++) { | |
583 if (cur_byte >= ll->len) { | |
584 if (multiline && line < view->lines_num - 1) { | |
585 line++; | |
586 ll = buffer_get_line(view->buffer, line); | |
587 layout = get_pango_layout(view, line); | |
588 attrs = pango_layout_get_log_attrs_reado… | |
589 c = 0; | |
590 cur_byte = 0; | |
591 i++; | |
592 continue; | |
593 } else { | |
594 break; | |
595 } | |
596 } | |
597 cur_byte = line_next_utf8(ll, cur_byte); | |
598 for (c++; c < (size_t)nattrs; c++) { | |
599 if (attrs[c].is_cursor_position) | |
600 break; | |
601 cur_byte = line_next_utf8(ll, cur_byte); | |
602 } | |
603 } | |
604 if (line_ret) | |
605 *line_ret = line; | |
606 if (byte_ret) | |
607 *byte_ret = cur_byte <= ll->len ? cur_byte : ll->len; | |
608 } | |
609 | |
610 void | |
611 view_prev_cursor_pos( | |
612 ledit_view *view, | |
613 size_t line, size_t byte, | |
614 int num, int multiline, | |
615 size_t *line_ret, size_t *byte_ret) { | |
616 int nattrs; | |
617 ledit_line *ll = buffer_get_line(view->buffer, line); | |
618 size_t c = line_byte_to_char(ll, byte); | |
619 size_t cur_byte = byte; | |
620 PangoLayout *layout = get_pango_layout(view, line); | |
621 const PangoLogAttr *attrs = | |
622 pango_layout_get_log_attrs_readonly(layout, &nattrs); | |
623 for (int i = 0; i < num; i++) { | |
624 if (cur_byte == 0) { | |
625 if (multiline && line > 0) { | |
626 line--; | |
627 ll = buffer_get_line(view->buffer, line); | |
628 layout = get_pango_layout(view, line); | |
629 attrs = pango_layout_get_log_attrs_reado… | |
630 c = (size_t)nattrs; | |
631 cur_byte = ll->len; | |
632 i++; | |
633 continue; | |
634 } else { | |
635 break; | |
636 } | |
637 } | |
638 cur_byte = line_prev_utf8(ll, cur_byte); | |
639 for (; c-- > 0;) { | |
640 if (attrs[c].is_cursor_position) | |
641 break; | |
642 cur_byte = line_prev_utf8(ll, cur_byte); | |
643 } | |
644 } | |
645 if (line_ret) | |
646 *line_ret = line; | |
647 if (byte_ret) | |
648 *byte_ret = cur_byte; | |
649 } | |
650 | |
651 static int | |
652 line_next_word( | |
653 ledit_view *view, | |
654 size_t line, size_t byte, size_t char_index, int wrapped_line, | |
655 size_t *char_ret, size_t *byte_ret, size_t *real_byte_ret) { | |
656 int nattrs; | |
657 ledit_line *ll = buffer_get_line(view->buffer, line); | |
658 int cur_byte = wrapped_line ? byte : line_next_utf8(ll, byte); | |
659 PangoLayout *layout = get_pango_layout(view, line); | |
660 const PangoLogAttr *attrs = | |
661 pango_layout_get_log_attrs_readonly(layout, &nattrs); | |
662 for (size_t i = wrapped_line ? char_index : char_index + 1; i < … | |
663 if (attrs[i].is_word_start) { | |
664 *char_ret = i; | |
665 *real_byte_ret = cur_byte; | |
666 *byte_ret = cur_byte; | |
667 return 0; | |
668 } | |
669 cur_byte = line_next_utf8(ll, cur_byte); | |
670 } | |
671 return -1; | |
672 } | |
673 | |
674 static int | |
675 line_prev_word( | |
676 ledit_view *view, | |
677 size_t line, size_t byte, size_t char_index, | |
678 size_t *char_ret, size_t *byte_ret) { | |
679 int nattrs; | |
680 ledit_line *ll = buffer_get_line(view->buffer, line); | |
681 size_t cur_byte = line_prev_utf8(ll, byte); | |
682 PangoLayout *layout = get_pango_layout(view, line); | |
683 const PangoLogAttr *attrs = | |
684 pango_layout_get_log_attrs_readonly(layout, &nattrs); | |
685 if (char_index > (size_t)nattrs - 1) | |
686 char_index = (size_t)nattrs - 1; | |
687 /* this is a bit weird because size_t can't be negative */ | |
688 for (size_t i = char_index; i > 0; i--) { | |
689 if (attrs[i-1].is_word_start) { | |
690 *char_ret = i-1; | |
691 *byte_ret = cur_byte; | |
692 return 0; | |
693 } | |
694 cur_byte = line_prev_utf8(ll, cur_byte); | |
695 } | |
696 return -1; | |
697 } | |
698 | |
699 static int | |
700 line_prev_bigword( | |
701 ledit_view *view, | |
702 size_t line, size_t byte, size_t char_index, | |
703 size_t *char_ret, size_t *byte_ret) { | |
704 int nattrs; | |
705 ledit_line *ll = buffer_get_line(view->buffer, line); | |
706 size_t cur_byte = line_prev_utf8(ll, byte); | |
707 PangoLayout *layout = get_pango_layout(view, line); | |
708 const PangoLogAttr *attrs = | |
709 pango_layout_get_log_attrs_readonly(layout, &nattrs); | |
710 size_t next_cursorb = byte; | |
711 size_t next_cursorc = char_index; | |
712 int found_word = 0; | |
713 if (char_index > (size_t)nattrs - 1) | |
714 char_index = (size_t)nattrs - 1; | |
715 /* FIXME: use for (size_t i = ...; i-- > 0;) everywhere */ | |
716 /* this is a bit weird because size_t can't be negative */ | |
717 for (size_t i = char_index; i > 0; i--) { | |
718 if (!found_word && !attrs[i-1].is_white) { | |
719 found_word = 1; | |
720 } else if (found_word && attrs[i-1].is_white) { | |
721 *char_ret = next_cursorc; | |
722 *byte_ret = next_cursorb; | |
723 return 0; | |
724 } | |
725 if (found_word && i-1 == 0) { | |
726 *char_ret = 0; | |
727 *byte_ret = 0; | |
728 return 0; | |
729 } | |
730 if (attrs[i-1].is_cursor_position) { | |
731 next_cursorc = i-1; | |
732 next_cursorb = cur_byte; | |
733 } | |
734 cur_byte = line_prev_utf8(ll, cur_byte); | |
735 } | |
736 return -1; | |
737 } | |
738 | |
739 static int | |
740 line_next_bigword_end( | |
741 ledit_view *view, | |
742 size_t line, size_t byte, size_t char_index, int wrapped_line, | |
743 size_t *char_ret, size_t *byte_ret, size_t *real_byte_ret) { | |
744 int nattrs; | |
745 ledit_line *ll = buffer_get_line(view->buffer, line); | |
746 PangoLayout *layout = get_pango_layout(view, line); | |
747 const PangoLogAttr *attrs = | |
748 pango_layout_get_log_attrs_readonly(layout, &nattrs); | |
749 size_t last_cursorb = 0, last_cursorc = 0; | |
750 int last_cursor_valid; | |
751 if (wrapped_line) { | |
752 last_cursorb = byte; | |
753 last_cursorc = char_index; | |
754 last_cursor_valid = 1; | |
755 } else { | |
756 last_cursor_valid = 0; | |
757 } | |
758 int found_word = 0; | |
759 size_t cur_byte = byte; | |
760 for (size_t i = char_index; i < (size_t)nattrs; i++) { | |
761 if (last_cursor_valid && !found_word && !attrs[i].is_whi… | |
762 found_word = 1; | |
763 } else if (found_word && attrs[i].is_white) { | |
764 *char_ret = last_cursorc; | |
765 *real_byte_ret = cur_byte; | |
766 *byte_ret = last_cursorb; | |
767 return 0; | |
768 } | |
769 if (attrs[i].is_cursor_position) { | |
770 last_cursorc = i; | |
771 last_cursorb = cur_byte; | |
772 last_cursor_valid = 1; | |
773 } | |
774 cur_byte = line_next_utf8(ll, cur_byte); | |
775 } | |
776 return -1; | |
777 } | |
778 | |
779 static int | |
780 line_next_word_end( | |
781 ledit_view *view, | |
782 size_t line, size_t byte, size_t char_index, int wrapped_line, | |
783 size_t *char_ret, size_t *byte_ret, size_t *real_byte_ret) { | |
784 int nattrs; | |
785 ledit_line *ll = buffer_get_line(view->buffer, line); | |
786 size_t cur_byte = line_next_utf8(ll, byte); | |
787 PangoLayout *layout = get_pango_layout(view, line); | |
788 const PangoLogAttr *attrs = | |
789 pango_layout_get_log_attrs_readonly(layout, &nattrs); | |
790 size_t last_cursorb = 0, last_cursorc = 0; | |
791 int last_cursor_valid; | |
792 if (wrapped_line) { | |
793 last_cursorb = byte; | |
794 last_cursorc = char_index; | |
795 last_cursor_valid = 1; | |
796 } else { | |
797 last_cursor_valid = 0; | |
798 } | |
799 for (size_t i = char_index + 1; i < (size_t)nattrs; i++) { | |
800 if (last_cursor_valid && attrs[i].is_word_end) { | |
801 *char_ret = last_cursorc; | |
802 *real_byte_ret = cur_byte; | |
803 *byte_ret = last_cursorb; | |
804 return 0; | |
805 } | |
806 if (attrs[i].is_cursor_position) { | |
807 last_cursorc = i; | |
808 last_cursorb = cur_byte; | |
809 last_cursor_valid = 1; | |
810 } | |
811 cur_byte = line_next_utf8(ll, cur_byte); | |
812 } | |
813 return -1; | |
814 } | |
815 | |
816 static int | |
817 line_next_bigword( | |
818 ledit_view *view, | |
819 size_t line, size_t byte, size_t char_index, int wrapped_line, | |
820 size_t *char_ret, size_t *byte_ret, size_t *real_byte_ret) { | |
821 int nattrs; | |
822 ledit_line *ll = buffer_get_line(view->buffer, line); | |
823 size_t cur_byte = byte; | |
824 PangoLayout *layout = get_pango_layout(view, line); | |
825 const PangoLogAttr *attrs = | |
826 pango_layout_get_log_attrs_readonly(layout, &nattrs); | |
827 int found_ws = wrapped_line; | |
828 for (size_t i = char_index; i < (size_t)nattrs; i++) { | |
829 if (!found_ws && attrs[i].is_white) { | |
830 found_ws = 1; | |
831 } else if (found_ws && !attrs[i].is_white) { | |
832 *char_ret = i; | |
833 *real_byte_ret = cur_byte; | |
834 *byte_ret = cur_byte; | |
835 return 0; | |
836 } | |
837 cur_byte = line_next_utf8(ll, cur_byte); | |
838 } | |
839 return -1; | |
840 } | |
841 | |
842 size_t | |
843 view_line_next_non_whitespace(ledit_view *view, size_t line, size_t byte… | |
844 int nattrs; | |
845 ledit_line *ll = buffer_get_line(view->buffer, line); | |
846 size_t c = line_byte_to_char(ll, byte); | |
847 size_t cur_byte = byte; | |
848 PangoLayout *layout = get_pango_layout(view, line); | |
849 const PangoLogAttr *attrs = | |
850 pango_layout_get_log_attrs_readonly(layout, &nattrs); | |
851 for (; c < (size_t)nattrs; c++) { | |
852 if (!attrs[c].is_white) | |
853 return cur_byte; | |
854 cur_byte = line_next_utf8(ll, cur_byte); | |
855 } | |
856 return ll->len; | |
857 } | |
858 | |
859 /* FIXME: document that word and bigword are a bit weird because word us… | |
860 | |
861 #define GEN_NEXT_WORD(name, func) … | |
862 void … | |
863 view_next_##name( … | |
864 ledit_view *view, … | |
865 size_t line, size_t byte, int num_repeat, … | |
866 size_t *line_ret, size_t *byte_ret, size_t *real_byte_ret) { … | |
867 size_t cur_line = line; … | |
868 size_t cur_byte = byte; … | |
869 ledit_line *ll = buffer_get_line(view->buffer, line); … | |
870 size_t cur_char = line_byte_to_char(ll, byte); … | |
871 size_t real_byte = 0; … | |
872 int last_ret = -1; … | |
873 int wrapped_line; … | |
874 for (int i = 0; i < num_repeat; i++) { … | |
875 wrapped_line = 0; … | |
876 while ((last_ret = func(view, cur_line, cur_byte, cur_ch… | |
877 wrapped_line, &cur_char, &cur_byte, &real_byte))… | |
878 cur_line < view->lines_num - 1) { … | |
879 cur_line++; … | |
880 cur_byte = 0; … | |
881 cur_char = 0; … | |
882 wrapped_line = 1; … | |
883 } … | |
884 if (last_ret == -1 && cur_line == view->lines_num - 1) … | |
885 break; … | |
886 } … | |
887 if (last_ret == -1) { … | |
888 *line_ret = view->lines_num - 1; … | |
889 ledit_line *ll = buffer_get_line(view->buffer, view->lin… | |
890 *byte_ret = view_get_legal_normal_pos(view, view->lines_… | |
891 *real_byte_ret = ll->len; … | |
892 } else { … | |
893 *line_ret = cur_line; … | |
894 *byte_ret = cur_byte; … | |
895 *real_byte_ret = real_byte; … | |
896 } … | |
897 } | |
898 | |
899 #define GEN_PREV_WORD(name, func) … | |
900 void … | |
901 view_prev_##name( … | |
902 ledit_view *view, … | |
903 size_t line, size_t byte, int num_repeat, … | |
904 size_t *line_ret, size_t *byte_ret, size_t *real_byte_ret) { … | |
905 size_t cur_line = line; … | |
906 size_t cur_byte = byte; … | |
907 ledit_line *ll = buffer_get_line(view->buffer, line); … | |
908 size_t cur_char = line_byte_to_char(ll, byte); … | |
909 int last_ret = -1; … | |
910 for (int i = 0; i < num_repeat; i++) { … | |
911 while ((last_ret = func(view, cur_line, cur_byte, cur_ch… | |
912 &cur_char, &cur_byte)) == -1 && cur_line > 0) { … | |
913 cur_line--; … | |
914 ll = buffer_get_line(view->buffer, cur_line); … | |
915 cur_byte = ll->len; … | |
916 cur_char = ll->len; … | |
917 } … | |
918 if (last_ret == -1 && cur_line == 0) … | |
919 break; … | |
920 } … | |
921 if (last_ret == -1) { … | |
922 *line_ret = 0; … | |
923 *byte_ret = 0; … | |
924 *real_byte_ret = 0; … | |
925 } else { … | |
926 *line_ret = cur_line; … | |
927 *byte_ret = cur_byte; … | |
928 *real_byte_ret = cur_byte; … | |
929 } … | |
930 } | |
931 | |
932 GEN_NEXT_WORD(word, line_next_word) | |
933 GEN_NEXT_WORD(word_end, line_next_word_end) | |
934 GEN_NEXT_WORD(bigword, line_next_bigword) | |
935 GEN_NEXT_WORD(bigword_end, line_next_bigword_end) | |
936 GEN_PREV_WORD(word, line_prev_word) | |
937 GEN_PREV_WORD(bigword, line_prev_bigword) | |
938 | |
939 void | |
940 view_get_pos_softline_bounds( | |
941 ledit_view *view, size_t line, size_t pos, | |
942 size_t *start_byte_ret, size_t *end_byte_ret) { | |
943 ledit_assert(line < view->lines_num); | |
944 ledit_line *ll = buffer_get_line(view->buffer, line); | |
945 ledit_assert(pos <= ll->len); | |
946 PangoLayout *layout = get_pango_layout(view, line); | |
947 int x, sli; | |
948 if (pos > INT_MAX) | |
949 err_overflow(); | |
950 pango_layout_index_to_line_x(layout, (int)pos, 0, &sli, &x); | |
951 PangoLayoutLine *pl = pango_layout_get_line_readonly(layout, sli… | |
952 *start_byte_ret = (size_t)pl->start_index; | |
953 *end_byte_ret = (size_t)(pl->start_index + pl->length); | |
954 } | |
955 | |
956 void | |
957 view_get_softline_bounds( | |
958 ledit_view *view, size_t line, int softline, | |
959 size_t *start_byte_ret, size_t *end_byte_ret) { | |
960 ledit_assert(line < view->lines_num); | |
961 ledit_view_line *vl = view_get_line(view, line); | |
962 PangoLayout *layout = get_pango_layout(view, line); | |
963 ledit_assert(softline < vl->softlines); | |
964 PangoLayoutLine *pl = pango_layout_get_line_readonly(layout, sof… | |
965 *start_byte_ret = (size_t)pl->start_index; | |
966 *end_byte_ret = (size_t)(pl->start_index + pl->length); | |
967 } | |
968 | |
969 int | |
970 view_get_softline_count(ledit_view *view, size_t line) { | |
971 ledit_assert(line < view->lines_num); | |
972 ledit_view_line *vl = view_get_line(view, line); | |
973 if (vl->text_dirty) | |
974 set_pango_text_and_highlight(view, line); | |
975 return vl->softlines; | |
976 } | |
977 | |
978 int | |
979 view_pos_to_softline(ledit_view *view, size_t line, size_t pos) { | |
980 ledit_assert(line < view->lines_num); | |
981 ledit_line *ll = buffer_get_line(view->buffer, line); | |
982 ledit_assert(pos <= ll->len); | |
983 PangoLayout *layout = get_pango_layout(view, line); | |
984 int x, sli; | |
985 if (pos > INT_MAX) | |
986 err_overflow(); | |
987 pango_layout_index_to_line_x(layout, (int)pos, 0, &sli, &x); | |
988 return sli; | |
989 } | |
990 | |
991 void | |
992 view_get_cursor_pixel_pos(ledit_view *view, size_t line, size_t pos, int… | |
993 ledit_assert(line < view->lines_num); | |
994 ledit_line *ll = buffer_get_line(view->buffer, line); | |
995 ledit_assert(pos <= ll->len); | |
996 PangoLayout *layout = get_pango_layout(view, line); | |
997 PangoRectangle strong, weak; | |
998 if (pos > INT_MAX) | |
999 err_overflow(); | |
1000 pango_layout_get_cursor_pos(layout, (int)pos, &strong, &weak); | |
1001 *x_ret = strong.x / PANGO_SCALE; | |
1002 *y_ret = strong.y / PANGO_SCALE; | |
1003 *h_ret = strong.height / PANGO_SCALE; | |
1004 } | |
1005 | |
1006 /* prev_index_ret is used instead of just calling get_legal_normal_pos | |
1007 because weird things happen otherwise | |
1008 -> in certain cases, this is still weird because prev_index_ret somet… | |
1009 is not at the end of the line, but this is the best I could come up | |
1010 with for now */ | |
1011 size_t | |
1012 view_move_cursor_visually(ledit_view *view, size_t line, size_t pos, int… | |
1013 if (pos > INT_MAX) | |
1014 err_overflow(); | |
1015 /* FIXME: trailing */ | |
1016 int trailing = 0; | |
1017 ledit_line *cur_line = buffer_get_line(view->buffer, line); | |
1018 PangoLayout *layout = get_pango_layout(view, line); | |
1019 int tmp_index; | |
1020 int new_index = (int)pos, last_index = (int)pos; | |
1021 int dir = 1; | |
1022 int num = movement; | |
1023 if (movement < 0) { | |
1024 dir = -1; | |
1025 num = -movement; | |
1026 } | |
1027 /* FIXME: This is stupid. Anything outside the range of int won'… | |
1028 anyways because of pango (and because everything else would b… | |
1029 anyways with such long lines), so it's stupid to do all this … | |
1030 casting. */ | |
1031 if (cur_line->len > INT_MAX) | |
1032 err_overflow(); | |
1033 while (num > 0) { | |
1034 tmp_index = new_index; | |
1035 pango_layout_move_cursor_visually( | |
1036 layout, TRUE, | |
1037 new_index, trailing, dir, | |
1038 &new_index, &trailing | |
1039 ); | |
1040 if (new_index < 0) | |
1041 new_index = 0; | |
1042 else if (new_index > (int)cur_line->len) | |
1043 new_index = (int)cur_line->len; | |
1044 num--; | |
1045 if (tmp_index != new_index) | |
1046 last_index = tmp_index; | |
1047 } | |
1048 /* FIXME: Allow cursor to be at end of soft line */ | |
1049 /* we don't currently support a difference between the cursor be… | |
1050 the end of a soft line and the beginning of the next line */ | |
1051 /* FIXME: spaces at end of softlines are weird in normal mode */ | |
1052 while (trailing > 0) { | |
1053 trailing--; | |
1054 new_index = line_next_utf8(cur_line, new_index); | |
1055 } | |
1056 if (new_index < 0) | |
1057 new_index = 0; | |
1058 if (prev_index_ret) | |
1059 *prev_index_ret = (size_t)last_index; | |
1060 return (size_t)new_index; | |
1061 } | |
1062 | |
1063 /* FIXME: implement */ | |
1064 /* | |
1065 int | |
1066 ledit_line_nearest_cursor_pos(ledit_line *line, int byte) { | |
1067 } | |
1068 | |
1069 void | |
1070 ledit_line_word_boundaries(ledit_line *line, int byte, int *start_ret, i… | |
1071 } | |
1072 */ | |
1073 | |
1074 static void | |
1075 set_pango_text_and_highlight(ledit_view *view, size_t line) { | |
1076 cache_layout *cl; | |
1077 ledit_theme *theme = config_get_theme(); | |
1078 ledit_line *ll = buffer_get_line(view->buffer, line); | |
1079 ledit_view_line *vl = view_get_line(view, line); | |
1080 char old_valid = vl->cache_layout_valid; | |
1081 if (!vl->cache_layout_valid) { | |
1082 cache_assign_layout_index( | |
1083 view->cache, line, | |
1084 view, &set_layout_line_helper, &invalidate_layout_li… | |
1085 ); | |
1086 cl = cache_get_layout(view->cache, vl->cache_layout_inde… | |
1087 } else { | |
1088 cl = cache_get_layout(view->cache, vl->cache_layout_inde… | |
1089 } | |
1090 if (cl->layout == NULL) { | |
1091 cl->layout = pango_layout_new(view->window->context); | |
1092 pango_layout_set_font_description(cl->layout, view->wind… | |
1093 pango_layout_set_wrap(cl->layout, PANGO_WRAP_WORD_CHAR); | |
1094 pango_layout_set_spacing(cl->layout, theme->extra_line_s… | |
1095 } | |
1096 if (vl->text_dirty || !old_valid) { | |
1097 buffer_normalize_line(ll); | |
1098 if (ll->len > INT_MAX) | |
1099 err_overflow(); | |
1100 pango_layout_set_text(cl->layout, ll->text, (int)ll->len… | |
1101 set_line_layout_attrs(view, line, cl->layout); | |
1102 pango_layout_set_width(cl->layout, vl->w * PANGO_SCALE); | |
1103 vl->softlines = pango_layout_get_line_count(cl->layout); | |
1104 int w, h; | |
1105 pango_layout_get_pixel_size(cl->layout, &w, &h); | |
1106 if (h != vl->h) { | |
1107 vl->h = h; | |
1108 vl->h_dirty = 1; | |
1109 } | |
1110 vl->text_dirty = 0; | |
1111 vl->dirty = 1; | |
1112 } else if (vl->highlight_dirty) { | |
1113 set_line_layout_attrs(view, line, cl->layout); | |
1114 } | |
1115 vl->highlight_dirty = 0; | |
1116 } | |
1117 | |
1118 static PangoLayout * | |
1119 get_pango_layout(ledit_view *view, size_t line) { | |
1120 set_pango_text_and_highlight(view, line); | |
1121 ledit_view_line *vl = view_get_line(view, line); | |
1122 cache_layout *cl = cache_get_layout( | |
1123 view->cache, vl->cache_layout_index | |
1124 ); | |
1125 return cl->layout; | |
1126 } | |
1127 | |
1128 void | |
1129 view_pos_to_x_softline(ledit_view *view, size_t line, size_t pos, int *x… | |
1130 ledit_view_line *vl = view_get_line(view, line); | |
1131 PangoLayout *layout = get_pango_layout(view, line); | |
1132 if (pos > INT_MAX) | |
1133 err_overflow(); | |
1134 pango_layout_index_to_line_x(layout, (int)pos, 0, softline_ret, … | |
1135 PangoLayoutLine *pango_line = pango_layout_get_line_readonly(lay… | |
1136 /* add left margin to x position if line is aligned right */ | |
1137 if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) { | |
1138 PangoRectangle rect; | |
1139 pango_layout_line_get_extents(pango_line, NULL, &rect); | |
1140 *x_ret += (vl->w * PANGO_SCALE - rect.width); | |
1141 } | |
1142 /* if in normal mode, change position to the middle of the | |
1143 current rectangle so that moving around won't jump weirdly */ | |
1144 /* FIXME: also in visual? */ | |
1145 /* FIXME: this is too much magic for my taste */ | |
1146 if (view->mode == NORMAL) { | |
1147 PangoRectangle rect; | |
1148 pango_layout_index_to_pos(layout, (int)pos, &rect); | |
1149 *x_ret += rect.width / 2; | |
1150 } | |
1151 } | |
1152 | |
1153 size_t | |
1154 view_x_softline_to_pos(ledit_view *view, size_t line, int x, int softlin… | |
1155 int trailing = 0; | |
1156 int x_relative = x; | |
1157 ledit_view_line *vl = view_get_line(view, line); | |
1158 PangoLayout *layout = get_pango_layout(view, line); | |
1159 PangoLayoutLine *pango_line = | |
1160 pango_layout_get_line_readonly(layout, softline); | |
1161 /* x is absolute, so the margin at the left needs to be subtract… | |
1162 if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) { | |
1163 PangoRectangle rect; | |
1164 pango_layout_line_get_extents(pango_line, NULL, &rect); | |
1165 x_relative -= (vl->w * PANGO_SCALE - rect.width); | |
1166 } | |
1167 int tmp_pos; | |
1168 pango_layout_line_x_to_index( | |
1169 pango_line, x_relative, &tmp_pos, &trailing | |
1170 ); | |
1171 size_t pos = (size_t)tmp_pos; | |
1172 /* if in insert or visual mode, snap to the nearest border betwe… | |
1173 /* FIXME: add parameter for this instead of checking mode */ | |
1174 if (view->mode == INSERT || view->mode == VISUAL) { | |
1175 ledit_line *ll = buffer_get_line(view->buffer, line); | |
1176 while (trailing > 0) { | |
1177 trailing--; | |
1178 pos = line_next_utf8(ll, pos); | |
1179 } | |
1180 } | |
1181 return pos; | |
1182 } | |
1183 | |
1184 size_t | |
1185 view_get_legal_normal_pos(ledit_view *view, size_t line, size_t pos) { | |
1186 /* move back one grapheme if at end of line */ | |
1187 size_t ret = pos; | |
1188 ledit_line *ll = buffer_get_line(view->buffer, line); | |
1189 if (pos == ll->len && pos > 0) { | |
1190 int nattrs; | |
1191 PangoLayout *layout = get_pango_layout(view, line); | |
1192 const PangoLogAttr *attrs = | |
1193 pango_layout_get_log_attrs_readonly(layout, &nattrs); | |
1194 if (nattrs < 2) | |
1195 return 0; | |
1196 size_t cur = nattrs - 2; | |
1197 ret = line_prev_utf8(ll, ret); | |
1198 while (ret > 0 && cur > 0 && !attrs[cur].is_cursor_posit… | |
1199 cur--; | |
1200 ret = line_prev_utf8(ll, ret); | |
1201 } | |
1202 } | |
1203 return ret; | |
1204 } | |
1205 | |
1206 void | |
1207 view_delete_range( | |
1208 ledit_view *view, | |
1209 enum delete_mode delmode, int start_undo_group, | |
1210 size_t line_index1, size_t byte_index1, | |
1211 size_t line_index2, size_t byte_index2, | |
1212 size_t *new_line_ret, size_t *new_byte_ret, | |
1213 txtbuf *text_ret) { | |
1214 view_delete_range_base( | |
1215 view, | |
1216 delmode, start_undo_group, | |
1217 line_index1, byte_index1, | |
1218 line_index2, byte_index2, | |
1219 new_line_ret, new_byte_ret, | |
1220 text_ret | |
1221 ); | |
1222 /* need to start recalculating one line before in case first | |
1223 line was deleted and offset is now wrong */ | |
1224 size_t min = line_index1 < line_index2 ? line_index1 : line_inde… | |
1225 buffer_recalc_all_views_from_line( | |
1226 view->buffer, min > 0 ? min - 1 : min | |
1227 ); | |
1228 } | |
1229 | |
1230 /* Note: line_index* and byte_index* don't need to be sorted */ | |
1231 /* line_index1, byte_index1 are used as the cursor position in order | |
1232 to determine the new cursor position */ | |
1233 /* FIXME: use at least somewhat sensible variable names */ | |
1234 void | |
1235 view_delete_range_base( | |
1236 ledit_view *view, | |
1237 enum delete_mode delmode, int start_undo_group, | |
1238 size_t line_index1, size_t byte_index1, | |
1239 size_t line_index2, size_t byte_index2, | |
1240 size_t *new_line_ret, size_t *new_byte_ret, | |
1241 txtbuf *text_ret) { | |
1242 /* FIXME: Oh boy, this is nasty */ | |
1243 /* range line x, range byte x */ | |
1244 size_t rgl1 = 0, rgb1 = 0, rgl2 = 0, rgb2 = 0; | |
1245 /* line_index1 and byte_index1 are used as cursor start position | |
1246 -> FIXME: why not just use view->cur_line, view->cur_index he… | |
1247 size_t cur_line = line_index1; | |
1248 size_t cur_byte = byte_index1; | |
1249 sort_range(&line_index1, &byte_index1, &line_index2, &byte_index… | |
1250 size_t new_line = 0, new_byte = 0; | |
1251 ledit_assert(line_index1 < view->lines_num); | |
1252 ledit_assert(line_index2 < view->lines_num); | |
1253 ledit_range cur_range = {view->cur_line, view->cur_index, 0, 0}; | |
1254 /* FIXME: could this be simplified by just calculating the range… | |
1255 the non-line-based version? */ | |
1256 if (delmode == DELETE_HARDLINE) { | |
1257 int x, sl_useless; | |
1258 size_t l1 = line_index1, l2 = line_index2; | |
1259 ledit_line *ll; | |
1260 view_pos_to_x_softline(view, cur_line, cur_byte, &x, &sl… | |
1261 if (l1 > 0 && l2 < view->lines_num - 1) { | |
1262 rgl1 = l1; | |
1263 rgb1 = 0; | |
1264 rgl2 = l2 + 1; | |
1265 rgb2 = 0; | |
1266 } else if (l1 > 0) { | |
1267 rgl1 = l1 - 1; | |
1268 ll = buffer_get_line(view->buffer, rgl1); | |
1269 rgb1 = ll->len; | |
1270 rgl2 = l2; | |
1271 ll = buffer_get_line(view->buffer, rgl2); | |
1272 rgb2 = ll->len; | |
1273 } else if (l2 < view->lines_num - 1) { | |
1274 rgl1 = l1; | |
1275 rgb1 = 0; | |
1276 rgl2 = l2 + 1; | |
1277 rgb2 = 0; | |
1278 } else { | |
1279 rgl1 = l1; | |
1280 rgb1 = 0; | |
1281 rgl2 = l2; | |
1282 ll = buffer_get_line(view->buffer, rgl2); | |
1283 rgb2 = ll->len; | |
1284 } | |
1285 if (l2 < view->lines_num - 1) { | |
1286 new_line = l1; | |
1287 new_byte = view_x_softline_to_pos( | |
1288 view, l2 + 1, x, 0 | |
1289 ); | |
1290 } else if (l1 > 0) { | |
1291 new_line = l1 - 1; | |
1292 new_byte = view_x_softline_to_pos( | |
1293 view, l1 - 1, x, 0 | |
1294 ); | |
1295 } else { | |
1296 new_line = 0; | |
1297 new_byte = 0; | |
1298 } | |
1299 buffer_delete_with_undo_base( | |
1300 view->buffer, cur_range, | |
1301 start_undo_group, view->mode, | |
1302 rgl1, rgb1, rgl2, rgb2, text_ret | |
1303 ); | |
1304 } else if (delmode == DELETE_SOFTLINE) { | |
1305 int x, sl_useless; | |
1306 view_pos_to_x_softline(view, cur_line, cur_byte, &x, &sl… | |
1307 if (line_index1 == line_index2) { | |
1308 int x_useless, l1, l2; | |
1309 ledit_line *line1 = buffer_get_line(view->buffer… | |
1310 ledit_view_line *vline1 = view_get_line(view, li… | |
1311 view_pos_to_x_softline(view, line_index1, byte_i… | |
1312 view_pos_to_x_softline(view, line_index2, byte_i… | |
1313 PangoLayout *layout = get_pango_layout(view, lin… | |
1314 PangoLayoutLine *pl1 = pango_layout_get_line_rea… | |
1315 PangoLayoutLine *pl2 = pango_layout_get_line_rea… | |
1316 /* don't delete entire line if it is the last on… | |
1317 if (l1 == 0 && l2 == vline1->softlines - 1 && vi… | |
1318 if (line_index1 < view->lines_num - 1) { | |
1319 /* cursor can be moved to next h… | |
1320 new_line = line_index1; | |
1321 new_byte = view_x_softline_to_po… | |
1322 view, line_index1 + 1, x, 0 | |
1323 ); | |
1324 rgl1 = line_index1; | |
1325 rgb1 = 0; | |
1326 rgl2 = line_index1 + 1; | |
1327 rgb2 = 0; | |
1328 } else { | |
1329 /* cursor has to be be moved to … | |
1330 because last line in buffer i… | |
1331 /* note: logically, line_index1 … | |
1332 view->lines_num > 1 && line_i… | |
1333 new_line = line_index1 - 1; | |
1334 ledit_line *prevline = buffer_ge… | |
1335 ledit_view_line *vprevline = vie… | |
1336 if (vprevline->text_dirty) | |
1337 set_pango_text_and_highl… | |
1338 new_byte = view_x_softline_to_po… | |
1339 rgl1 = line_index1 - 1; | |
1340 rgb1 = prevline->len; | |
1341 rgl2 = line_index1; | |
1342 rgb2 = line1->len; | |
1343 } | |
1344 buffer_delete_with_undo_base( | |
1345 view->buffer, cur_range, | |
1346 start_undo_group, view->mode, | |
1347 rgl1, rgb1, rgl2, rgb2, text_ret | |
1348 ); | |
1349 } else { | |
1350 ledit_assert(pl2->start_index + pl2->len… | |
1351 rgl1 = rgl2 = line_index1; | |
1352 rgb1 = (size_t)pl1->start_index; | |
1353 rgb2 = (size_t)(pl2->start_index + pl2->… | |
1354 /* the deletion has to happen before cal… | |
1355 position because deleting softlines c… | |
1356 wrapping as well */ | |
1357 buffer_delete_with_undo_base( | |
1358 view->buffer, cur_range, | |
1359 start_undo_group, view->mode, | |
1360 rgl1, rgb1, rgl2, rgb2, text_ret | |
1361 ); | |
1362 set_pango_text_and_highlight(view, line_… | |
1363 vline1 = view_get_line(view, line_index1… | |
1364 if (l1 == vline1->softlines && line_inde… | |
1365 new_line = line_index1 + 1; | |
1366 new_byte = view_x_softline_to_po… | |
1367 view, line_index1 + 1, x, 0 | |
1368 ); | |
1369 } else if (l1 <= vline1->softlines - 1) { | |
1370 new_line = line_index1; | |
1371 new_byte = view_x_softline_to_po… | |
1372 view, line_index1, x, l1 | |
1373 ); | |
1374 } else if (l1 > 0) { | |
1375 new_line = line_index1; | |
1376 new_byte = view_x_softline_to_po… | |
1377 view, line_index1, x, l1 - 1 | |
1378 ); | |
1379 } else { | |
1380 /* the line has been emptied and… | |
1381 new_line = 0; | |
1382 new_byte = 0; | |
1383 } | |
1384 } | |
1385 } else { | |
1386 int x_useless, sl1, sl2; | |
1387 size_t l1 = line_index1, b1 = byte_index1; | |
1388 size_t l2 = line_index2, b2 = byte_index2; | |
1389 ledit_line *ll2 = buffer_get_line(view->buffer, … | |
1390 ledit_view_line *vl2 = view_get_line(view, l2); | |
1391 PangoLayout *layout1 = get_pango_layout(view, l1… | |
1392 PangoLayout *layout2 = get_pango_layout(view, l2… | |
1393 pango_layout_index_to_line_x(layout1, b1, 0, &sl… | |
1394 pango_layout_index_to_line_x(layout2, b2, 0, &sl… | |
1395 PangoLayoutLine *pl1 = pango_layout_get_line_rea… | |
1396 PangoLayoutLine *pl2 = pango_layout_get_line_rea… | |
1397 if (sl1 == 0 && sl2 == vl2->softlines - 1) { | |
1398 if (l1 == 0 && l2 == view->lines_num - 1… | |
1399 rgl1 = l1; | |
1400 rgl2 = l2; | |
1401 rgb1 = 0; | |
1402 rgb2 = ll2->len; | |
1403 new_line = 0; | |
1404 new_byte = 0; | |
1405 } else { | |
1406 if (l2 == view->lines_num - 1) { | |
1407 new_line = l1 - 1; | |
1408 ledit_line *new_lline = … | |
1409 ledit_view_line *new_vli… | |
1410 if (new_vline->text_dirt… | |
1411 set_pango_text_a… | |
1412 new_byte = view_x_softli… | |
1413 rgl1 = l1 - 1; | |
1414 rgb1 = new_lline->len; | |
1415 rgl2 = l2; | |
1416 rgb2 = ll2->len; | |
1417 } else { | |
1418 new_line = l1; | |
1419 new_byte = view_x_softli… | |
1420 view, l2 + 1, x, 0 | |
1421 ); | |
1422 rgl1 = l1; | |
1423 rgb1 = 0; | |
1424 rgl2 = l2 + 1; | |
1425 rgb2 = 0; | |
1426 } | |
1427 } | |
1428 buffer_delete_with_undo_base( | |
1429 view->buffer, cur_range, | |
1430 start_undo_group, view->mode, | |
1431 rgl1, rgb1, rgl2, rgb2, text_ret | |
1432 ); | |
1433 } else if (sl1 == 0) { | |
1434 rgl1 = l1; | |
1435 rgb1 = 0; | |
1436 rgl2 = l2; | |
1437 rgb2 = (size_t)(pl2->start_index + pl2->… | |
1438 buffer_delete_with_undo_base( | |
1439 view->buffer, cur_range, | |
1440 start_undo_group, view->mode, | |
1441 rgl1, rgb1, rgl2, rgb2, text_ret | |
1442 ); | |
1443 new_line = l1; | |
1444 new_byte = view_x_softline_to_pos(view, … | |
1445 } else if (sl2 == vl2->softlines - 1) { | |
1446 rgl1 = l1; | |
1447 rgb1 = (size_t)pl1->start_index; | |
1448 rgl2 = l2; | |
1449 rgb2 = ll2->len; | |
1450 if (l2 + 1 == view->lines_num) { | |
1451 new_line = l1; | |
1452 new_byte = view_x_softline_to_po… | |
1453 } else { | |
1454 new_line = l1 + 1; | |
1455 new_byte = view_x_softline_to_po… | |
1456 view, l2 + 1, x, 0 | |
1457 ); | |
1458 } | |
1459 buffer_delete_with_undo_base( | |
1460 view->buffer, cur_range, | |
1461 start_undo_group, view->mode, | |
1462 rgl1, rgb1, rgl2, rgb2, text_ret | |
1463 ); | |
1464 } else { | |
1465 rgl1 = l1; | |
1466 rgb1 = (size_t)pl1->start_index; | |
1467 rgl2 = l2; | |
1468 rgb2 = (size_t)(pl2->start_index + pl2->… | |
1469 buffer_delete_with_undo_base( | |
1470 view->buffer, cur_range, | |
1471 start_undo_group, view->mode, | |
1472 rgl1, rgb1, rgl2, rgb2, text_ret | |
1473 ); | |
1474 new_line = l1; | |
1475 /* important so vl1->softlines is update… | |
1476 set_pango_text_and_highlight(view, l1); | |
1477 ledit_view_line *vl1 = view_get_line(vie… | |
1478 /* it's technically possible that the re… | |
1479 second line is so small that it doesn… | |
1480 softline, so there needs to be a spec… | |
1481 a bit weird because the cursor will s… | |
1482 same line, but it now includes the re… | |
1483 (FIXME: this is probably not the best… | |
1484 new_byte = view_x_softline_to_pos( | |
1485 view, l1, x, sl1 + 1 < vl1->softline… | |
1486 ); | |
1487 } | |
1488 } | |
1489 } else { | |
1490 rgl1 = line_index1; | |
1491 rgb1 = byte_index1; | |
1492 rgl2 = line_index2; | |
1493 rgb2 = byte_index2; | |
1494 new_line = rgl1; | |
1495 new_byte = rgb1; | |
1496 buffer_delete_with_undo_base( | |
1497 view->buffer, cur_range, | |
1498 start_undo_group, view->mode, | |
1499 rgl1, rgb1, rgl2, rgb2, text_ret | |
1500 ); | |
1501 } | |
1502 /* note: line1/byte1 need to be set at the top since deleting te… | |
1503 might change the current line/byte of the view through the no… | |
1504 functions */ | |
1505 cur_range.line2 = new_line; | |
1506 cur_range.byte2 = new_byte; | |
1507 undo_change_last_cur_range(view->buffer->undo, cur_range); | |
1508 /* FIXME: too much magic - maybe don't include this here */ | |
1509 if (view->mode == NORMAL) | |
1510 new_byte = view_get_legal_normal_pos(view, new_line, new… | |
1511 if (new_line_ret) | |
1512 *new_line_ret = new_line; | |
1513 if (new_byte_ret) | |
1514 *new_byte_ret = new_byte; | |
1515 } | |
1516 | |
1517 /* FIXME: any way to make this more efficient? */ | |
1518 void | |
1519 view_resize_textview(void *data) { | |
1520 ledit_theme *theme = config_get_theme(); | |
1521 ledit_view *view = (ledit_view *)data; | |
1522 view->total_height = 0; | |
1523 int text_w, text_h; | |
1524 window_get_textview_size(view->window, &text_w, &text_h); | |
1525 for (size_t i = 0; i < view->lines_num; i++) { | |
1526 ledit_view_line *line = view_get_line(view, i); | |
1527 line->w = text_w; | |
1528 line->text_dirty = 1; /* it's a bit weird to set this, i… | |
1529 set_pango_text_and_highlight(view, i); | |
1530 line->y_offset = view->total_height; | |
1531 line->dirty = 1; | |
1532 line->h_dirty = 0; | |
1533 view->total_height += line->h + theme->extra_line_spacin… | |
1534 } | |
1535 view->total_height -= theme->extra_line_spacing; | |
1536 window_set_scroll_max(view->window, view->total_height); | |
1537 if (view->display_offset > 0 && | |
1538 view->display_offset + text_h > view->total_height) { | |
1539 view_scroll(view, view->total_height - text_h); | |
1540 } | |
1541 } | |
1542 | |
1543 void | |
1544 view_scroll(ledit_view *view, long new_offset) { | |
1545 int text_w, text_h; | |
1546 window_get_textview_size(view->window, &text_w, &text_h); | |
1547 if (new_offset + text_h > view->total_height) | |
1548 new_offset = view->total_height - text_h; | |
1549 if (new_offset < 0) | |
1550 new_offset = 0; | |
1551 view->display_offset = new_offset; | |
1552 window_set_scroll_pos(view->window, view->display_offset); | |
1553 } | |
1554 | |
1555 /* FIXME: there's gotta be a better/more efficient way to do this... */ | |
1556 /* FIXME: make sure h_dirty is not set here */ | |
1557 /* FIXME: this might cause weird effects when used together with extra-l… | |
1558 void | |
1559 view_get_nearest_legal_pos( | |
1560 ledit_view *view, | |
1561 size_t line, size_t byte, | |
1562 /*int snap_to_nearest, int snap_middle, FIXME: take these parameters… | |
1563 size_t *line_ret, size_t *byte_ret) { | |
1564 PangoRectangle strong, weak; | |
1565 int text_w, text_h; | |
1566 int x, sl_useless; | |
1567 window_get_textview_size(view->window, &text_w, &text_h); | |
1568 ledit_view_line *vline = view_get_line(view, line); | |
1569 PangoLayout *layout = get_pango_layout(view, line); | |
1570 if (byte > INT_MAX) | |
1571 err_overflow(); | |
1572 pango_layout_get_cursor_pos(layout, (int)byte, &strong, &weak); | |
1573 view_pos_to_x_softline(view, line, byte, &x, &sl_useless); | |
1574 long cursor_y = strong.y / PANGO_SCALE + vline->y_offset; | |
1575 PangoRectangle ink, log; | |
1576 if (cursor_y < view->display_offset) { | |
1577 /* search for the hard line covering the top of the scre… | |
1578 size_t hline = line; | |
1579 while (vline->y_offset + vline->h <= view->display_offse… | |
1580 ++hline; | |
1581 vline = view_get_line(view, hline); | |
1582 } | |
1583 /* the current hard line is now the one at the very top … | |
1584 layout = get_pango_layout(view, hline); | |
1585 int num_sl = vline->softlines; | |
1586 int cur_y_off = 0; | |
1587 int sl_index = -1; | |
1588 PangoLayoutLine *sl; | |
1589 /* search for first soft line completely on-screen */ | |
1590 for (int i = 0; i < num_sl; i++) { | |
1591 sl = pango_layout_get_line_readonly(layout, i); | |
1592 if (cur_y_off + vline->y_offset >= view->display… | |
1593 sl_index = i; | |
1594 break; | |
1595 } | |
1596 pango_layout_line_get_pixel_extents(sl, &ink, &l… | |
1597 cur_y_off += log.height; | |
1598 } | |
1599 if (sl_index >= 0) { | |
1600 /* we found the correct soft line */ | |
1601 *line_ret = hline; | |
1602 *byte_ret = view_x_softline_to_pos(view, hline, … | |
1603 } else if (hline < view->lines_num - 1) { | |
1604 /* need to move to next hard line */ | |
1605 *line_ret = hline + 1; | |
1606 *byte_ret = view_x_softline_to_pos(view, hline +… | |
1607 } else { | |
1608 /* no idea if this can happen, but just fail and… | |
1609 the last soft line of the last hard line */ | |
1610 *line_ret = hline; | |
1611 *byte_ret = view_x_softline_to_pos(view, hline, … | |
1612 } | |
1613 } else if (cursor_y + strong.height / PANGO_SCALE > | |
1614 view->display_offset + text_h) { | |
1615 /* search for the hard line covering the bottom of the s… | |
1616 size_t hline = line; | |
1617 while (vline->y_offset > view->display_offset + text_h &… | |
1618 --hline; | |
1619 vline = view_get_line(view, hline); | |
1620 } | |
1621 /* the current hard line is now the one at the very bott… | |
1622 layout = get_pango_layout(view, hline); | |
1623 int num_sl = vline->softlines; | |
1624 int cur_y_off = 0; | |
1625 int sl_index = -1; | |
1626 PangoLayoutLine *sl; | |
1627 /* search for last soft line completely on-screen */ | |
1628 for (int i = num_sl - 1; i >= 0; i--) { | |
1629 sl = pango_layout_get_line_readonly(layout, i); | |
1630 if (vline->y_offset + vline->h - cur_y_off < vie… | |
1631 sl_index = i; | |
1632 break; | |
1633 } | |
1634 pango_layout_line_get_pixel_extents(sl, &ink, &l… | |
1635 cur_y_off += log.height; | |
1636 } | |
1637 if (sl_index >= 0) { | |
1638 /* we found the correct soft line */ | |
1639 *line_ret = hline; | |
1640 *byte_ret = view_x_softline_to_pos(view, hline, … | |
1641 } else if (hline > 0) { | |
1642 /* need to move to previous hard line */ | |
1643 *line_ret = hline - 1; | |
1644 vline = view_get_line(view, hline - 1); | |
1645 num_sl = vline->softlines; | |
1646 *byte_ret = view_x_softline_to_pos(view, hline -… | |
1647 } else { | |
1648 /* no idea if this can happen, but just fail and… | |
1649 the first soft line of the first hard line */ | |
1650 *line_ret = hline; | |
1651 *byte_ret = view_x_softline_to_pos(view, hline, … | |
1652 } | |
1653 } else { | |
1654 *line_ret = line; | |
1655 *byte_ret = byte; | |
1656 } | |
1657 } | |
1658 | |
1659 void | |
1660 view_xy_to_line_byte(ledit_view *view, int x, int y, int snap_to_nearest… | |
1661 ledit_theme *theme = config_get_theme(); | |
1662 long pos = view->display_offset + y; | |
1663 for (size_t i = 0; i < view->lines_num; i++) { | |
1664 ledit_view_line *vline = view_get_line(view, i); | |
1665 long y1 = vline->y_offset - (i == 0 ? 0 : theme->extra_l… | |
1666 long y2 = vline->y_offset + vline->h + theme->extra_line… | |
1667 if ((y1 <= pos && y2 > pos) || i == view->lines_num - 1)… | |
1668 int index, trailing; | |
1669 PangoLayout *layout = get_pango_layout(view, i); | |
1670 /* FIXME: what if i == view->lines_num - 1 but p… | |
1671 pango_layout_xy_to_index( | |
1672 layout, | |
1673 x * PANGO_SCALE, (int)(pos - y1) * PANGO_SCA… | |
1674 &index, &trailing | |
1675 ); | |
1676 *byte_ret = (size_t)index; | |
1677 if (snap_to_nearest) { | |
1678 ledit_line *ll = buffer_get_line(view->b… | |
1679 while (trailing > 0) { | |
1680 trailing--; | |
1681 *byte_ret = line_next_utf8(ll, *… | |
1682 } | |
1683 } | |
1684 *line_ret = i; | |
1685 return; | |
1686 } | |
1687 } | |
1688 *line_ret = 0; | |
1689 *byte_ret = 0; | |
1690 } | |
1691 | |
1692 static void | |
1693 scroll_to_pos(ledit_view *view, size_t line, size_t byte, int top) { | |
1694 PangoRectangle strong, weak; | |
1695 int text_w, text_h; | |
1696 window_get_textview_size(view->window, &text_w, &text_h); | |
1697 ledit_view_line *vl = view_get_line(view, line); | |
1698 PangoLayout *layout = get_pango_layout(view, line); | |
1699 if (byte > INT_MAX) | |
1700 err_overflow(); | |
1701 pango_layout_get_cursor_pos(layout, (int)byte, &strong, &weak); | |
1702 long cursor_y = strong.y / PANGO_SCALE + vl->y_offset; | |
1703 if (top) { | |
1704 view_scroll(view, cursor_y); | |
1705 } else { | |
1706 view_scroll(view, cursor_y - text_h + strong.height / PA… | |
1707 } | |
1708 } | |
1709 | |
1710 void | |
1711 view_scroll_to_pos_top(ledit_view *view, size_t line, size_t byte) { | |
1712 scroll_to_pos(view, line, byte, 1); | |
1713 } | |
1714 | |
1715 void | |
1716 view_scroll_to_pos_bottom(ledit_view *view, size_t line, size_t byte) { | |
1717 scroll_to_pos(view, line, byte, 0); | |
1718 } | |
1719 | |
1720 void | |
1721 view_ensure_cursor_shown(ledit_view *view) { | |
1722 PangoRectangle strong, weak; | |
1723 int text_w, text_h; | |
1724 window_get_textview_size(view->window, &text_w, &text_h); | |
1725 ledit_view_line *vline = view_get_line(view, view->cur_line); | |
1726 PangoLayout *layout = get_pango_layout(view, view->cur_line); | |
1727 if (view->cur_index > INT_MAX) | |
1728 err_overflow(); | |
1729 pango_layout_get_cursor_pos( | |
1730 layout, (int)view->cur_index, &strong, &weak | |
1731 ); | |
1732 long cursor_y = strong.y / PANGO_SCALE + vline->y_offset; | |
1733 if (cursor_y < view->display_offset) { | |
1734 view_scroll(view, cursor_y); | |
1735 } else if (cursor_y + strong.height / PANGO_SCALE > | |
1736 view->display_offset + text_h) { | |
1737 view_scroll(view, cursor_y - text_h + strong.height / PA… | |
1738 } | |
1739 } | |
1740 | |
1741 /* FIXME: don't reset selection when selection is clicked away */ | |
1742 /* FIXME: when selecting with mouse, only call this when button is relea… | |
1743 /* lines and bytes need to be sorted already! */ | |
1744 static void | |
1745 copy_selection_to_x_primary(ledit_view *view, size_t line1, size_t byte1… | |
1746 txtbuf *primary = clipboard_get_primary_buffer(view->buffer->cli… | |
1747 buffer_copy_text_to_txtbuf(view->buffer, primary, line1, byte1, … | |
1748 clipboard_set_primary_selection_owner(view->buffer->clipboard); | |
1749 /* | |
1750 FIXME | |
1751 if (XGetSelectionOwner(state.dpy, XA_PRIMARY) != state.win) | |
1752 selclear(); | |
1753 */ | |
1754 } | |
1755 | |
1756 void | |
1757 view_wipe_selection(ledit_view *view) { | |
1758 if (view->sel_valid) { | |
1759 if (view->sel.line1 > view->sel.line2) | |
1760 swap_sz(&view->sel.line1, &view->sel.line2); | |
1761 for (size_t i = view->sel.line1; i <= view->sel.line2; i… | |
1762 view_wipe_line_cursor_attrs(view, i); | |
1763 } | |
1764 } | |
1765 view->sel_valid = 0; | |
1766 view->sel.line1 = view->sel.line2 = 0; | |
1767 view->sel.byte1 = view->sel.byte2 = 0; | |
1768 } | |
1769 | |
1770 void | |
1771 view_set_selection(ledit_view *view, size_t line1, size_t byte1, size_t … | |
1772 if (view->sel_valid && | |
1773 line1 == view->sel.line1 && line2 == view->sel.line2 && | |
1774 byte1 == view->sel.byte1 && byte2 == view->sel.byte2) { | |
1775 return; | |
1776 } | |
1777 size_t l1_new = line1, l2_new = line2; | |
1778 size_t b1_new = byte1, b2_new = byte2; | |
1779 sort_range(&l1_new, &b1_new, &l2_new, &b2_new); | |
1780 sort_range(&view->sel.line1, &view->sel.byte1, &view->sel.line2,… | |
1781 /* FIXME: make this a bit nicer and optimize it */ | |
1782 /* FIXME: always reset view->sel so bugs like this can't happen … | |
1783 a function forgets to check sel_valid */ | |
1784 if (view->sel_valid) { | |
1785 if (view->sel.line1 > l2_new || view->sel.line2 < l1_new… | |
1786 for (size_t i = view->sel.line1; i <= view->sel.… | |
1787 view_wipe_line_cursor_attrs(view, i); | |
1788 } | |
1789 } else { | |
1790 for (size_t i = view->sel.line1; i < l1_new; i++… | |
1791 view_wipe_line_cursor_attrs(view, i); | |
1792 } | |
1793 for (size_t i = view->sel.line2; i > l2_new; i--… | |
1794 view_wipe_line_cursor_attrs(view, i); | |
1795 } | |
1796 } | |
1797 } | |
1798 for (size_t i = l1_new; i <= l2_new; i++) { | |
1799 /* only change the ones that were not already selected */ | |
1800 if (!view->sel_valid || i <= view->sel.line1 || i >= vie… | |
1801 ledit_view_line *vl = view_get_line(view, i); | |
1802 vl->highlight_dirty = 1; | |
1803 } | |
1804 } | |
1805 /* force current line to recalculate in case it wasn't covered a… | |
1806 ledit_view_line *vl = view_get_line(view, line2); | |
1807 vl->highlight_dirty = 1; | |
1808 if (l1_new != l2_new || b1_new != b2_new) | |
1809 copy_selection_to_x_primary(view, l1_new, b1_new, l2_new… | |
1810 view->sel.line1 = line1; | |
1811 view->sel.byte1 = byte1; | |
1812 view->sel.line2 = line2; | |
1813 view->sel.byte2 = byte2; | |
1814 view->sel_valid = 1; | |
1815 view->redraw = 1; | |
1816 } | |
1817 | |
1818 static void | |
1819 view_scroll_handler(void *view, long pos) { | |
1820 /* FIXME: check for invalid pos? */ | |
1821 ((ledit_view *)view)->display_offset = pos; | |
1822 } | |
1823 | |
1824 static void | |
1825 view_button_handler(void *data, XEvent *event) { | |
1826 size_t l, b; | |
1827 ledit_view *view= (ledit_view *)data; | |
1828 int x, y; | |
1829 int snap; | |
1830 switch (event->type) { | |
1831 case ButtonPress: | |
1832 if (event->xbutton.button == Button1) { | |
1833 x = event->xbutton.x; | |
1834 y = event->xbutton.y; | |
1835 snap = view->mode == NORMAL ? 0 : 1; | |
1836 view_xy_to_line_byte(view, x, y, snap, &l, &b); | |
1837 view->selecting = 1; | |
1838 view_wipe_line_cursor_attrs(view, view->cur_line… | |
1839 view->cur_line = l; | |
1840 view->cur_index = b; | |
1841 /* don't set selection yet because the mouse may… | |
1842 dragged, so we don't want to switch to visual… | |
1843 allows setting just the cursor position in no… | |
1844 without always switching to visual) */ | |
1845 if (view->mode == NORMAL) | |
1846 view_set_line_cursor_attrs(view, l, b); | |
1847 else if (view->mode == VISUAL) | |
1848 view_set_selection(view, l, b, l, b); | |
1849 } else if (event->xbutton.button == Button2) { | |
1850 view->button2_pressed = 1; | |
1851 } | |
1852 break; | |
1853 case ButtonRelease: | |
1854 /* FIXME: view->button2_pressed should be set to 0 even … | |
1855 the event does not occur inside the text area */ | |
1856 if (event->xbutton.button == Button1) { | |
1857 view->selecting = 0; | |
1858 } else if (event->xbutton.button == Button2) { | |
1859 if (view->button2_pressed && view->mode == INSER… | |
1860 view_paste_primary(view); | |
1861 view->button2_pressed = 0; | |
1862 } | |
1863 break; | |
1864 case MotionNotify: | |
1865 x = event->xmotion.x; | |
1866 y = event->xmotion.y; | |
1867 if (view->selecting) { | |
1868 y = y >= 0 ? y : 0; | |
1869 view_xy_to_line_byte(view, x, y, 1, &l, &b); | |
1870 if (view->mode == NORMAL) { | |
1871 view_wipe_line_cursor_attrs(view, view->… | |
1872 /* FIXME: return to old mode afterwards?… | |
1873 /* should change_mode_group even be call… | |
1874 } | |
1875 view_set_mode(view, VISUAL); | |
1876 if (!view->sel_valid) { | |
1877 /* the selection has just started, so th… | |
1878 position is already set to the beginn… | |
1879 selection (see case ButtonPress above… | |
1880 view_set_selection( | |
1881 view, | |
1882 view->cur_line, view->cur_index, l, b | |
1883 ); | |
1884 } else { | |
1885 view_set_selection( | |
1886 view, | |
1887 view->sel.line1, view->sel.byte1, l,… | |
1888 ); | |
1889 } | |
1890 view->cur_line = l; | |
1891 view->cur_index = b; | |
1892 } | |
1893 break; | |
1894 } | |
1895 } | |
1896 | |
1897 static void | |
1898 view_redraw_text(ledit_view *view) { | |
1899 ledit_theme *theme = config_get_theme(); | |
1900 int cur_line_y = 0; | |
1901 int cursor_displayed = 0; | |
1902 int text_w, text_h; | |
1903 window_get_textview_size(view->window, &text_w, &text_h); | |
1904 /* FIXME: use binary search here */ | |
1905 /* FIXME: draw extra highlight when extra-line-spacing set | |
1906 (also between soft lines because pango doesn't do that) */ | |
1907 for (size_t i = 0; i < view->lines_num; i++) { | |
1908 ledit_view_line *vline = view_get_line(view, i); | |
1909 if (vline->y_offset + vline->h > view->display_offset) { | |
1910 /* FIXME: vline->text_dirty should not happen he… | |
1911 if (vline->text_dirty || vline->highlight_dirty) | |
1912 set_pango_text_and_highlight(view, i); | |
1913 if (vline->dirty || !vline->cache_pixmap_valid) { | |
1914 render_line(view, i); | |
1915 } | |
1916 int final_y = 0; | |
1917 int dest_y = vline->y_offset - view->display_off… | |
1918 int final_h = vline->h; | |
1919 if (vline->y_offset < view->display_offset) { | |
1920 dest_y = 0; | |
1921 final_y = view->display_offset - vline->… | |
1922 final_h -= view->display_offset - vline-… | |
1923 } | |
1924 if (dest_y + final_h > text_h) { | |
1925 final_h -= final_y + final_h - | |
1926 view->display_offset - text_h; | |
1927 } | |
1928 cache_pixmap *pix = cache_get_pixmap( | |
1929 view->cache, vline->cache_pixmap_index | |
1930 ); | |
1931 XCopyArea( | |
1932 view->buffer->common->dpy, pix->pixmap, | |
1933 view->window->drawable, view->window->gc, | |
1934 0, final_y, vline->w, final_h, 0, dest_y | |
1935 ); | |
1936 if (i == view->cur_line) { | |
1937 cur_line_y = vline->y_offset - view->dis… | |
1938 cursor_displayed = 1; | |
1939 } | |
1940 if (vline->y_offset + vline->h >= view->display_… | |
1941 break; | |
1942 } | |
1943 } | |
1944 | |
1945 XSetForeground(view->buffer->common->dpy, view->window->gc, them… | |
1946 PangoRectangle strong, weak; | |
1947 ledit_line *cur_line = buffer_get_line(view->buffer, view->cur_l… | |
1948 PangoLayout *layout = get_pango_layout(view, view->cur_line); | |
1949 pango_layout_get_cursor_pos( | |
1950 layout, (int)view->cur_index, &strong, &weak | |
1951 ); | |
1952 /* FIXME: long, int, etc. */ | |
1953 int cursor_y = strong.y / PANGO_SCALE + cur_line_y; | |
1954 if (cursor_displayed && cursor_y >= 0) { | |
1955 if (view->mode == NORMAL) { | |
1956 /* FIXME: figure out if there's a better way to … | |
1957 /* Seriously, which of the pango folks though it… | |
1958 not highlight spaces at the end of soft lines… | |
1959 horrible idea. Or am I just too stupid to use… | |
1960 /* FIXME: properly document what is happening he… | |
1961 | |
1962 int box_x = strong.x / PANGO_SCALE; | |
1963 int box_w = 10; | |
1964 /* determine where the box should be drawn */ | |
1965 PangoDirection dir = PANGO_DIRECTION_LTR; | |
1966 size_t tmp_index = view->cur_index; | |
1967 if (view->cur_index >= cur_line->len && cur_line… | |
1968 tmp_index = cur_line->len - 1; | |
1969 else if (view->cur_index >= cur_line->len) | |
1970 tmp_index = 0; | |
1971 dir = ledit_pango_layout_get_direction(layout, (… | |
1972 | |
1973 int x, sli; | |
1974 pango_layout_index_to_line_x(layout, (int)view->… | |
1975 PangoLayoutLine *sl = pango_layout_get_line_read… | |
1976 if (dir != sl->resolved_dir) { | |
1977 box_w = 3; | |
1978 } | |
1979 if (dir == PANGO_DIRECTION_RTL || dir == PANGO_D… | |
1980 box_x = box_x - box_w; | |
1981 } | |
1982 | |
1983 if (view->cur_index == cur_line->len || | |
1984 (cur_line->text[view->cur_index] == ' ' && | |
1985 view->cur_index == (size_t)(sl->start_index… | |
1986 XFillRectangle( | |
1987 view->buffer->common->dpy, view->win… | |
1988 box_x, cursor_y, | |
1989 box_w, strong.height / PANGO_SCALE | |
1990 ); | |
1991 } | |
1992 } else if (view->mode == INSERT || view->mode == VISUAL)… | |
1993 XDrawLine( | |
1994 view->buffer->common->dpy, view->window->dra… | |
1995 strong.x / PANGO_SCALE, cursor_y, | |
1996 strong.x / PANGO_SCALE, | |
1997 (strong.y + strong.height) / PANGO_SCALE + c… | |
1998 ); | |
1999 } | |
2000 } | |
2001 /* move input method position */ | |
2002 if (!ledit_window_bottom_bar_text_shown(view->window)) { | |
2003 xximspot( | |
2004 view->window, | |
2005 strong.x / PANGO_SCALE, | |
2006 (strong.y + strong.height) / PANGO_SCALE + cur_line_y | |
2007 ); | |
2008 } | |
2009 view->redraw = 0; | |
2010 } | |
2011 | |
2012 void | |
2013 view_redraw(ledit_view *view, size_t lang_index) { | |
2014 window_set_format_args( | |
2015 view->window, view->mode, view->buffer->hard_line_based, | |
2016 view->cur_line + 1, view->cur_index + 1, lang_index | |
2017 ); | |
2018 if (view->redraw || view->window->redraw) { | |
2019 window_clear(view->window); | |
2020 view_redraw_text(view); | |
2021 window_redraw(view->window); | |
2022 } | |
2023 } | |
2024 | |
2025 void | |
2026 view_undo(ledit_view *view, int num) { | |
2027 /* FIXME: maybe wipe selection (although I guess this | |
2028 currently isn't possible in visual mode anyways) */ | |
2029 view_wipe_line_cursor_attrs(view, view->cur_line); | |
2030 for (int i = 0; i < num; i++) { | |
2031 undo_status s = buffer_undo(view->buffer, view->mode, &v… | |
2032 if (view->mode == NORMAL) { | |
2033 view->cur_index = view_get_legal_normal_pos( | |
2034 view, view->cur_line, view->cur_index | |
2035 ); | |
2036 } | |
2037 if (s != UNDO_NORMAL) { | |
2038 window_show_message(view->window, undo_state_to_… | |
2039 break; | |
2040 } | |
2041 } | |
2042 if (view->mode == NORMAL) | |
2043 view_set_line_cursor_attrs(view, view->cur_line, view->c… | |
2044 else | |
2045 view_wipe_line_cursor_attrs(view, view->cur_line); | |
2046 } | |
2047 | |
2048 void | |
2049 view_redo(ledit_view *view, int num) { | |
2050 view_wipe_line_cursor_attrs(view, view->cur_line); | |
2051 for (int i = 0; i < num; i++) { | |
2052 undo_status s = buffer_redo(view->buffer, view->mode, &v… | |
2053 if (view->mode == NORMAL) { | |
2054 view->cur_index = view_get_legal_normal_pos( | |
2055 view, view->cur_line, view->cur_index | |
2056 ); | |
2057 } | |
2058 if (s != UNDO_NORMAL) { | |
2059 window_show_message(view->window, undo_state_to_… | |
2060 break; | |
2061 } | |
2062 } | |
2063 if (view->mode == NORMAL) | |
2064 view_set_line_cursor_attrs(view, view->cur_line, view->c… | |
2065 else | |
2066 view_wipe_line_cursor_attrs(view, view->cur_line); | |
2067 } | |
2068 | |
2069 static void | |
2070 paste_txtbuf(ledit_view *view, txtbuf *buf) { | |
2071 if (view->mode == NORMAL) | |
2072 view_wipe_line_cursor_attrs(view, view->cur_line); | |
2073 ledit_range cur_range; | |
2074 cur_range.line1 = view->cur_line; | |
2075 cur_range.byte1 = view->cur_index; | |
2076 /* just to avoid false positives during static analysis */ | |
2077 cur_range.line2 = cur_range.byte2 = 0; | |
2078 buffer_insert_with_undo( | |
2079 view->buffer, cur_range, 1, 1, view->mode, | |
2080 view->cur_line, view->cur_index, buf->text, buf->len, | |
2081 &view->cur_line, &view->cur_index | |
2082 ); | |
2083 view_ensure_cursor_shown(view); | |
2084 if (view->mode == NORMAL) | |
2085 view_set_line_cursor_attrs(view, view->cur_line, view->c… | |
2086 } | |
2087 | |
2088 void | |
2089 view_paste_clipboard(ledit_view *view) { | |
2090 txtbuf *buf = clipboard_get_clipboard_text(view->buffer->clipboa… | |
2091 if (!buf) | |
2092 return; /* FIXME: warning? */ | |
2093 paste_txtbuf(view, buf); | |
2094 } | |
2095 | |
2096 void | |
2097 view_paste_primary(ledit_view *view) { | |
2098 txtbuf *buf = clipboard_get_primary_text(view->buffer->clipboard… | |
2099 if (!buf) | |
2100 return; /* FIXME: warning? */ | |
2101 paste_txtbuf(view, buf); | |
2102 } |