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