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