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