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