1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * GNU General Public License for more details.
10 *
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software Foundation,
13 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14 *
15 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
16 * All rights reserved.
17 */
18
19 /** \file
20 * \ingroup sptext
21 */
22
23 #include "MEM_guardedalloc.h"
24
25 #include "BLF_api.h"
26
27 #include "BLI_blenlib.h"
28 #include "BLI_math.h"
29
30 #include "DNA_screen_types.h"
31 #include "DNA_space_types.h"
32 #include "DNA_text_types.h"
33
34 #include "BKE_context.h"
35 #include "BKE_screen.h"
36 #include "BKE_text.h"
37 #include "BKE_text_suggestions.h"
38
39 #include "ED_text.h"
40
41 #include "GPU_immediate.h"
42 #include "GPU_state.h"
43
44 #include "UI_interface.h"
45 #include "UI_resources.h"
46 #include "UI_view2d.h"
47
48 #include "text_format.h"
49 #include "text_intern.h"
50
51 /******************** text font drawing ******************/
52
53 typedef struct TextDrawContext {
54 int font_id;
55 int cwidth_px;
56 int lheight_px;
57 bool syntax_highlight;
58 } TextDrawContext;
59
text_draw_context_init(const SpaceText * st,TextDrawContext * tdc)60 static void text_draw_context_init(const SpaceText *st, TextDrawContext *tdc)
61 {
62 tdc->font_id = blf_mono_font;
63 tdc->cwidth_px = 0;
64 tdc->lheight_px = st->runtime.lheight_px;
65 tdc->syntax_highlight = st->showsyntax && ED_text_is_syntax_highlight_supported(st->text);
66 }
67
text_font_begin(const TextDrawContext * tdc)68 static void text_font_begin(const TextDrawContext *tdc)
69 {
70 BLF_size(tdc->font_id, tdc->lheight_px, 72);
71 }
72
text_font_end(const TextDrawContext * UNUSED (tdc))73 static void text_font_end(const TextDrawContext *UNUSED(tdc))
74 {
75 }
76
text_font_draw(const TextDrawContext * tdc,int x,int y,const char * str)77 static int text_font_draw(const TextDrawContext *tdc, int x, int y, const char *str)
78 {
79 int columns;
80
81 BLF_position(tdc->font_id, x, y, 0);
82 columns = BLF_draw_mono(tdc->font_id, str, BLF_DRAW_STR_DUMMY_MAX, tdc->cwidth_px);
83
84 return tdc->cwidth_px * columns;
85 }
86
text_font_draw_character(const TextDrawContext * tdc,int x,int y,char c)87 static int text_font_draw_character(const TextDrawContext *tdc, int x, int y, char c)
88 {
89 BLF_position(tdc->font_id, x, y, 0);
90 BLF_draw(tdc->font_id, &c, 1);
91
92 return tdc->cwidth_px;
93 }
94
text_font_draw_character_utf8(const TextDrawContext * tdc,int x,int y,const char * c)95 static int text_font_draw_character_utf8(const TextDrawContext *tdc, int x, int y, const char *c)
96 {
97 int columns;
98
99 const size_t len = BLI_str_utf8_size_safe(c);
100 BLF_position(tdc->font_id, x, y, 0);
101 columns = BLF_draw_mono(tdc->font_id, c, len, tdc->cwidth_px);
102
103 return tdc->cwidth_px * columns;
104 }
105
106 #if 0
107 /* Formats every line of the current text */
108 static void txt_format_text(SpaceText *st)
109 {
110 TextLine *linep;
111
112 if (!st->text) {
113 return;
114 }
115
116 for (linep = st->text->lines.first; linep; linep = linep->next) {
117 txt_format_line(st, linep, 0);
118 }
119 }
120 #endif
121
122 /* Sets the current drawing color based on the format character specified */
format_draw_color(const TextDrawContext * tdc,char formatchar)123 static void format_draw_color(const TextDrawContext *tdc, char formatchar)
124 {
125 switch (formatchar) {
126 case FMT_TYPE_WHITESPACE:
127 break;
128 case FMT_TYPE_SYMBOL:
129 UI_FontThemeColor(tdc->font_id, TH_SYNTAX_S);
130 break;
131 case FMT_TYPE_COMMENT:
132 UI_FontThemeColor(tdc->font_id, TH_SYNTAX_C);
133 break;
134 case FMT_TYPE_NUMERAL:
135 UI_FontThemeColor(tdc->font_id, TH_SYNTAX_N);
136 break;
137 case FMT_TYPE_STRING:
138 UI_FontThemeColor(tdc->font_id, TH_SYNTAX_L);
139 break;
140 case FMT_TYPE_DIRECTIVE:
141 UI_FontThemeColor(tdc->font_id, TH_SYNTAX_D);
142 break;
143 case FMT_TYPE_SPECIAL:
144 UI_FontThemeColor(tdc->font_id, TH_SYNTAX_V);
145 break;
146 case FMT_TYPE_RESERVED:
147 UI_FontThemeColor(tdc->font_id, TH_SYNTAX_R);
148 break;
149 case FMT_TYPE_KEYWORD:
150 UI_FontThemeColor(tdc->font_id, TH_SYNTAX_B);
151 break;
152 case FMT_TYPE_DEFAULT:
153 default:
154 UI_FontThemeColor(tdc->font_id, TH_TEXT);
155 break;
156 }
157 }
158
159 /************************** draw text *****************************/
160
161 /**
162 * Notes on word-wrap
163 * --
164 * All word-wrap functions follow the algorithm below to maintain consistency:
165 * - line:
166 * The line to wrap (tabs converted to spaces)
167 * - view_width:
168 * The maximum number of characters displayable in the region
169 * This equals region_width/font_width for the region
170 * - wrap_chars:
171 * Characters that allow wrapping. This equals [' ', '\t', '-']
172 *
173 * \code{.py}
174 * def wrap(line, view_width, wrap_chars):
175 * draw_start = 0
176 * draw_end = view_width
177 * pos = 0
178 * for c in line:
179 * if pos-draw_start >= view_width:
180 * print line[draw_start:draw_end]
181 * draw_start = draw_end
182 * draw_end += view_width
183 * elif c in wrap_chars:
184 * draw_end = pos+1
185 * pos += 1
186 * print line[draw_start:]
187 * \encode
188 */
189
wrap_width(const SpaceText * st,ARegion * region)190 int wrap_width(const SpaceText *st, ARegion *region)
191 {
192 int winx = region->winx - TXT_SCROLL_WIDTH;
193 int x, max;
194
195 x = TXT_BODY_LEFT(st);
196 max = st->runtime.cwidth_px ? (winx - x) / st->runtime.cwidth_px : 0;
197 return max > 8 ? max : 8;
198 }
199
200 /* Sets (offl, offc) for transforming (line, curs) to its wrapped position */
wrap_offset(const SpaceText * st,ARegion * region,TextLine * linein,int cursin,int * offl,int * offc)201 void wrap_offset(
202 const SpaceText *st, ARegion *region, TextLine *linein, int cursin, int *offl, int *offc)
203 {
204 Text *text;
205 TextLine *linep;
206 int i, j, start, end, max, chop;
207 char ch;
208
209 *offl = *offc = 0;
210
211 if (!st->text) {
212 return;
213 }
214 if (!st->wordwrap) {
215 return;
216 }
217
218 text = st->text;
219
220 /* Move pointer to first visible line (top) */
221 linep = text->lines.first;
222 i = st->top;
223 while (i > 0 && linep) {
224 int lines = text_get_visible_lines(st, region, linep->line);
225
226 /* Line before top */
227 if (linep == linein) {
228 if (lines <= i) {
229 /* no visible part of line */
230 return;
231 }
232 }
233
234 if (i - lines < 0) {
235 break;
236 }
237
238 linep = linep->next;
239 (*offl) += lines - 1;
240 i -= lines;
241 }
242
243 max = wrap_width(st, region);
244 cursin = BLI_str_utf8_offset_to_column(linein->line, cursin);
245
246 while (linep) {
247 start = 0;
248 end = max;
249 chop = 1;
250 *offc = 0;
251 for (i = 0, j = 0; linep->line[j]; j += BLI_str_utf8_size_safe(linep->line + j)) {
252 int chars;
253 int columns = BLI_str_utf8_char_width_safe(linep->line + j); /* = 1 for tab */
254
255 /* Mimic replacement of tabs */
256 ch = linep->line[j];
257 if (ch == '\t') {
258 chars = st->tabnumber - i % st->tabnumber;
259 if (linep == linein && i < cursin) {
260 cursin += chars - 1;
261 }
262 ch = ' ';
263 }
264 else {
265 chars = 1;
266 }
267
268 while (chars--) {
269 if (i + columns - start > max) {
270 end = MIN2(end, i);
271
272 if (chop && linep == linein && i >= cursin) {
273 if (i == cursin) {
274 (*offl)++;
275 *offc -= end - start;
276 }
277
278 return;
279 }
280
281 (*offl)++;
282 *offc -= end - start;
283
284 start = end;
285 end += max;
286 chop = 1;
287 }
288 else if (ch == ' ' || ch == '-') {
289 end = i + 1;
290 chop = 0;
291 if (linep == linein && i >= cursin) {
292 return;
293 }
294 }
295 i += columns;
296 }
297 }
298 if (linep == linein) {
299 break;
300 }
301 linep = linep->next;
302 }
303 }
304
305 /* cursin - mem, offc - view */
wrap_offset_in_line(const SpaceText * st,ARegion * region,TextLine * linein,int cursin,int * offl,int * offc)306 void wrap_offset_in_line(
307 const SpaceText *st, ARegion *region, TextLine *linein, int cursin, int *offl, int *offc)
308 {
309 int i, j, start, end, chars, max, chop;
310 char ch;
311
312 *offl = *offc = 0;
313
314 if (!st->text) {
315 return;
316 }
317 if (!st->wordwrap) {
318 return;
319 }
320
321 max = wrap_width(st, region);
322
323 start = 0;
324 end = max;
325 chop = 1;
326 *offc = 0;
327 cursin = BLI_str_utf8_offset_to_column(linein->line, cursin);
328
329 for (i = 0, j = 0; linein->line[j]; j += BLI_str_utf8_size_safe(linein->line + j)) {
330 int columns = BLI_str_utf8_char_width_safe(linein->line + j); /* = 1 for tab */
331
332 /* Mimic replacement of tabs */
333 ch = linein->line[j];
334 if (ch == '\t') {
335 chars = st->tabnumber - i % st->tabnumber;
336 if (i < cursin) {
337 cursin += chars - 1;
338 }
339 ch = ' ';
340 }
341 else {
342 chars = 1;
343 }
344
345 while (chars--) {
346 if (i + columns - start > max) {
347 end = MIN2(end, i);
348
349 if (chop && i >= cursin) {
350 if (i == cursin) {
351 (*offl)++;
352 *offc -= end - start;
353 }
354
355 return;
356 }
357
358 (*offl)++;
359 *offc -= end - start;
360
361 start = end;
362 end += max;
363 chop = 1;
364 }
365 else if (ch == ' ' || ch == '-') {
366 end = i + 1;
367 chop = 0;
368 if (i >= cursin) {
369 return;
370 }
371 }
372 i += columns;
373 }
374 }
375 }
376
text_get_char_pos(const SpaceText * st,const char * line,int cur)377 int text_get_char_pos(const SpaceText *st, const char *line, int cur)
378 {
379 int a = 0, i;
380
381 for (i = 0; i < cur && line[i]; i += BLI_str_utf8_size_safe(line + i)) {
382 if (line[i] == '\t') {
383 a += st->tabnumber - a % st->tabnumber;
384 }
385 else {
386 a += BLI_str_utf8_char_width_safe(line + i);
387 }
388 }
389 return a;
390 }
391
txt_utf8_forward_columns(const char * str,int columns,int * padding)392 static const char *txt_utf8_forward_columns(const char *str, int columns, int *padding)
393 {
394 int col;
395 const char *p = str;
396 while (*p) {
397 col = BLI_str_utf8_char_width(p);
398 if (columns - col < 0) {
399 break;
400 }
401 columns -= col;
402 p += BLI_str_utf8_size_safe(p);
403 if (columns == 0) {
404 break;
405 }
406 }
407 if (padding) {
408 *padding = *p ? columns : 0;
409 }
410 return p;
411 }
412
text_draw_wrapped(const SpaceText * st,const TextDrawContext * tdc,const char * str,int x,int y,int w,const char * format,int skip)413 static int text_draw_wrapped(const SpaceText *st,
414 const TextDrawContext *tdc,
415 const char *str,
416 int x,
417 int y,
418 int w,
419 const char *format,
420 int skip)
421 {
422 const bool use_syntax = (tdc->syntax_highlight && format);
423 FlattenString fs;
424 int basex, lines;
425 int i, wrap, end, max, columns, padding; /* column */
426 /* warning, only valid when 'use_syntax' is set */
427 int a, fstart, fpos; /* utf8 chars */
428 int mi, ma, mstart, mend; /* mem */
429 char fmt_prev = 0xff;
430 /* don't draw lines below this */
431 const int clip_min_y = -(int)(st->runtime.lheight_px - 1);
432
433 flatten_string(st, &fs, str);
434 str = fs.buf;
435 max = w / st->runtime.cwidth_px;
436 if (max < 8) {
437 max = 8;
438 }
439 basex = x;
440 lines = 1;
441
442 fpos = fstart = 0;
443 mstart = 0;
444 mend = txt_utf8_forward_columns(str, max, &padding) - str;
445 end = wrap = max - padding;
446
447 for (i = 0, mi = 0; str[mi]; i += columns, mi += BLI_str_utf8_size_safe(str + mi)) {
448 columns = BLI_str_utf8_char_width_safe(str + mi);
449 if (i + columns > end) {
450 /* skip hidden part of line */
451 if (skip) {
452 skip--;
453 if (use_syntax) {
454 /* currently fpos only used when formatting */
455 fpos += BLI_strnlen_utf8(str + mstart, mend - mstart);
456 }
457 fstart = fpos;
458 mstart = mend;
459 mend = txt_utf8_forward_columns(str + mend, max, &padding) - str;
460 end = (wrap += max - padding);
461 continue;
462 }
463
464 /* Draw the visible portion of text on the overshot line */
465 for (a = fstart, ma = mstart; ma < mend; a++, ma += BLI_str_utf8_size_safe(str + ma)) {
466 if (use_syntax) {
467 if (fmt_prev != format[a]) {
468 format_draw_color(tdc, fmt_prev = format[a]);
469 }
470 }
471 x += text_font_draw_character_utf8(tdc, x, y, str + ma);
472 fpos++;
473 }
474 y -= TXT_LINE_HEIGHT(st);
475 x = basex;
476 lines++;
477 fstart = fpos;
478 mstart = mend;
479 mend = txt_utf8_forward_columns(str + mend, max, &padding) - str;
480 end = (wrap += max - padding);
481
482 if (y <= clip_min_y) {
483 break;
484 }
485 }
486 else if (str[mi] == ' ' || str[mi] == '-') {
487 wrap = i + 1;
488 mend = mi + 1;
489 }
490 }
491
492 /* Draw the remaining text */
493 for (a = fstart, ma = mstart; str[ma] && y > clip_min_y;
494 a++, ma += BLI_str_utf8_size_safe(str + ma)) {
495 if (use_syntax) {
496 if (fmt_prev != format[a]) {
497 format_draw_color(tdc, fmt_prev = format[a]);
498 }
499 }
500
501 x += text_font_draw_character_utf8(tdc, x, y, str + ma);
502 }
503
504 flatten_string_free(&fs);
505
506 return lines;
507 }
508
text_draw(const SpaceText * st,const TextDrawContext * tdc,char * str,int cshift,int maxwidth,int x,int y,const char * format)509 static void text_draw(const SpaceText *st,
510 const TextDrawContext *tdc,
511 char *str,
512 int cshift,
513 int maxwidth,
514 int x,
515 int y,
516 const char *format)
517 {
518 const bool use_syntax = (tdc->syntax_highlight && format);
519 FlattenString fs;
520 int columns, size, n, w = 0, padding, amount = 0;
521 const char *in = NULL;
522
523 for (n = flatten_string(st, &fs, str), str = fs.buf; n > 0; n--) {
524 columns = BLI_str_utf8_char_width_safe(str);
525 size = BLI_str_utf8_size_safe(str);
526
527 if (!in) {
528 if (w >= cshift) {
529 padding = w - cshift;
530 in = str;
531 }
532 else if (format) {
533 format++;
534 }
535 }
536 if (in) {
537 if (maxwidth && w + columns > cshift + maxwidth) {
538 break;
539 }
540 amount++;
541 }
542
543 w += columns;
544 str += size;
545 }
546 if (!in) {
547 flatten_string_free(&fs);
548 return; /* String is shorter than shift or ends with a padding */
549 }
550
551 x += tdc->cwidth_px * padding;
552
553 if (use_syntax) {
554 int a, str_shift = 0;
555 char fmt_prev = 0xff;
556
557 for (a = 0; a < amount; a++) {
558 if (format[a] != fmt_prev) {
559 format_draw_color(tdc, fmt_prev = format[a]);
560 }
561 x += text_font_draw_character_utf8(tdc, x, y, in + str_shift);
562 str_shift += BLI_str_utf8_size_safe(in + str_shift);
563 }
564 }
565 else {
566 text_font_draw(tdc, x, y, in);
567 }
568
569 flatten_string_free(&fs);
570 }
571
572 /************************ cache utilities *****************************/
573
574 typedef struct DrawCache {
575 int *line_height;
576 int total_lines, nlines;
577
578 /* this is needed to check cache relevance */
579 int winx, wordwrap, showlinenrs, tabnumber;
580 short lheight;
581 char cwidth_px;
582 char text_id[MAX_ID_NAME];
583
584 /* for partial lines recalculation */
585 short update_flag;
586 int valid_head, valid_tail; /* amount of unchanged lines */
587 } DrawCache;
588
text_drawcache_init(SpaceText * st)589 static void text_drawcache_init(SpaceText *st)
590 {
591 DrawCache *drawcache = MEM_callocN(sizeof(DrawCache), "text draw cache");
592
593 drawcache->winx = -1;
594 drawcache->nlines = BLI_listbase_count(&st->text->lines);
595 drawcache->text_id[0] = '\0';
596
597 st->runtime.drawcache = drawcache;
598 }
599
text_update_drawcache(SpaceText * st,ARegion * region)600 static void text_update_drawcache(SpaceText *st, ARegion *region)
601 {
602 DrawCache *drawcache;
603 int full_update = 0, nlines = 0;
604 Text *txt = st->text;
605
606 if (st->runtime.drawcache == NULL) {
607 text_drawcache_init(st);
608 }
609
610 text_update_character_width(st);
611
612 drawcache = st->runtime.drawcache;
613 nlines = drawcache->nlines;
614
615 /* check if full cache update is needed */
616
617 /* area was resized */
618 full_update |= drawcache->winx != region->winx;
619 /* word-wrapping option was toggled */
620 full_update |= drawcache->wordwrap != st->wordwrap;
621 /* word-wrapping option was toggled */
622 full_update |= drawcache->showlinenrs != st->showlinenrs;
623 /* word-wrapping option was toggled */
624 full_update |= drawcache->tabnumber != st->tabnumber;
625 /* word-wrapping option was toggled */
626 full_update |= drawcache->lheight != st->runtime.lheight_px;
627 /* word-wrapping option was toggled */
628 full_update |= drawcache->cwidth_px != st->runtime.cwidth_px;
629 /* text datablock was changed */
630 full_update |= !STREQLEN(drawcache->text_id, txt->id.name, MAX_ID_NAME);
631
632 if (st->wordwrap) {
633 /* update line heights */
634 if (full_update || !drawcache->line_height) {
635 drawcache->valid_head = 0;
636 drawcache->valid_tail = 0;
637 drawcache->update_flag = 1;
638 }
639
640 if (drawcache->update_flag) {
641 TextLine *line = st->text->lines.first;
642 int lineno = 0, size, lines_count;
643 int *fp = drawcache->line_height, *new_tail, *old_tail;
644
645 nlines = BLI_listbase_count(&txt->lines);
646 size = sizeof(int) * nlines;
647
648 if (fp) {
649 fp = MEM_reallocN(fp, size);
650 }
651 else {
652 fp = MEM_callocN(size, "text drawcache line_height");
653 }
654
655 drawcache->valid_tail = drawcache->valid_head = 0;
656 old_tail = fp + drawcache->nlines - drawcache->valid_tail;
657 new_tail = fp + nlines - drawcache->valid_tail;
658 memmove(new_tail, old_tail, drawcache->valid_tail);
659
660 drawcache->total_lines = 0;
661
662 if (st->showlinenrs) {
663 st->runtime.line_number_display_digits = integer_digits_i(nlines);
664 }
665
666 while (line) {
667 if (drawcache->valid_head) { /* we're inside valid head lines */
668 lines_count = fp[lineno];
669 drawcache->valid_head--;
670 }
671 else if (lineno > new_tail - fp) { /* we-re inside valid tail lines */
672 lines_count = fp[lineno];
673 }
674 else {
675 lines_count = text_get_visible_lines(st, region, line->line);
676 }
677
678 fp[lineno] = lines_count;
679
680 line = line->next;
681 lineno++;
682 drawcache->total_lines += lines_count;
683 }
684
685 drawcache->line_height = fp;
686 }
687 }
688 else {
689 if (drawcache->line_height) {
690 MEM_freeN(drawcache->line_height);
691 drawcache->line_height = NULL;
692 }
693
694 if (full_update || drawcache->update_flag) {
695 nlines = BLI_listbase_count(&txt->lines);
696
697 if (st->showlinenrs) {
698 st->runtime.line_number_display_digits = integer_digits_i(nlines);
699 }
700 }
701
702 drawcache->total_lines = nlines;
703 }
704
705 drawcache->nlines = nlines;
706
707 /* store settings */
708 drawcache->winx = region->winx;
709 drawcache->wordwrap = st->wordwrap;
710 drawcache->lheight = st->runtime.lheight_px;
711 drawcache->cwidth_px = st->runtime.cwidth_px;
712 drawcache->showlinenrs = st->showlinenrs;
713 drawcache->tabnumber = st->tabnumber;
714
715 strncpy(drawcache->text_id, txt->id.name, MAX_ID_NAME);
716
717 /* clear update flag */
718 drawcache->update_flag = 0;
719 drawcache->valid_head = 0;
720 drawcache->valid_tail = 0;
721 }
722
text_drawcache_tag_update(SpaceText * st,int full)723 void text_drawcache_tag_update(SpaceText *st, int full)
724 {
725 /* this happens if text editor ops are caled from python */
726 if (st == NULL) {
727 return;
728 }
729
730 if (st->runtime.drawcache != NULL) {
731 DrawCache *drawcache = st->runtime.drawcache;
732 Text *txt = st->text;
733
734 if (drawcache->update_flag) {
735 /* happens when tagging update from space listener */
736 /* should do nothing to prevent locally tagged cache be fully recalculated */
737 return;
738 }
739
740 if (!full) {
741 int sellno = BLI_findindex(&txt->lines, txt->sell);
742 int curlno = BLI_findindex(&txt->lines, txt->curl);
743
744 if (curlno < sellno) {
745 drawcache->valid_head = curlno;
746 drawcache->valid_tail = drawcache->nlines - sellno - 1;
747 }
748 else {
749 drawcache->valid_head = sellno;
750 drawcache->valid_tail = drawcache->nlines - curlno - 1;
751 }
752
753 /* quick cache recalculation is also used in delete operator,
754 * which could merge lines which are adjacent to current selection lines
755 * expand recalculate area to this lines */
756 if (drawcache->valid_head > 0) {
757 drawcache->valid_head--;
758 }
759 if (drawcache->valid_tail > 0) {
760 drawcache->valid_tail--;
761 }
762 }
763 else {
764 drawcache->valid_head = 0;
765 drawcache->valid_tail = 0;
766 }
767
768 drawcache->update_flag = 1;
769 }
770 }
771
text_free_caches(SpaceText * st)772 void text_free_caches(SpaceText *st)
773 {
774 DrawCache *drawcache = st->runtime.drawcache;
775
776 if (drawcache) {
777 if (drawcache->line_height) {
778 MEM_freeN(drawcache->line_height);
779 }
780
781 MEM_freeN(drawcache);
782 }
783 }
784
785 /************************ word-wrap utilities *****************************/
786
787 /* cache should be updated in caller */
text_get_visible_lines_no(const SpaceText * st,int lineno)788 static int text_get_visible_lines_no(const SpaceText *st, int lineno)
789 {
790 const DrawCache *drawcache = st->runtime.drawcache;
791
792 return drawcache->line_height[lineno];
793 }
794
text_get_visible_lines(const SpaceText * st,ARegion * region,const char * str)795 int text_get_visible_lines(const SpaceText *st, ARegion *region, const char *str)
796 {
797 int i, j, start, end, max, lines, chars;
798 char ch;
799
800 max = wrap_width(st, region);
801 lines = 1;
802 start = 0;
803 end = max;
804 for (i = 0, j = 0; str[j]; j += BLI_str_utf8_size_safe(str + j)) {
805 int columns = BLI_str_utf8_char_width_safe(str + j); /* = 1 for tab */
806
807 /* Mimic replacement of tabs */
808 ch = str[j];
809 if (ch == '\t') {
810 chars = st->tabnumber - i % st->tabnumber;
811 ch = ' ';
812 }
813 else {
814 chars = 1;
815 }
816
817 while (chars--) {
818 if (i + columns - start > max) {
819 lines++;
820 start = MIN2(end, i);
821 end += max;
822 }
823 else if (ch == ' ' || ch == '-') {
824 end = i + 1;
825 }
826
827 i += columns;
828 }
829 }
830
831 return lines;
832 }
833
text_get_span_wrap(const SpaceText * st,ARegion * region,TextLine * from,TextLine * to)834 int text_get_span_wrap(const SpaceText *st, ARegion *region, TextLine *from, TextLine *to)
835 {
836 if (st->wordwrap) {
837 int ret = 0;
838 TextLine *tmp = from;
839
840 /* Look forwards */
841 while (tmp) {
842 if (tmp == to) {
843 return ret;
844 }
845 ret += text_get_visible_lines(st, region, tmp->line);
846 tmp = tmp->next;
847 }
848
849 return ret;
850 }
851 return txt_get_span(from, to);
852 }
853
text_get_total_lines(SpaceText * st,ARegion * region)854 int text_get_total_lines(SpaceText *st, ARegion *region)
855 {
856 DrawCache *drawcache;
857
858 text_update_drawcache(st, region);
859 drawcache = st->runtime.drawcache;
860
861 return drawcache->total_lines;
862 }
863
864 /************************ draw scrollbar *****************************/
865
calc_text_rcts(SpaceText * st,ARegion * region,rcti * scroll,rcti * back)866 static void calc_text_rcts(SpaceText *st, ARegion *region, rcti *scroll, rcti *back)
867 {
868 int lhlstart, lhlend, ltexth, sell_off, curl_off;
869 short barheight, barstart, hlstart, hlend, blank_lines;
870 short pix_available, pix_top_margin, pix_bottom_margin, pix_bardiff;
871
872 pix_top_margin = (0.4 * U.widget_unit);
873 pix_bottom_margin = (0.4 * U.widget_unit);
874 pix_available = region->winy - pix_top_margin - pix_bottom_margin;
875 ltexth = text_get_total_lines(st, region);
876 blank_lines = st->runtime.viewlines / 2;
877
878 /* nicer code: use scroll rect for entire bar */
879 back->xmin = region->winx - (0.6 * U.widget_unit);
880 back->xmax = region->winx;
881 back->ymin = 0;
882 back->ymax = region->winy;
883
884 scroll->xmax = region->winx - (0.2 * U.widget_unit);
885 scroll->xmin = scroll->xmax - (0.4 * U.widget_unit);
886 scroll->ymin = pix_top_margin;
887 scroll->ymax = pix_available;
888
889 /* when re-sizing a view-port with the bar at the bottom to a greater height
890 * more blank lines will be added */
891 if (ltexth + blank_lines < st->top + st->runtime.viewlines) {
892 blank_lines = st->top + st->runtime.viewlines - ltexth;
893 }
894
895 ltexth += blank_lines;
896
897 barheight = (ltexth > 0) ? (st->runtime.viewlines * pix_available) / ltexth : 0;
898 pix_bardiff = 0;
899 if (barheight < 20) {
900 pix_bardiff = 20 - barheight; /* take into account the now non-linear sizing of the bar */
901 barheight = 20;
902 }
903 barstart = (ltexth > 0) ? ((pix_available - pix_bardiff) * st->top) / ltexth : 0;
904
905 st->runtime.scroll_region_handle = *scroll;
906 st->runtime.scroll_region_handle.ymax -= barstart;
907 st->runtime.scroll_region_handle.ymin = st->runtime.scroll_region_handle.ymax - barheight;
908
909 CLAMP(st->runtime.scroll_region_handle.ymin, pix_bottom_margin, region->winy - pix_top_margin);
910 CLAMP(st->runtime.scroll_region_handle.ymax, pix_bottom_margin, region->winy - pix_top_margin);
911
912 st->runtime.scroll_px_per_line = (pix_available > 0) ? (float)ltexth / pix_available : 0;
913 if (st->runtime.scroll_px_per_line < 0.1f) {
914 st->runtime.scroll_px_per_line = 0.1f;
915 }
916
917 curl_off = text_get_span_wrap(st, region, st->text->lines.first, st->text->curl);
918 sell_off = text_get_span_wrap(st, region, st->text->lines.first, st->text->sell);
919 lhlstart = MIN2(curl_off, sell_off);
920 lhlend = MAX2(curl_off, sell_off);
921
922 if (ltexth > 0) {
923 hlstart = (lhlstart * pix_available) / ltexth;
924 hlend = (lhlend * pix_available) / ltexth;
925
926 /* the scrollbar is non-linear sized */
927 if (pix_bardiff > 0) {
928 /* the start of the highlight is in the current viewport */
929 if (st->runtime.viewlines && lhlstart >= st->top &&
930 lhlstart <= st->top + st->runtime.viewlines) {
931 /* speed the progresion of the start of the highlight through the scrollbar */
932 hlstart = (((pix_available - pix_bardiff) * lhlstart) / ltexth) +
933 (pix_bardiff * (lhlstart - st->top) / st->runtime.viewlines);
934 }
935 else if (lhlstart > st->top + st->runtime.viewlines && hlstart < barstart + barheight &&
936 hlstart > barstart) {
937 /* push hl start down */
938 hlstart = barstart + barheight;
939 }
940 else if (lhlend > st->top && lhlstart < st->top && hlstart > barstart) {
941 /*fill out start */
942 hlstart = barstart;
943 }
944
945 if (hlend <= hlstart) {
946 hlend = hlstart + 2;
947 }
948
949 /* the end of the highlight is in the current viewport */
950 if (st->runtime.viewlines && lhlend >= st->top &&
951 lhlend <= st->top + st->runtime.viewlines) {
952 /* speed the progresion of the end of the highlight through the scrollbar */
953 hlend = (((pix_available - pix_bardiff) * lhlend) / ltexth) +
954 (pix_bardiff * (lhlend - st->top) / st->runtime.viewlines);
955 }
956 else if (lhlend < st->top && hlend >= barstart - 2 && hlend < barstart + barheight) {
957 /* push hl end up */
958 hlend = barstart;
959 }
960 else if (lhlend > st->top + st->runtime.viewlines &&
961 lhlstart < st->top + st->runtime.viewlines && hlend < barstart + barheight) {
962 /* fill out end */
963 hlend = barstart + barheight;
964 }
965
966 if (hlend <= hlstart) {
967 hlstart = hlend - 2;
968 }
969 }
970 }
971 else {
972 hlstart = 0;
973 hlend = 0;
974 }
975
976 if (hlend - hlstart < 2) {
977 hlend = hlstart + 2;
978 }
979
980 st->runtime.scroll_region_select = *scroll;
981 st->runtime.scroll_region_select.ymax = region->winy - pix_top_margin - hlstart;
982 st->runtime.scroll_region_select.ymin = region->winy - pix_top_margin - hlend;
983
984 CLAMP(st->runtime.scroll_region_select.ymin, pix_bottom_margin, region->winy - pix_top_margin);
985 CLAMP(st->runtime.scroll_region_select.ymax, pix_bottom_margin, region->winy - pix_top_margin);
986 }
987
draw_textscroll(const SpaceText * st,rcti * scroll,rcti * back)988 static void draw_textscroll(const SpaceText *st, rcti *scroll, rcti *back)
989 {
990 bTheme *btheme = UI_GetTheme();
991 uiWidgetColors wcol = btheme->tui.wcol_scroll;
992 float col[4];
993 float rad;
994
995 /* background so highlights don't go behind the scrollbar */
996 uint pos = GPU_vertformat_attr_add(
997 immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
998 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
999 immUniformThemeColor(TH_BACK);
1000 immRecti(pos, back->xmin, back->ymin, back->xmax, back->ymax);
1001 immUnbindProgram();
1002
1003 UI_draw_widget_scroll(&wcol,
1004 scroll,
1005 &st->runtime.scroll_region_handle,
1006 (st->flags & ST_SCROLL_SELECT) ? UI_SCROLL_PRESSED : 0);
1007
1008 UI_draw_roundbox_corner_set(UI_CNR_ALL);
1009 rad = 0.4f * min_ii(BLI_rcti_size_x(&st->runtime.scroll_region_select),
1010 BLI_rcti_size_y(&st->runtime.scroll_region_select));
1011 UI_GetThemeColor3fv(TH_HILITE, col);
1012 col[3] = 0.18f;
1013 UI_draw_roundbox_aa(true,
1014 st->runtime.scroll_region_select.xmin + 1,
1015 st->runtime.scroll_region_select.ymin,
1016 st->runtime.scroll_region_select.xmax - 1,
1017 st->runtime.scroll_region_select.ymax,
1018 rad,
1019 col);
1020 }
1021
1022 /*********************** draw documentation *******************************/
1023
1024 #if 0
1025 static void draw_documentation(const SpaceText *st, ARegion *region)
1026 {
1027 TextDrawContext tdc = {0};
1028 TextLine *tmp;
1029 char *docs, buf[DOC_WIDTH + 1], *p;
1030 int i, br, lines;
1031 int boxw, boxh, l, x, y /* , top */ /* UNUSED */;
1032
1033 if (!st || !st->text) {
1034 return;
1035 }
1036 if (!texttool_text_is_active(st->text)) {
1037 return;
1038 }
1039
1040 docs = texttool_docs_get();
1041
1042 if (!docs) {
1043 return;
1044 }
1045
1046 text_draw_context_init(st, &tdc);
1047
1048 /* Count the visible lines to the cursor */
1049 for (tmp = st->text->curl, l = -st->top; tmp; tmp = tmp->prev, l++) {
1050 /* pass */
1051 }
1052
1053 if (l < 0) {
1054 return;
1055 }
1056
1057 x = TXT_BODY_LEFT(st) + (st->runtime.cwidth_px * (st->text->curc - st->left));
1058 if (texttool_suggest_first()) {
1059 x += SUGG_LIST_WIDTH * st->runtime.cwidth_px + 50;
1060 }
1061
1062 /* top = */ /* UNUSED */ y = region->winy - st->runtime.lheight_px * l - 2;
1063 boxw = DOC_WIDTH * st->runtime.cwidth_px + 20;
1064 boxh = (DOC_HEIGHT + 1) * TXT_LINE_HEIGHT(st);
1065
1066 /* Draw panel */
1067 uint pos = GPU_vertformat_attr_add(
1068 immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
1069 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1070
1071 immUniformThemeColor(TH_BACK);
1072 immRecti(pos, x, y, x + boxw, y - boxh);
1073 immUniformThemeColor(TH_SHADE1);
1074 immBegin(GPU_PRIM_LINE_LOOP, 4);
1075 immVertex2i(pos, x, y);
1076 immVertex2i(pos, x + boxw, y);
1077 immVertex2i(pos, x + boxw, y - boxh);
1078 immVertex2i(pos, x, y - boxh);
1079 immEnd();
1080 immBegin(GPU_PRIM_LINE_LOOP, 3);
1081 immVertex2i(pos, x + boxw - 10, y - 7);
1082 immVertex2i(pos, x + boxw - 4, y - 7);
1083 immVertex2i(pos, x + boxw - 7, y - 2);
1084 immEnd();
1085 immBegin(GPU_PRIM_LINE_LOOP, 3);
1086 immVertex2i(pos, x + boxw - 10, y - boxh + 7);
1087 immVertex2i(pos, x + boxw - 4, y - boxh + 7);
1088 immVertex2i(pos, x + boxw - 7, y - boxh + 2);
1089 immEnd();
1090
1091 immUnbindProgram();
1092
1093 UI_FontThemeColor(tdc.font_id, TH_TEXT);
1094
1095 i = 0;
1096 br = DOC_WIDTH;
1097 lines = 0; /* XXX -doc_scroll; */
1098 for (p = docs; *p; p++) {
1099 if (*p == '\r' && *(++p) != '\n') {
1100 *(--p) = '\n'; /* Fix line endings */
1101 }
1102 if (*p == ' ' || *p == '\t') {
1103 br = i;
1104 }
1105 else if (*p == '\n') {
1106 buf[i] = '\0';
1107 if (lines >= 0) {
1108 y -= st->runtime.lheight_px;
1109 text_draw(st, &tdc, buf, 0, 0, x + 4, y - 3, NULL);
1110 }
1111 i = 0;
1112 br = DOC_WIDTH;
1113 lines++;
1114 }
1115 buf[i++] = *p;
1116 if (i == DOC_WIDTH) { /* Reached the width, go to last break and wrap there */
1117 buf[br] = '\0';
1118 if (lines >= 0) {
1119 y -= st->runtime.lheight_px;
1120 text_draw(st, &tdc, buf, 0, 0, x + 4, y - 3, NULL);
1121 }
1122 p -= i - br - 1; /* Rewind pointer to last break */
1123 i = 0;
1124 br = DOC_WIDTH;
1125 lines++;
1126 }
1127 if (lines >= DOC_HEIGHT) {
1128 break;
1129 }
1130 }
1131 }
1132 #endif
1133
1134 /*********************** draw suggestion list *******************************/
1135
draw_suggestion_list(const SpaceText * st,const TextDrawContext * tdc,ARegion * region)1136 static void draw_suggestion_list(const SpaceText *st, const TextDrawContext *tdc, ARegion *region)
1137 {
1138 SuggItem *item, *first, *last, *sel;
1139 char str[SUGG_LIST_WIDTH * BLI_UTF8_MAX + 1];
1140 int offl, offc, vcurl, vcurc;
1141 int w, boxw = 0, boxh, i, x, y, *top;
1142 const int lheight = TXT_LINE_HEIGHT(st);
1143 const int margin_x = 2;
1144
1145 if (!st->text) {
1146 return;
1147 }
1148 if (!texttool_text_is_active(st->text)) {
1149 return;
1150 }
1151
1152 first = texttool_suggest_first();
1153 last = texttool_suggest_last();
1154
1155 if (!first || !last) {
1156 return;
1157 }
1158
1159 text_pop_suggest_list();
1160 sel = texttool_suggest_selected();
1161 top = texttool_suggest_top();
1162
1163 wrap_offset(st, region, st->text->curl, st->text->curc, &offl, &offc);
1164 vcurl = txt_get_span(st->text->lines.first, st->text->curl) - st->top + offl;
1165 vcurc = text_get_char_pos(st, st->text->curl->line, st->text->curc) - st->left + offc;
1166
1167 x = TXT_BODY_LEFT(st) + (vcurc * st->runtime.cwidth_px);
1168 y = region->winy - (vcurl + 1) * lheight - 2;
1169
1170 /* offset back so the start of the text lines up with the suggestions,
1171 * not essential but makes suggestions easier to follow */
1172 x -= st->runtime.cwidth_px *
1173 (st->text->curc - text_find_identifier_start(st->text->curl->line, st->text->curc));
1174
1175 boxw = SUGG_LIST_WIDTH * st->runtime.cwidth_px + 20;
1176 boxh = SUGG_LIST_SIZE * lheight + 8;
1177
1178 if (x + boxw > region->winx) {
1179 x = MAX2(0, region->winx - boxw);
1180 }
1181
1182 /* not needed but stands out nicer */
1183 UI_draw_box_shadow(220, x, y - boxh, x + boxw, y);
1184
1185 uint pos = GPU_vertformat_attr_add(
1186 immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
1187 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1188
1189 immUniformThemeColor(TH_SHADE1);
1190 immRecti(pos, x - 1, y + 1, x + boxw + 1, y - boxh - 1);
1191 immUniformThemeColorShade(TH_BACK, 16);
1192 immRecti(pos, x, y, x + boxw, y - boxh);
1193
1194 immUnbindProgram();
1195
1196 /* Set the top 'item' of the visible list */
1197 for (i = 0, item = first; i < *top && item->next; i++, item = item->next) {
1198 /* pass */
1199 }
1200
1201 for (i = 0; i < SUGG_LIST_SIZE && item; i++, item = item->next) {
1202 int len = txt_utf8_forward_columns(item->name, SUGG_LIST_WIDTH, NULL) - item->name;
1203
1204 y -= lheight;
1205
1206 BLI_strncpy(str, item->name, len + 1);
1207
1208 w = st->runtime.cwidth_px * text_get_char_pos(st, str, len);
1209
1210 if (item == sel) {
1211 uint posi = GPU_vertformat_attr_add(
1212 immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
1213 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1214
1215 immUniformThemeColor(TH_SHADE2);
1216 immRecti(posi, x + margin_x, y - 3, x + margin_x + w, y + lheight - 3);
1217
1218 immUnbindProgram();
1219 }
1220
1221 format_draw_color(tdc, item->type);
1222 text_draw(st, tdc, str, 0, 0, x + margin_x, y - 1, NULL);
1223
1224 if (item == last) {
1225 break;
1226 }
1227 }
1228 }
1229
1230 /*********************** draw cursor ************************/
1231
draw_text_decoration(SpaceText * st,ARegion * region)1232 static void draw_text_decoration(SpaceText *st, ARegion *region)
1233 {
1234 Text *text = st->text;
1235 int vcurl, vcurc, vsell, vselc, hidden = 0;
1236 int x, y, w, i;
1237 int offl, offc;
1238 const int lheight = TXT_LINE_HEIGHT(st);
1239
1240 /* Convert to view space character coordinates to determine if cursor is hidden */
1241 wrap_offset(st, region, text->sell, text->selc, &offl, &offc);
1242 vsell = txt_get_span(text->lines.first, text->sell) - st->top + offl;
1243 vselc = text_get_char_pos(st, text->sell->line, text->selc) - st->left + offc;
1244
1245 if (vselc < 0) {
1246 vselc = 0;
1247 hidden = 1;
1248 }
1249
1250 if (text->curl == text->sell && text->curc == text->selc && !st->line_hlight && hidden) {
1251 /* Nothing to draw here */
1252 return;
1253 }
1254
1255 uint pos = GPU_vertformat_attr_add(
1256 immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
1257 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1258
1259 /* Draw the selection */
1260 if (text->curl != text->sell || text->curc != text->selc) {
1261 /* Convert all to view space character coordinates */
1262 wrap_offset(st, region, text->curl, text->curc, &offl, &offc);
1263 vcurl = txt_get_span(text->lines.first, text->curl) - st->top + offl;
1264 vcurc = text_get_char_pos(st, text->curl->line, text->curc) - st->left + offc;
1265
1266 if (vcurc < 0) {
1267 vcurc = 0;
1268 }
1269
1270 immUniformThemeColor(TH_SHADE2);
1271
1272 x = TXT_BODY_LEFT(st);
1273 y = region->winy;
1274 if (st->flags & ST_SCROLL_SELECT) {
1275 y += st->runtime.scroll_ofs_px[1];
1276 }
1277
1278 if (vcurl == vsell) {
1279 y -= vcurl * lheight;
1280
1281 if (vcurc < vselc) {
1282 immRecti(pos,
1283 x + vcurc * st->runtime.cwidth_px,
1284 y,
1285 x + vselc * st->runtime.cwidth_px,
1286 y - lheight);
1287 }
1288 else {
1289 immRecti(pos,
1290 x + vselc * st->runtime.cwidth_px,
1291 y,
1292 x + vcurc * st->runtime.cwidth_px,
1293 y - lheight);
1294 }
1295 }
1296 else {
1297 int froml, fromc, tol, toc;
1298
1299 if (vcurl < vsell) {
1300 froml = vcurl;
1301 tol = vsell;
1302 fromc = vcurc;
1303 toc = vselc;
1304 }
1305 else {
1306 froml = vsell;
1307 tol = vcurl;
1308 fromc = vselc;
1309 toc = vcurc;
1310 }
1311
1312 y -= froml * lheight;
1313
1314 immRecti(pos, x + fromc * st->runtime.cwidth_px - U.pixelsize, y, region->winx, y - lheight);
1315 y -= lheight;
1316
1317 for (i = froml + 1; i < tol; i++) {
1318 immRecti(pos, x - U.pixelsize, y, region->winx, y - lheight);
1319 y -= lheight;
1320 }
1321
1322 if (x + toc * st->runtime.cwidth_px > x) {
1323 immRecti(pos, x - U.pixelsize, y, x + toc * st->runtime.cwidth_px, y - lheight);
1324 }
1325 y -= lheight;
1326 }
1327 }
1328
1329 if (st->line_hlight) {
1330 int y1, y2;
1331
1332 if (st->wordwrap) {
1333 int visible_lines = text_get_visible_lines(st, region, text->sell->line);
1334
1335 wrap_offset_in_line(st, region, text->sell, text->selc, &offl, &offc);
1336
1337 y1 = region->winy - (vsell - offl) * lheight;
1338 if (st->flags & ST_SCROLL_SELECT) {
1339 y1 += st->runtime.scroll_ofs_px[1];
1340 }
1341 y2 = y1 - (lheight * visible_lines);
1342 }
1343 else {
1344 y1 = region->winy - vsell * lheight;
1345 if (st->flags & ST_SCROLL_SELECT) {
1346 y1 += st->runtime.scroll_ofs_px[1];
1347 }
1348 y2 = y1 - (lheight);
1349 }
1350
1351 if (!(y1 < 0 || y2 > region->winy)) { /* check we need to draw */
1352 float highlight_color[4];
1353 UI_GetThemeColor4fv(TH_TEXT, highlight_color);
1354 highlight_color[3] = 0.1f;
1355 immUniformColor4fv(highlight_color);
1356 GPU_blend(GPU_BLEND_ALPHA);
1357 immRecti(pos, 0, y1, region->winx, y2);
1358 GPU_blend(GPU_BLEND_NONE);
1359 }
1360 }
1361
1362 if (!hidden) {
1363 /* Draw the cursor itself (we draw the sel. cursor as this is the leading edge) */
1364 x = TXT_BODY_LEFT(st) + (vselc * st->runtime.cwidth_px);
1365 y = region->winy - vsell * lheight;
1366 if (st->flags & ST_SCROLL_SELECT) {
1367 y += st->runtime.scroll_ofs_px[1];
1368 }
1369
1370 immUniformThemeColor(TH_HILITE);
1371
1372 if (st->overwrite) {
1373 char ch = text->sell->line[text->selc];
1374
1375 y += TXT_LINE_SPACING(st);
1376 w = st->runtime.cwidth_px;
1377 if (ch == '\t') {
1378 w *= st->tabnumber - (vselc + st->left) % st->tabnumber;
1379 }
1380
1381 immRecti(
1382 pos, x, y - lheight - U.pixelsize, x + w + U.pixelsize, y - lheight - (3 * U.pixelsize));
1383 }
1384 else {
1385 immRecti(pos, x - U.pixelsize, y, x + U.pixelsize, y - lheight);
1386 }
1387 }
1388
1389 immUnbindProgram();
1390 }
1391
1392 /******************* draw matching brackets *********************/
1393
draw_brackets(const SpaceText * st,const TextDrawContext * tdc,ARegion * region)1394 static void draw_brackets(const SpaceText *st, const TextDrawContext *tdc, ARegion *region)
1395 {
1396 TextLine *startl, *endl, *linep;
1397 Text *text = st->text;
1398 int b, fc, find, stack, viewc, viewl, offl, offc, x, y;
1399 int startc, endc, c;
1400
1401 char ch;
1402
1403 /* syntax_highlight must be on or else the format string will be null */
1404 if (!text->curl || !tdc->syntax_highlight) {
1405 return;
1406 }
1407
1408 startl = text->curl;
1409 startc = text->curc;
1410 b = text_check_bracket(startl->line[startc]);
1411 if (b == 0 && startc > 0) {
1412 b = text_check_bracket(startl->line[--startc]);
1413 }
1414 if (b == 0) {
1415 return;
1416 }
1417
1418 linep = startl;
1419 c = startc;
1420 fc = BLI_str_utf8_offset_to_index(linep->line, startc);
1421 endl = NULL;
1422 endc = -1;
1423 find = -b;
1424 stack = 0;
1425
1426 /* Don't highlight backets if syntax HL is off or bracket in string or comment. */
1427 if (!linep->format || linep->format[fc] == FMT_TYPE_STRING ||
1428 linep->format[fc] == FMT_TYPE_COMMENT) {
1429 return;
1430 }
1431
1432 if (b > 0) {
1433 /* opening bracket, search forward for close */
1434 fc++;
1435 c += BLI_str_utf8_size_safe(linep->line + c);
1436 while (linep) {
1437 while (c < linep->len) {
1438 if (linep->format && linep->format[fc] != FMT_TYPE_STRING &&
1439 linep->format[fc] != FMT_TYPE_COMMENT) {
1440 b = text_check_bracket(linep->line[c]);
1441 if (b == find) {
1442 if (stack == 0) {
1443 endl = linep;
1444 endc = c;
1445 break;
1446 }
1447 stack--;
1448 }
1449 else if (b == -find) {
1450 stack++;
1451 }
1452 }
1453 fc++;
1454 c += BLI_str_utf8_size_safe(linep->line + c);
1455 }
1456 if (endl) {
1457 break;
1458 }
1459 linep = linep->next;
1460 c = 0;
1461 fc = 0;
1462 }
1463 }
1464 else {
1465 /* closing bracket, search backward for open */
1466 fc--;
1467 if (c > 0) {
1468 c -= linep->line + c - BLI_str_prev_char_utf8(linep->line + c);
1469 }
1470 while (linep) {
1471 while (fc >= 0) {
1472 if (linep->format && linep->format[fc] != FMT_TYPE_STRING &&
1473 linep->format[fc] != FMT_TYPE_COMMENT) {
1474 b = text_check_bracket(linep->line[c]);
1475 if (b == find) {
1476 if (stack == 0) {
1477 endl = linep;
1478 endc = c;
1479 break;
1480 }
1481 stack--;
1482 }
1483 else if (b == -find) {
1484 stack++;
1485 }
1486 }
1487 fc--;
1488 if (c > 0) {
1489 c -= linep->line + c - BLI_str_prev_char_utf8(linep->line + c);
1490 }
1491 }
1492 if (endl) {
1493 break;
1494 }
1495 linep = linep->prev;
1496 if (linep) {
1497 if (linep->format) {
1498 fc = strlen(linep->format) - 1;
1499 }
1500 else {
1501 fc = -1;
1502 }
1503 if (linep->len) {
1504 c = BLI_str_prev_char_utf8(linep->line + linep->len) - linep->line;
1505 }
1506 else {
1507 fc = -1;
1508 }
1509 }
1510 }
1511 }
1512
1513 if (!endl || endc == -1) {
1514 return;
1515 }
1516
1517 UI_FontThemeColor(tdc->font_id, TH_HILITE);
1518 x = TXT_BODY_LEFT(st);
1519 y = region->winy - st->runtime.lheight_px;
1520 if (st->flags & ST_SCROLL_SELECT) {
1521 y += st->runtime.scroll_ofs_px[1];
1522 }
1523
1524 /* draw opening bracket */
1525 ch = startl->line[startc];
1526 wrap_offset(st, region, startl, startc, &offl, &offc);
1527 viewc = text_get_char_pos(st, startl->line, startc) - st->left + offc;
1528
1529 if (viewc >= 0) {
1530 viewl = txt_get_span(text->lines.first, startl) - st->top + offl;
1531
1532 text_font_draw_character(
1533 tdc, x + viewc * st->runtime.cwidth_px, y - viewl * TXT_LINE_HEIGHT(st), ch);
1534 text_font_draw_character(
1535 tdc, x + viewc * st->runtime.cwidth_px + 1, y - viewl * TXT_LINE_HEIGHT(st), ch);
1536 }
1537
1538 /* draw closing bracket */
1539 ch = endl->line[endc];
1540 wrap_offset(st, region, endl, endc, &offl, &offc);
1541 viewc = text_get_char_pos(st, endl->line, endc) - st->left + offc;
1542
1543 if (viewc >= 0) {
1544 viewl = txt_get_span(text->lines.first, endl) - st->top + offl;
1545
1546 text_font_draw_character(
1547 tdc, x + viewc * st->runtime.cwidth_px, y - viewl * TXT_LINE_HEIGHT(st), ch);
1548 text_font_draw_character(
1549 tdc, x + viewc * st->runtime.cwidth_px + 1, y - viewl * TXT_LINE_HEIGHT(st), ch);
1550 }
1551 }
1552
1553 /*********************** main region drawing *************************/
1554
draw_text_main(SpaceText * st,ARegion * region)1555 void draw_text_main(SpaceText *st, ARegion *region)
1556 {
1557 TextDrawContext tdc = {0};
1558 Text *text = st->text;
1559 TextFormatType *tft;
1560 TextLine *tmp;
1561 rcti scroll, back;
1562 char linenr[12];
1563 int i, x, y, winx, linecount = 0, lineno = 0;
1564 int wraplinecount = 0, wrap_skip = 0;
1565 int margin_column_x;
1566
1567 /* if no text, nothing to do */
1568 if (!text) {
1569 return;
1570 }
1571
1572 /* dpi controlled line height and font size */
1573 st->runtime.lheight_px = (U.widget_unit * st->lheight) / 20;
1574
1575 /* don't draw lines below this */
1576 const int clip_min_y = -(int)(st->runtime.lheight_px - 1);
1577
1578 st->runtime.viewlines = (st->runtime.lheight_px) ?
1579 (int)(region->winy - clip_min_y) / TXT_LINE_HEIGHT(st) :
1580 0;
1581
1582 text_draw_context_init(st, &tdc);
1583
1584 text_update_drawcache(st, region);
1585
1586 /* make sure all the positional pointers exist */
1587 if (!text->curl || !text->sell || !text->lines.first || !text->lines.last) {
1588 txt_clean_text(text);
1589 }
1590
1591 /* update rects for scroll */
1592 calc_text_rcts(st, region, &scroll, &back); /* scroll will hold the entire bar size */
1593
1594 /* update syntax formatting if needed */
1595 tft = ED_text_format_get(text);
1596 tmp = text->lines.first;
1597 lineno = 0;
1598 for (i = 0; i < st->top && tmp; i++) {
1599 if (tdc.syntax_highlight && !tmp->format) {
1600 tft->format_line(st, tmp, false);
1601 }
1602
1603 if (st->wordwrap) {
1604 int lines = text_get_visible_lines_no(st, lineno);
1605
1606 if (wraplinecount + lines > st->top) {
1607 wrap_skip = st->top - wraplinecount;
1608 break;
1609 }
1610
1611 wraplinecount += lines;
1612 tmp = tmp->next;
1613 linecount++;
1614 }
1615 else {
1616 tmp = tmp->next;
1617 linecount++;
1618 }
1619
1620 lineno++;
1621 }
1622
1623 text_font_begin(&tdc);
1624
1625 tdc.cwidth_px = max_ii((int)BLF_fixed_width(tdc.font_id), 1);
1626 st->runtime.cwidth_px = tdc.cwidth_px;
1627
1628 /* draw line numbers background */
1629 if (st->showlinenrs) {
1630 uint pos = GPU_vertformat_attr_add(
1631 immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
1632 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1633 immUniformThemeColor(TH_GRID);
1634 immRecti(pos, 0, 0, TXT_NUMCOL_WIDTH(st), region->winy);
1635 immUnbindProgram();
1636 }
1637 else {
1638 st->runtime.line_number_display_digits = 0; /* not used */
1639 }
1640
1641 x = TXT_BODY_LEFT(st);
1642 y = region->winy - st->runtime.lheight_px;
1643 int viewlines = st->runtime.viewlines;
1644 if (st->flags & ST_SCROLL_SELECT) {
1645 y += st->runtime.scroll_ofs_px[1];
1646 viewlines += 1;
1647 }
1648
1649 winx = region->winx - TXT_SCROLL_WIDTH;
1650
1651 /* draw cursor, margin, selection and highlight */
1652 draw_text_decoration(st, region);
1653
1654 /* draw the text */
1655 UI_FontThemeColor(tdc.font_id, TH_TEXT);
1656
1657 for (i = 0; y > clip_min_y && i < viewlines && tmp; i++, tmp = tmp->next) {
1658 if (tdc.syntax_highlight && !tmp->format) {
1659 tft->format_line(st, tmp, false);
1660 }
1661
1662 if (st->showlinenrs && !wrap_skip) {
1663 /* Draw line number. */
1664 UI_FontThemeColor(tdc.font_id, (tmp == text->curl) ? TH_HILITE : TH_LINENUMBERS);
1665 BLI_snprintf(linenr,
1666 sizeof(linenr),
1667 "%*d",
1668 st->runtime.line_number_display_digits,
1669 i + linecount + 1);
1670 text_font_draw(&tdc, TXT_NUMCOL_PAD * st->runtime.cwidth_px, y, linenr);
1671 /* Change back to text color. */
1672 UI_FontThemeColor(tdc.font_id, TH_TEXT);
1673 }
1674
1675 if (st->wordwrap) {
1676 /* draw word wrapped text */
1677 int lines = text_draw_wrapped(st, &tdc, tmp->line, x, y, winx - x, tmp->format, wrap_skip);
1678 y -= lines * TXT_LINE_HEIGHT(st);
1679 }
1680 else {
1681 /* draw unwrapped text */
1682 text_draw(
1683 st, &tdc, tmp->line, st->left, region->winx / st->runtime.cwidth_px, x, y, tmp->format);
1684 y -= TXT_LINE_HEIGHT(st);
1685 }
1686
1687 wrap_skip = 0;
1688 }
1689
1690 if (st->flags & ST_SHOW_MARGIN) {
1691 margin_column_x = x + st->runtime.cwidth_px * (st->margin_column - st->left);
1692 if (margin_column_x >= x) {
1693 uint pos = GPU_vertformat_attr_add(
1694 immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
1695 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1696 float margin_color[4];
1697 UI_GetThemeColor4fv(TH_TEXT, margin_color);
1698 margin_color[3] = 0.2f;
1699 immUniformColor4fv(margin_color);
1700 GPU_blend(GPU_BLEND_ALPHA);
1701 immRecti(pos, margin_column_x, 0, margin_column_x + U.pixelsize, region->winy);
1702 GPU_blend(GPU_BLEND_NONE);
1703 immUnbindProgram();
1704 }
1705 }
1706
1707 /* draw other stuff */
1708 draw_brackets(st, &tdc, region);
1709 draw_textscroll(st, &scroll, &back);
1710 /* draw_documentation(st, region); - No longer supported */
1711 draw_suggestion_list(st, &tdc, region);
1712
1713 text_font_end(&tdc);
1714 }
1715
1716 /************************** update ***************************/
1717
text_update_character_width(SpaceText * st)1718 void text_update_character_width(SpaceText *st)
1719 {
1720 TextDrawContext tdc = {0};
1721
1722 text_draw_context_init(st, &tdc);
1723
1724 text_font_begin(&tdc);
1725 st->runtime.cwidth_px = BLF_fixed_width(tdc.font_id);
1726 st->runtime.cwidth_px = MAX2(st->runtime.cwidth_px, (char)1);
1727 text_font_end(&tdc);
1728 }
1729
1730 /* Moves the view to the cursor location,
1731 * also used to make sure the view isn't outside the file */
text_scroll_to_cursor(SpaceText * st,ARegion * region,const bool center)1732 void text_scroll_to_cursor(SpaceText *st, ARegion *region, const bool center)
1733 {
1734 Text *text;
1735 int i, x, winx = region->winx;
1736
1737 if (ELEM(NULL, st, st->text, st->text->curl)) {
1738 return;
1739 }
1740
1741 text = st->text;
1742
1743 text_update_character_width(st);
1744
1745 i = txt_get_span(text->lines.first, text->sell);
1746 if (st->wordwrap) {
1747 int offl, offc;
1748 wrap_offset(st, region, text->sell, text->selc, &offl, &offc);
1749 i += offl;
1750 }
1751
1752 if (center) {
1753 if (st->top + st->runtime.viewlines <= i || st->top > i) {
1754 st->top = i - st->runtime.viewlines / 2;
1755 }
1756 }
1757 else {
1758 if (st->top + st->runtime.viewlines <= i) {
1759 st->top = i - (st->runtime.viewlines - 1);
1760 }
1761 else if (st->top > i) {
1762 st->top = i;
1763 }
1764 }
1765
1766 if (st->wordwrap) {
1767 st->left = 0;
1768 }
1769 else {
1770 x = st->runtime.cwidth_px * (text_get_char_pos(st, text->sell->line, text->selc) - st->left);
1771 winx -= TXT_BODY_LEFT(st) + TXT_SCROLL_WIDTH;
1772
1773 if (center) {
1774 if (x <= 0 || x > winx) {
1775 st->left += (x - winx / 2) / st->runtime.cwidth_px;
1776 }
1777 }
1778 else {
1779 if (x <= 0) {
1780 st->left += ((x + 1) / st->runtime.cwidth_px) - 1;
1781 }
1782 else if (x > winx) {
1783 st->left += ((x - (winx + 1)) / st->runtime.cwidth_px) + 1;
1784 }
1785 }
1786 }
1787
1788 if (st->top < 0) {
1789 st->top = 0;
1790 }
1791 if (st->left < 0) {
1792 st->left = 0;
1793 }
1794
1795 st->runtime.scroll_ofs_px[0] = 0;
1796 st->runtime.scroll_ofs_px[1] = 0;
1797 }
1798
1799 /* takes an area instead of a region, use for listeners */
text_scroll_to_cursor__area(SpaceText * st,ScrArea * area,const bool center)1800 void text_scroll_to_cursor__area(SpaceText *st, ScrArea *area, const bool center)
1801 {
1802 ARegion *region;
1803
1804 if (ELEM(NULL, st, st->text, st->text->curl)) {
1805 return;
1806 }
1807
1808 region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
1809
1810 if (region) {
1811 text_scroll_to_cursor(st, region, center);
1812 }
1813 }
1814
text_update_cursor_moved(bContext * C)1815 void text_update_cursor_moved(bContext *C)
1816 {
1817 ScrArea *area = CTX_wm_area(C);
1818 SpaceText *st = CTX_wm_space_text(C);
1819
1820 text_scroll_to_cursor__area(st, area, true);
1821 }
1822
1823 /**
1824 * Takes a cursor (row, character) and returns x,y pixel coords.
1825 */
ED_text_region_location_from_cursor(SpaceText * st,ARegion * region,const int cursor_co[2],int r_pixel_co[2])1826 bool ED_text_region_location_from_cursor(SpaceText *st,
1827 ARegion *region,
1828 const int cursor_co[2],
1829 int r_pixel_co[2])
1830 {
1831 TextLine *line = NULL;
1832
1833 if (!st->text) {
1834 goto error;
1835 }
1836
1837 line = BLI_findlink(&st->text->lines, cursor_co[0]);
1838 if (!line || (cursor_co[1] < 0) || (cursor_co[1] > line->len)) {
1839 goto error;
1840 }
1841 else {
1842 int offl, offc;
1843 int linenr_offset = TXT_BODY_LEFT(st);
1844 /* handle tabs as well! */
1845 int char_pos = text_get_char_pos(st, line->line, cursor_co[1]);
1846
1847 wrap_offset(st, region, line, cursor_co[1], &offl, &offc);
1848 r_pixel_co[0] = (char_pos + offc - st->left) * st->runtime.cwidth_px + linenr_offset;
1849 r_pixel_co[1] = (cursor_co[0] + offl - st->top) * TXT_LINE_HEIGHT(st);
1850 r_pixel_co[1] = (region->winy - (r_pixel_co[1] + (TXT_BODY_LPAD * st->runtime.cwidth_px))) -
1851 st->runtime.lheight_px;
1852 }
1853 return true;
1854
1855 error:
1856 r_pixel_co[0] = r_pixel_co[1] = -1;
1857 return false;
1858 }
1859