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