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 moved independently of 28 the selection, leading to weird "jumping selection" - this should be made impossible */ 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, size_t byte1, size_t line2, size_t byte2); 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 layout. */ 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_highlight). 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 index. 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 character 78 * at that position is highlighted (this is used for the normal mode cursor). 79 * Otherwise, the default attributes (basic_attrs) are set. 80 */ 81 static void set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *layout); 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 and 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, size_t index); 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 pos) { 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_new(FALSE); 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, view); 118 window_set_button_callback(view->window, &view_button_handler, view); 119 window_set_resize_callback(view->window, &view_resize_textview, view); 120 121 view->lines = ledit_reallocarray(NULL, buffer->lines_cap, sizeof(ledit_view_line)); 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 line, const char *func) { 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, size_t len) { 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_index); 208 } 209 if (sel_valid) 210 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index); 211 } 212 213 void 214 view_notify_delete_text(ledit_view *view, size_t line, size_t index, size_t len) { 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->cur_index) { 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_index); 234 } 235 if (sel_valid) 236 view_set_selection(view, view->cur_line, view->cur_index, 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_helper 246 ); 247 resize_and_move_line_gap(view, add_sz(view->lines_num, 1), line + 1); 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, 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 it breaks anything */ 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, view->cur_index); 281 } 282 cache_invalidate_from_line( 283 view->cache, index1, view, 284 &invalidate_pixmap_line_helper, &invalidate_layout_line_helper 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_num); 291 if (min_size != view->lines_cap) 292 resize_and_move_line_gap(view, view->lines_num, view->lines_gap); 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, 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, XRenderColor bg) { 320 PangoAttribute *attr0 = pango_attr_foreground_new(fg.red, fg.green, fg.blue); 321 PangoAttribute *attr1 = pango_attr_background_new(bg.red, bg.green, bg.blue); 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.byte2); 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, fg, bg); 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_index + 1, fg, bg); 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_pixmap_index); 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_spacing; 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 PangoLayouts to 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_readonly(layout, &nattrs); 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_readonly(layout, &nattrs); 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 < (size_t)nattrs; 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_white) { 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 uses unicode semantics */ 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_char, \ 877 wrapped_line, &cur_char, &cur_byte, &real_byte)) == -1 && \ 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->lines_num - 1); \ 890 *byte_ret = view_get_legal_normal_pos(view, view->lines_num - 1, ll->len); \ 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_char, \ 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, softline); 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 *x_ret, int *y_ret, int *h_ret) { 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 sometimes 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 movement, size_t *prev_index_ret) { 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't work 1028 anyways because of pango (and because everything else would break 1029 anyways with such long lines), so it's stupid to do all this weird 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 being at 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, int *end_ret) { 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_line_helper 1085 ); 1086 cl = cache_get_layout(view->cache, vl->cache_layout_index); 1087 } else { 1088 cl = cache_get_layout(view->cache, vl->cache_layout_index); 1089 } 1090 if (cl->layout == NULL) { 1091 cl->layout = pango_layout_new(view->window->context); 1092 pango_layout_set_font_description(cl->layout, view->window->font); 1093 pango_layout_set_wrap(cl->layout, PANGO_WRAP_WORD_CHAR); 1094 pango_layout_set_spacing(cl->layout, theme->extra_line_spacing * PANGO_SCALE); 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_ret, int *softline_ret) { 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, x_ret); 1135 PangoLayoutLine *pango_line = pango_layout_get_line_readonly(layout, *softline_ret); 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 softline) { 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 subtracted */ 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 between graphemes */ 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_position) { 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_index2; 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 here? */ 1247 size_t cur_line = line_index1; 1248 size_t cur_byte = byte_index1; 1249 sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2); 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 and then using 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_useless); 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_useless); 1307 if (line_index1 == line_index2) { 1308 int x_useless, l1, l2; 1309 ledit_line *line1 = buffer_get_line(view->buffer, line_index1); 1310 ledit_view_line *vline1 = view_get_line(view, line_index1); 1311 view_pos_to_x_softline(view, line_index1, byte_index1, &x_useless, &l1); 1312 view_pos_to_x_softline(view, line_index2, byte_index2, &x_useless, &l2); 1313 PangoLayout *layout = get_pango_layout(view, line_index1); 1314 PangoLayoutLine *pl1 = pango_layout_get_line_readonly(layout, l1); 1315 PangoLayoutLine *pl2 = pango_layout_get_line_readonly(layout, l2); 1316 /* don't delete entire line if it is the last one remaining */ 1317 if (l1 == 0 && l2 == vline1->softlines - 1 && view->lines_num > 1) { 1318 if (line_index1 < view->lines_num - 1) { 1319 /* cursor can be moved to next hard line */ 1320 new_line = line_index1; 1321 new_byte = view_x_softline_to_pos( 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 previous hard line 1330 because last line in buffer is deleted */ 1331 /* note: logically, line_index1 - 1 must be >= 0 because 1332 view->lines_num > 1 && line_index1 >= view->lines_num - 1 */ 1333 new_line = line_index1 - 1; 1334 ledit_line *prevline = buffer_get_line(view->buffer, new_line); 1335 ledit_view_line *vprevline = view_get_line(view, new_line); 1336 if (vprevline->text_dirty) 1337 set_pango_text_and_highlight(view, new_line); 1338 new_byte = view_x_softline_to_pos(view, new_line, x, vprevline->softlines - 1); 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->length >= pl1->start_index); 1351 rgl1 = rgl2 = line_index1; 1352 rgb1 = (size_t)pl1->start_index; 1353 rgb2 = (size_t)(pl2->start_index + pl2->length); 1354 /* the deletion has to happen before calculating the new cursor 1355 position because deleting softlines could change the line 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_index1); 1363 vline1 = view_get_line(view, line_index1); 1364 if (l1 == vline1->softlines && line_index1 < view->lines_num - 1) { 1365 new_line = line_index1 + 1; 1366 new_byte = view_x_softline_to_pos( 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_pos( 1372 view, line_index1, x, l1 1373 ); 1374 } else if (l1 > 0) { 1375 new_line = line_index1; 1376 new_byte = view_x_softline_to_pos( 1377 view, line_index1, x, l1 - 1 1378 ); 1379 } else { 1380 /* the line has been emptied and is the last line remaining */ 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, l2); 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, &sl1, &x_useless); 1394 pango_layout_index_to_line_x(layout2, b2, 0, &sl2, &x_useless); 1395 PangoLayoutLine *pl1 = pango_layout_get_line_readonly(layout1, sl1); 1396 PangoLayoutLine *pl2 = pango_layout_get_line_readonly(layout2, sl2); 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 = buffer_get_line(view->buffer, new_line); 1409 ledit_view_line *new_vline = view_get_line(view, new_line); 1410 if (new_vline->text_dirty) 1411 set_pango_text_and_highlight(view, new_line); 1412 new_byte = view_x_softline_to_pos(view, new_line, x, new_vline->softlines - 1); 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_softline_to_pos( 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->length); 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, l1, x, 0); 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_pos(view, l1, x, sl1 - 1); 1453 } else { 1454 new_line = l1 + 1; 1455 new_byte = view_x_softline_to_pos( 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->length); 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 updated */ 1476 set_pango_text_and_highlight(view, l1); 1477 ledit_view_line *vl1 = view_get_line(view, l1); 1478 /* it's technically possible that the remaining part of the 1479 second line is so small that it doesn't generate a new 1480 softline, so there needs to be a special case - this is 1481 a bit weird because the cursor will seem to stay on the 1482 same line, but it now includes the rest of the second line 1483 (FIXME: this is probably not the best thing to do) */ 1484 new_byte = view_x_softline_to_pos( 1485 view, l1, x, sl1 + 1 < vl1->softlines ? sl1 + 1 : sl1 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 text 1503 might change the current line/byte of the view through the notify 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_byte); 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, it should rather be something like 'w_dirty' */ 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_spacing; 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-line-spacing */ 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 screen */ 1578 size_t hline = line; 1579 while (vline->y_offset + vline->h <= view->display_offset && hline < view->lines_num - 1) { 1580 ++hline; 1581 vline = view_get_line(view, hline); 1582 } 1583 /* the current hard line is now the one at the very top of the screen*/ 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_offset) { 1593 sl_index = i; 1594 break; 1595 } 1596 pango_layout_line_get_pixel_extents(sl, &ink, &log); 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, x, sl_index); 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 + 1, x, 0); 1607 } else { 1608 /* no idea if this can happen, but just fail and use 1609 the last soft line of the last hard line */ 1610 *line_ret = hline; 1611 *byte_ret = view_x_softline_to_pos(view, hline, x, num_sl - 1); 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 screen */ 1616 size_t hline = line; 1617 while (vline->y_offset > view->display_offset + text_h && hline > 0) { 1618 --hline; 1619 vline = view_get_line(view, hline); 1620 } 1621 /* the current hard line is now the one at the very bottom of the screen*/ 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 < view->display_offset + text_h) { 1631 sl_index = i; 1632 break; 1633 } 1634 pango_layout_line_get_pixel_extents(sl, &ink, &log); 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, x, sl_index); 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 - 1, x, num_sl - 1); 1647 } else { 1648 /* no idea if this can happen, but just fail and use 1649 the first soft line of the first hard line */ 1650 *line_ret = hline; 1651 *byte_ret = view_x_softline_to_pos(view, hline, x, 0); 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, size_t *line_ret, size_t *byte_ret) { 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_line_spacing / 2); 1666 long y2 = vline->y_offset + vline->h + theme->extra_line_spacing / 2 + theme->extra_line_spacing % 2; 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 pos - h < 0? */ 1671 pango_layout_xy_to_index( 1672 layout, 1673 x * PANGO_SCALE, (int)(pos - y1) * PANGO_SCALE, 1674 &index, &trailing 1675 ); 1676 *byte_ret = (size_t)index; 1677 if (snap_to_nearest) { 1678 ledit_line *ll = buffer_get_line(view->buffer, i); 1679 while (trailing > 0) { 1680 trailing--; 1681 *byte_ret = line_next_utf8(ll, *byte_ret); 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 / PANGO_SCALE); 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 / PANGO_SCALE); 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 released */ 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, size_t line2, size_t byte2) { 1746 txtbuf *primary = clipboard_get_primary_buffer(view->buffer->clipboard); 1747 buffer_copy_text_to_txtbuf(view->buffer, primary, line1, byte1, line2, byte2); 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 line2, size_t byte2) { 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, &view->sel.byte2); 1781 /* FIXME: make this a bit nicer and optimize it */ 1782 /* FIXME: always reset view->sel so bugs like this can't happen where 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.line2; i++) { 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 >= view->sel.line2) { 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 above */ 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, b2_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 not be 1842 dragged, so we don't want to switch to visual (this 1843 allows setting just the cursor position in normal mode 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 when 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 == INSERT) 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->cur_line); 1872 /* FIXME: return to old mode afterwards? */ 1873 /* should change_mode_group even be called here? */ 1874 } 1875 view_set_mode(view, VISUAL); 1876 if (!view->sel_valid) { 1877 /* the selection has just started, so the current 1878 position is already set to the beginning of the 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, b 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 here */ 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_offset; 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->y_offset; 1922 final_h -= view->display_offset - vline->y_offset; 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->display_offset; 1938 cursor_displayed = 1; 1939 } 1940 if (vline->y_offset + vline->h >= view->display_offset + text_h) 1941 break; 1942 } 1943 } 1944 1945 XSetForeground(view->buffer->common->dpy, view->window->gc, theme->cursor_bg.pixel); 1946 PangoRectangle strong, weak; 1947 ledit_line *cur_line = buffer_get_line(view->buffer, view->cur_line); 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 do this */ 1957 /* Seriously, which of the pango folks though it would be a good idea to 1958 not highlight spaces at the end of soft lines? That is an utterly 1959 horrible idea. Or am I just too stupid to use it properly? */ 1960 /* FIXME: properly document what is happening here */ 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->len > 0) 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, (int)tmp_index); 1972 1973 int x, sli; 1974 pango_layout_index_to_line_x(layout, (int)view->cur_index, 0, &sli, &x); 1975 PangoLayoutLine *sl = pango_layout_get_line_readonly(layout, sli); 1976 if (dir != sl->resolved_dir) { 1977 box_w = 3; 1978 } 1979 if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL) { 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 + sl->length - 1))) { 1986 XFillRectangle( 1987 view->buffer->common->dpy, view->window->drawable, view->window->gc, 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->drawable, view->window->gc, 1995 strong.x / PANGO_SCALE, cursor_y, 1996 strong.x / PANGO_SCALE, 1997 (strong.y + strong.height) / PANGO_SCALE + cur_line_y 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, &view->cur_line, &view->cur_index); 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_str(s), -1); 2039 break; 2040 } 2041 } 2042 if (view->mode == NORMAL) 2043 view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); 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, &view->cur_line, &view->cur_index); 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_str(s), -1); 2060 break; 2061 } 2062 } 2063 if (view->mode == NORMAL) 2064 view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); 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->cur_index); 2086 } 2087 2088 void 2089 view_paste_clipboard(ledit_view *view) { 2090 txtbuf *buf = clipboard_get_clipboard_text(view->buffer->clipboard); 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 }