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