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