2296b27916e537107c5d5da3bbf7de5169233f45
[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(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(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(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(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(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(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(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(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(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(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(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(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(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(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;
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(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(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(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 /* Move pointer to first visible line (top) */
1074 static TextLine *first_visible_line(SpaceText *st, ARegion *ar, int *wrap_top)
1075 {
1076         Text *text = st->text;
1077         TextLine *pline = text->lines.first;
1078         int i = st->top, lineno = 0;
1079
1080         text_update_drawcache(st, ar);
1081
1082         if (wrap_top) *wrap_top = 0;
1083
1084         if (st->wordwrap) {
1085                 while (i > 0 && pline) {
1086                         int lines = text_get_visible_lines_no(st, lineno);
1087
1088                         if (i - lines < 0) {
1089                                 if (wrap_top) *wrap_top = i;
1090                                 break;
1091                         }
1092                         else {
1093                                 pline = pline->next;
1094                                 i -= lines;
1095                                 lineno++;
1096                         }
1097                 }
1098         }
1099         else {
1100                 for (i = st->top; pline->next && i > 0; i--)
1101                         pline = pline->next;
1102         }
1103
1104         return pline;
1105 }
1106
1107 /************************ draw scrollbar *****************************/
1108
1109 static void calc_text_rcts(SpaceText *st, ARegion *ar, rcti *scroll, rcti *back)
1110 {
1111         int lhlstart, lhlend, ltexth, sell_off, curl_off;
1112         short barheight, barstart, hlstart, hlend, blank_lines;
1113         short pix_available, pix_top_margin, pix_bottom_margin, pix_bardiff;
1114
1115         pix_top_margin = 8;
1116         pix_bottom_margin = 4;
1117         pix_available = ar->winy - pix_top_margin - pix_bottom_margin;
1118         ltexth = text_get_total_lines(st, ar);
1119         blank_lines = st->viewlines / 2;
1120         
1121         /* nicer code: use scroll rect for entire bar */
1122         back->xmin = ar->winx - 18;
1123         back->xmax = ar->winx;
1124         back->ymin = 0;
1125         back->ymax = ar->winy;
1126         
1127         scroll->xmin = ar->winx - 17;
1128         scroll->xmax = ar->winx - 5;
1129         scroll->ymin = 4;
1130         scroll->ymax = 4 + pix_available;
1131         
1132         /* when re-sizing a view-port with the bar at the bottom to a greater height more blank lines will be added */
1133         if (ltexth + blank_lines < st->top + st->viewlines) {
1134                 blank_lines = st->top + st->viewlines - ltexth;
1135         }
1136         
1137         ltexth += blank_lines;
1138
1139         barheight = (ltexth > 0) ? (st->viewlines * pix_available) / ltexth : 0;
1140         pix_bardiff = 0;
1141         if (barheight < 20) {
1142                 pix_bardiff = 20 - barheight; /* take into account the now non-linear sizing of the bar */
1143                 barheight = 20;
1144         }
1145         barstart = (ltexth > 0) ? ((pix_available - pix_bardiff) * st->top) / ltexth : 0;
1146
1147         st->txtbar = *scroll;
1148         st->txtbar.ymax -= barstart;
1149         st->txtbar.ymin = st->txtbar.ymax - barheight;
1150
1151         CLAMP(st->txtbar.ymin, pix_bottom_margin, ar->winy - pix_top_margin);
1152         CLAMP(st->txtbar.ymax, pix_bottom_margin, ar->winy - pix_top_margin);
1153
1154         st->pix_per_line = (pix_available > 0) ? (float) ltexth / pix_available : 0;
1155         if (st->pix_per_line < 0.1f) st->pix_per_line = 0.1f;
1156
1157         curl_off = text_get_span_wrap(st, ar, st->text->lines.first, st->text->curl);
1158         sell_off = text_get_span_wrap(st, ar, st->text->lines.first, st->text->sell);
1159         lhlstart = MIN2(curl_off, sell_off);
1160         lhlend = MAX2(curl_off, sell_off);
1161
1162         if (ltexth > 0) {
1163                 hlstart = (lhlstart * pix_available) / ltexth;
1164                 hlend = (lhlend * pix_available) / ltexth;
1165
1166                 /* the scrollbar is non-linear sized */
1167                 if (pix_bardiff > 0) {
1168                         /* the start of the highlight is in the current viewport */
1169                         if (ltexth && st->viewlines && lhlstart >= st->top && lhlstart <= st->top + st->viewlines) {
1170                                 /* speed the progresion of the start of the highlight through the scrollbar */
1171                                 hlstart = ( ( (pix_available - pix_bardiff) * lhlstart) / ltexth) + (pix_bardiff * (lhlstart - st->top) / st->viewlines);
1172                         }
1173                         else if (lhlstart > st->top + st->viewlines && hlstart < barstart + barheight && hlstart > barstart) {
1174                                 /* push hl start down */
1175                                 hlstart = barstart + barheight;
1176                         }
1177                         else if (lhlend > st->top && lhlstart < st->top && hlstart > barstart) {
1178                                 /*fill out start */
1179                                 hlstart = barstart;
1180                         }
1181
1182                         if (hlend <= hlstart) {
1183                                 hlend = hlstart + 2;
1184                         }
1185
1186                         /* the end of the highlight is in the current viewport */
1187                         if (ltexth && st->viewlines && lhlend >= st->top && lhlend <= st->top + st->viewlines) {
1188                                 /* speed the progresion of the end of the highlight through the scrollbar */
1189                                 hlend = (((pix_available - pix_bardiff) * lhlend) / ltexth) + (pix_bardiff * (lhlend - st->top) / st->viewlines);
1190                         }
1191                         else if (lhlend < st->top && hlend >= barstart - 2 && hlend < barstart + barheight) {
1192                                 /* push hl end up */
1193                                 hlend = barstart;
1194                         }
1195                         else if (lhlend > st->top + st->viewlines && lhlstart < st->top + st->viewlines && hlend < barstart + barheight) {
1196                                 /* fill out end */
1197                                 hlend = barstart + barheight;
1198                         }
1199
1200                         if (hlend <= hlstart) {
1201                                 hlstart = hlend - 2;
1202                         }
1203                 }
1204         }
1205         else {
1206                 hlstart = 0;
1207                 hlend = 0;
1208         }
1209
1210         if (hlend - hlstart < 2) {
1211                 hlend = hlstart + 2;
1212         }
1213         
1214         st->txtscroll = *scroll;
1215         st->txtscroll.ymax = ar->winy - pix_top_margin - hlstart;
1216         st->txtscroll.ymin = ar->winy - pix_top_margin - hlend;
1217
1218         CLAMP(st->txtscroll.ymin, pix_bottom_margin, ar->winy - pix_top_margin);
1219         CLAMP(st->txtscroll.ymax, pix_bottom_margin, ar->winy - pix_top_margin);
1220 }
1221
1222 static void draw_textscroll(SpaceText *st, rcti *scroll, rcti *back)
1223 {
1224         bTheme *btheme = UI_GetTheme();
1225         uiWidgetColors wcol = btheme->tui.wcol_scroll;
1226         unsigned char col[4];
1227         float rad;
1228         
1229         UI_ThemeColor(TH_BACK);
1230         glRecti(back->xmin, back->ymin, back->xmax, back->ymax);
1231
1232         uiWidgetScrollDraw(&wcol, scroll, &st->txtbar, (st->flags & ST_SCROLL_SELECT) ? UI_SCROLL_PRESSED : 0);
1233
1234         uiSetRoundBox(UI_CNR_ALL);
1235         rad = 0.4f * mini(BLI_rcti_size_x(&st->txtscroll), BLI_rcti_size_y(&st->txtscroll));
1236         UI_GetThemeColor3ubv(TH_HILITE, col);
1237         col[3] = 48;
1238         glColor4ubv(col);
1239         glEnable(GL_BLEND);
1240         uiRoundBox(st->txtscroll.xmin + 1, st->txtscroll.ymin, st->txtscroll.xmax - 1, st->txtscroll.ymax, rad);
1241         glDisable(GL_BLEND);
1242 }
1243
1244 /************************** draw markers **************************/
1245
1246 static void draw_markers(SpaceText *st, ARegion *ar)
1247 {
1248         Text *text = st->text;
1249         TextMarker *marker, *next;
1250         TextLine *top, *line;
1251         int offl, offc, i, x1, x2, y1, y2, x, y;
1252         int topi, topy;
1253
1254         /* Move pointer to first visible line (top) */
1255         top = first_visible_line(st, ar, NULL);
1256         topi = BLI_findindex(&text->lines, top);
1257
1258         topy = txt_get_span(text->lines.first, top);
1259
1260         for (marker = text->markers.first; marker; marker = next) {
1261                 next = marker->next;
1262
1263                 /* invisible line (before top) */
1264                 if (marker->lineno < topi) continue;
1265
1266                 line = BLI_findlink(&text->lines, marker->lineno);
1267
1268                 /* Remove broken markers */
1269                 if (marker->end > line->len || marker->start > marker->end) {
1270                         BLI_freelinkN(&text->markers, marker);
1271                         continue;
1272                 }
1273
1274                 wrap_offset(st, ar, line, marker->start, &offl, &offc);
1275                 y1 = txt_get_span(top, line) - st->top + offl + topy;
1276                 x1 = text_get_char_pos(st, line->line, marker->start) - st->left + offc;
1277
1278                 wrap_offset(st, ar, line, marker->end, &offl, &offc);
1279                 y2 = txt_get_span(top, line) - st->top + offl + topy;
1280                 x2 = text_get_char_pos(st, line->line, marker->end) - st->left + offc;
1281
1282                 /* invisible part of line (before top, after last visible line) */
1283                 if (y2 < 0 || y1 > st->top + st->viewlines) continue;
1284
1285                 glColor3ubv(marker->color);
1286                 x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1287                 y = ar->winy - 3;
1288
1289                 if (y1 == y2) {
1290                         y -= y1 * st->lheight;
1291                         glBegin(GL_LINE_LOOP);
1292                         glVertex2i(x + x2 * st->cwidth + 1, y);
1293                         glVertex2i(x + x1 * st->cwidth - 2, y);
1294                         glVertex2i(x + x1 * st->cwidth - 2, y - st->lheight);
1295                         glVertex2i(x + x2 * st->cwidth + 1, y - st->lheight);
1296                         glEnd();
1297                 }
1298                 else {
1299                         y -= y1 * st->lheight;
1300                         glBegin(GL_LINE_STRIP);
1301                         glVertex2i(ar->winx, y);
1302                         glVertex2i(x + x1 * st->cwidth - 2, y);
1303                         glVertex2i(x + x1 * st->cwidth - 2, y - st->lheight);
1304                         glVertex2i(ar->winx, y - st->lheight);
1305                         glEnd();
1306                         y -= st->lheight;
1307
1308                         for (i = y1 + 1; i < y2; i++) {
1309                                 glBegin(GL_LINES);
1310                                 glVertex2i(x, y);
1311                                 glVertex2i(ar->winx, y);
1312                                 glVertex2i(x, y - st->lheight);
1313                                 glVertex2i(ar->winx, y - st->lheight);
1314                                 glEnd();
1315                                 y -= st->lheight;
1316                         }
1317
1318                         glBegin(GL_LINE_STRIP);
1319                         glVertex2i(x, y);
1320                         glVertex2i(x + x2 * st->cwidth + 1, y);
1321                         glVertex2i(x + x2 * st->cwidth + 1, y - st->lheight);
1322                         glVertex2i(x, y - st->lheight);
1323                         glEnd();
1324                 }
1325         }
1326 }
1327
1328 /*********************** draw documentation *******************************/
1329
1330 static void draw_documentation(SpaceText *st, ARegion *ar)
1331 {
1332         TextLine *tmp;
1333         char *docs, buf[DOC_WIDTH + 1], *p;
1334         int i, br, lines;
1335         int boxw, boxh, l, x, y /* , top */ /* UNUSED */;
1336         
1337         if (!st || !st->text) return;
1338         if (!texttool_text_is_active(st->text)) return;
1339         
1340         docs = texttool_docs_get();
1341
1342         if (!docs) return;
1343
1344         /* Count the visible lines to the cursor */
1345         for (tmp = st->text->curl, l = -st->top; tmp; tmp = tmp->prev, l++) ;
1346         if (l < 0) return;
1347         
1348         if (st->showlinenrs) {
1349                 x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET + TEXTXLOC - 4;
1350         }
1351         else {
1352                 x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET - 4;
1353         }
1354         if (texttool_suggest_first()) {
1355                 x += SUGG_LIST_WIDTH * st->cwidth + 50;
1356         }
1357
1358         /* top= */ /* UNUSED */ y = ar->winy - st->lheight * l - 2;
1359         boxw = DOC_WIDTH * st->cwidth + 20;
1360         boxh = (DOC_HEIGHT + 1) * st->lheight;
1361
1362         /* Draw panel */
1363         UI_ThemeColor(TH_BACK);
1364         glRecti(x, y, x + boxw, y - boxh);
1365         UI_ThemeColor(TH_SHADE1);
1366         glBegin(GL_LINE_LOOP);
1367         glVertex2i(x, y);
1368         glVertex2i(x + boxw, y);
1369         glVertex2i(x + boxw, y - boxh);
1370         glVertex2i(x, y - boxh);
1371         glEnd();
1372         glBegin(GL_LINE_LOOP);
1373         glVertex2i(x + boxw - 10, y - 7);
1374         glVertex2i(x + boxw - 4, y - 7);
1375         glVertex2i(x + boxw - 7, y - 2);
1376         glEnd();
1377         glBegin(GL_LINE_LOOP);
1378         glVertex2i(x + boxw - 10, y - boxh + 7);
1379         glVertex2i(x + boxw - 4, y - boxh + 7);
1380         glVertex2i(x + boxw - 7, y - boxh + 2);
1381         glEnd();
1382         UI_ThemeColor(TH_TEXT);
1383
1384         i = 0; br = DOC_WIDTH; lines = 0; // XXX -doc_scroll;
1385         for (p = docs; *p; p++) {
1386                 if (*p == '\r' && *(++p) != '\n') *(--p) = '\n';  /* Fix line endings */
1387                 if (*p == ' ' || *p == '\t')
1388                         br = i;
1389                 else if (*p == '\n') {
1390                         buf[i] = '\0';
1391                         if (lines >= 0) {
1392                                 y -= st->lheight;
1393                                 text_draw(st, buf, 0, 0, 1, x + 4, y - 3, NULL);
1394                         }
1395                         i = 0; br = DOC_WIDTH; lines++;
1396                 }
1397                 buf[i++] = *p;
1398                 if (i == DOC_WIDTH) { /* Reached the width, go to last break and wrap there */
1399                         buf[br] = '\0';
1400                         if (lines >= 0) {
1401                                 y -= st->lheight;
1402                                 text_draw(st, buf, 0, 0, 1, x + 4, y - 3, NULL);
1403                         }
1404                         p -= i - br - 1; /* Rewind pointer to last break */
1405                         i = 0; br = DOC_WIDTH; lines++;
1406                 }
1407                 if (lines >= DOC_HEIGHT) break;
1408         }
1409
1410         if (0 /* XXX doc_scroll*/ > 0 && lines < DOC_HEIGHT) {
1411                 // XXX doc_scroll--;
1412                 draw_documentation(st, ar);
1413         }
1414 }
1415
1416 /*********************** draw suggestion list *******************************/
1417
1418 static void draw_suggestion_list(SpaceText *st, ARegion *ar)
1419 {
1420         SuggItem *item, *first, *last, *sel;
1421         TextLine *tmp;
1422         char str[SUGG_LIST_WIDTH + 1];
1423         int w, boxw = 0, boxh, i, l, x, y, b, *top;
1424         
1425         if (!st || !st->text) return;
1426         if (!texttool_text_is_active(st->text)) return;
1427
1428         first = texttool_suggest_first();
1429         last = texttool_suggest_last();
1430
1431         if (!first || !last) return;
1432
1433         text_pop_suggest_list();
1434         sel = texttool_suggest_selected();
1435         top = texttool_suggest_top();
1436
1437         /* Count the visible lines to the cursor */
1438         for (tmp = st->text->curl, l = -st->top; tmp; tmp = tmp->prev, l++) ;
1439         if (l < 0) return;
1440         
1441         if (st->showlinenrs) {
1442                 x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET + TEXTXLOC - 4;
1443         }
1444         else {
1445                 x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET - 4;
1446         }
1447         y = ar->winy - st->lheight * l - 2;
1448
1449         boxw = SUGG_LIST_WIDTH * st->cwidth + 20;
1450         boxh = SUGG_LIST_SIZE * st->lheight + 8;
1451         
1452         UI_ThemeColor(TH_SHADE1);
1453         glRecti(x - 1, y + 1, x + boxw + 1, y - boxh - 1);
1454         UI_ThemeColor(TH_BACK);
1455         glRecti(x, y, x + boxw, y - boxh);
1456
1457         /* Set the top 'item' of the visible list */
1458         for (i = 0, item = first; i < *top && item->next; i++, item = item->next) ;
1459
1460         for (i = 0; i < SUGG_LIST_SIZE && item; i++, item = item->next) {
1461
1462                 y -= st->lheight;
1463
1464                 BLI_strncpy(str, item->name, SUGG_LIST_WIDTH);
1465
1466                 w = BLF_width(mono, str);
1467                 
1468                 if (item == sel) {
1469                         UI_ThemeColor(TH_SHADE2);
1470                         glRecti(x + 16, y - 3, x + 16 + w, y + st->lheight - 3);
1471                 }
1472                 b = 1; /* b=1 color block, text is default. b=0 no block, color text */
1473                 switch (item->type) {
1474                         case 'k': UI_ThemeColor(TH_SYNTAX_B); b = 0; break;
1475                         case 'm': UI_ThemeColor(TH_TEXT); break;
1476                         case 'f': UI_ThemeColor(TH_SYNTAX_L); break;
1477                         case 'v': UI_ThemeColor(TH_SYNTAX_N); break;
1478                         case '?': UI_ThemeColor(TH_TEXT); b = 0; break;
1479                 }
1480                 if (b) {
1481                         glRecti(x + 8, y + 2, x + 11, y + 5);
1482                         UI_ThemeColor(TH_TEXT);
1483                 }
1484                 text_draw(st, str, 0, 0, 1, x + 16, y - 1, NULL);
1485
1486                 if (item == last) break;
1487         }
1488 }
1489
1490 /*********************** draw cursor ************************/
1491
1492 static void draw_cursor(SpaceText *st, ARegion *ar)
1493 {
1494         Text *text = st->text;
1495         int vcurl, vcurc, vsell, vselc, hidden = 0;
1496         int x, y, w, i;
1497
1498         /* Draw the selection */
1499         if (text->curl != text->sell || text->curc != text->selc) {
1500                 int offl, offc;
1501                 /* Convert all to view space character coordinates */
1502                 wrap_offset(st, ar, text->curl, text->curc, &offl, &offc);
1503                 vcurl = txt_get_span(text->lines.first, text->curl) - st->top + offl;
1504                 vcurc = text_get_char_pos(st, text->curl->line, text->curc) - st->left + offc;
1505                 wrap_offset(st, ar, text->sell, text->selc, &offl, &offc);
1506                 vsell = txt_get_span(text->lines.first, text->sell) - st->top + offl;
1507                 vselc = text_get_char_pos(st, text->sell->line, text->selc) - st->left + offc;
1508
1509                 if (vcurc < 0) vcurc = 0;
1510                 if (vselc < 0) vselc = 0, hidden = 1;
1511                 
1512                 UI_ThemeColor(TH_SHADE2);
1513                 x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1514                 y = ar->winy - 2;
1515
1516                 if (vcurl == vsell) {
1517                         y -= vcurl * st->lheight;
1518                         if (vcurc < vselc)
1519                                 glRecti(x + vcurc * st->cwidth - 1, y, x + vselc * st->cwidth, y - st->lheight);
1520                         else
1521                                 glRecti(x + vselc * st->cwidth - 1, y, x + vcurc * st->cwidth, y - st->lheight);
1522                 }
1523                 else {
1524                         int froml, fromc, tol, toc;
1525
1526                         if (vcurl < vsell) {
1527                                 froml = vcurl; tol = vsell;
1528                                 fromc = vcurc; toc = vselc;
1529                         }
1530                         else {
1531                                 froml = vsell; tol = vcurl;
1532                                 fromc = vselc; toc = vcurc;
1533                         }
1534
1535                         y -= froml * st->lheight;
1536                         glRecti(x + fromc * st->cwidth - 1, y, ar->winx, y - st->lheight); y -= st->lheight;
1537                         for (i = froml + 1; i < tol; i++)
1538                                 glRecti(x - 4, y, ar->winx, y - st->lheight),  y -= st->lheight;
1539
1540                         glRecti(x - 4, y, x + toc * st->cwidth, y - st->lheight);  y -= st->lheight;
1541                 }
1542         }
1543         else {
1544                 int offl, offc;
1545                 wrap_offset(st, ar, text->sell, text->selc, &offl, &offc);
1546                 vsell = txt_get_span(text->lines.first, text->sell) - st->top + offl;
1547                 vselc = text_get_char_pos(st, text->sell->line, text->selc) - st->left + offc;
1548
1549                 if (vselc < 0) {
1550                         vselc = 0;
1551                         hidden = 1;
1552                 }
1553         }
1554
1555         if (st->line_hlight) {
1556                 int x1, x2, y1, y2;
1557
1558                 if (st->wordwrap) {
1559                         int visible_lines = text_get_visible_lines(st, ar, text->sell->line);
1560                         int offl, offc;
1561
1562                         wrap_offset_in_line(st, ar, text->sell, text->selc, &offl, &offc);
1563
1564                         y1 = ar->winy - 2 - (vsell - offl) * st->lheight;
1565                         y2 = y1 - st->lheight * visible_lines + 1;
1566                 }
1567                 else {
1568                         y1 = ar->winy - 2 - vsell * st->lheight;
1569                         y2 = y1 - st->lheight + 1;
1570                 }
1571
1572                 if (!(y1 < 0 || y2 > ar->winy)) { /* check we need to draw */
1573                         x1 = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1574                         x2 = x1 + ar->winx;
1575
1576                         glColor4ub(255, 255, 255, 32);
1577                         
1578                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1579                         glEnable(GL_BLEND);
1580                         glRecti(x1 - 4, y1, x2, y2);
1581                         glDisable(GL_BLEND);
1582                 }
1583         }
1584         
1585         if (!hidden) {
1586                 /* Draw the cursor itself (we draw the sel. cursor as this is the leading edge) */
1587                 x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1588                 x += vselc * st->cwidth;
1589                 y = ar->winy - 2 - vsell * st->lheight;
1590                 
1591                 if (st->overwrite) {
1592                         char ch = text->sell->line[text->selc];
1593                         
1594                         w = st->cwidth;
1595                         if (ch == '\t') w *= st->tabnumber - (vselc + st->left) % st->tabnumber;
1596                         
1597                         UI_ThemeColor(TH_HILITE);
1598                         glRecti(x, y - st->lheight - 1, x + w, y - st->lheight + 1);
1599                 }
1600                 else {
1601                         UI_ThemeColor(TH_HILITE);
1602                         glRecti(x - 1, y, x + 1, y - st->lheight);
1603                 }
1604         }
1605 }
1606
1607 /******************* draw matching brackets *********************/
1608
1609 static void draw_brackets(SpaceText *st, ARegion *ar)
1610 {
1611         TextLine *startl, *endl, *linep;
1612         Text *text = st->text;
1613         int b, fc, find, stack, viewc, viewl, offl, offc, x, y;
1614         int startc, endc, c;
1615         
1616         char ch;
1617
1618         // showsyntax must be on or else the format string will be null
1619         if (!text->curl || !st->showsyntax) return;
1620
1621         startl = text->curl;
1622         startc = text->curc;
1623         b = text_check_bracket(startl->line[startc]);
1624         if (b == 0 && startc > 0) b = text_check_bracket(startl->line[--startc]);
1625         if (b == 0) return;
1626
1627         linep = startl;
1628         c = startc;
1629         fc = txt_utf8_offset_to_index(linep->line, startc);
1630         endl = NULL;
1631         endc = -1;
1632         find = -b;
1633         stack = 0;
1634         
1635         /* Don't highlight backets if syntax HL is off or bracket in string or comment. */
1636         if (!linep->format || linep->format[fc] == 'l' || linep->format[fc] == '#')
1637                 return;
1638
1639         if (b > 0) {
1640                 /* opening bracket, search forward for close */
1641                 fc++;
1642                 c += BLI_str_utf8_size(linep->line + c);
1643                 while (linep) {
1644                         while (c < linep->len) {
1645                                 if (linep->format && linep->format[fc] != 'l' && linep->format[fc] != '#') {
1646                                         b = text_check_bracket(linep->line[c]);
1647                                         if (b == find) {
1648                                                 if (stack == 0) {
1649                                                         endl = linep;
1650                                                         endc = c;
1651                                                         break;
1652                                                 }
1653                                                 stack--;
1654                                         }
1655                                         else if (b == -find) {
1656                                                 stack++;
1657                                         }
1658                                 }
1659                                 fc++;
1660                                 c += BLI_str_utf8_size(linep->line + c);
1661                         }
1662                         if (endl) break;
1663                         linep = linep->next;
1664                         c = 0;
1665                         fc = 0;
1666                 }
1667         }
1668         else {
1669                 /* closing bracket, search backward for open */
1670                 fc--;
1671                 if (c > 0) c -= linep->line + c - BLI_str_prev_char_utf8(linep->line + c);
1672                 while (linep) {
1673                         while (fc >= 0) {
1674                                 if (linep->format && linep->format[fc] != 'l' && linep->format[fc] != '#') {
1675                                         b = text_check_bracket(linep->line[c]);
1676                                         if (b == find) {
1677                                                 if (stack == 0) {
1678                                                         endl = linep;
1679                                                         endc = c;
1680                                                         break;
1681                                                 }
1682                                                 stack--;
1683                                         }
1684                                         else if (b == -find) {
1685                                                 stack++;
1686                                         }
1687                                 }
1688                                 fc--;
1689                                 if (c > 0) c -= linep->line + c - BLI_str_prev_char_utf8(linep->line + c);
1690                         }
1691                         if (endl) break;
1692                         linep = linep->prev;
1693                         if (linep) {
1694                                 if (linep->format) fc = strlen(linep->format) - 1;
1695                                 else fc = -1;
1696                                 if (linep->len) c = BLI_str_prev_char_utf8(linep->line + linep->len) - linep->line;
1697                                 else fc = -1;
1698                         }
1699                 }
1700         }
1701
1702         if (!endl || endc == -1)
1703                 return;
1704
1705         UI_ThemeColor(TH_HILITE);
1706         x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1707         y = ar->winy - st->lheight;
1708
1709         /* draw opening bracket */
1710         ch = startl->line[startc];
1711         wrap_offset(st, ar, startl, startc, &offl, &offc);
1712         viewc = text_get_char_pos(st, startl->line, startc) - st->left + offc;
1713
1714         if (viewc >= 0) {
1715                 viewl = txt_get_span(text->lines.first, startl) - st->top + offl;
1716
1717                 text_font_draw_character(st, x + viewc * st->cwidth, y - viewl * st->lheight, ch);
1718                 text_font_draw_character(st, x + viewc * st->cwidth + 1, y - viewl * st->lheight, ch);
1719         }
1720
1721         /* draw closing bracket */
1722         ch = endl->line[endc];
1723         wrap_offset(st, ar, endl, endc, &offl, &offc);
1724         viewc = text_get_char_pos(st, endl->line, endc) - st->left + offc;
1725
1726         if (viewc >= 0) {
1727                 viewl = txt_get_span(text->lines.first, endl) - st->top + offl;
1728
1729                 text_font_draw_character(st, x + viewc * st->cwidth, y - viewl * st->lheight, ch);
1730                 text_font_draw_character(st, x + viewc * st->cwidth + 1, y - viewl * st->lheight, ch);
1731         }
1732 }
1733
1734 /*********************** main area drawing *************************/
1735
1736 void draw_text_main(SpaceText *st, ARegion *ar)
1737 {
1738         Text *text = st->text;
1739         TextLine *tmp;
1740         rcti scroll, back;
1741         char linenr[12];
1742         int i, x, y, winx, linecount = 0, lineno = 0;
1743         int wraplinecount = 0, wrap_skip = 0;
1744         int margin_column_x;
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                 margin_column_x = x + st->cwidth * (st->margin_column - st->left);
1850                 
1851                 if (margin_column_x >= x) {
1852                         glBegin(GL_LINES);
1853                         glVertex2i(margin_column_x, 0);
1854                         glVertex2i(margin_column_x, ar->winy - 2);
1855                         glEnd();
1856                 }
1857         }
1858
1859         /* draw other stuff */
1860         draw_brackets(st, ar);
1861         draw_markers(st, ar);
1862         glTranslatef(GLA_PIXEL_OFS, GLA_PIXEL_OFS, 0.0f); /* XXX scroll requires exact pixel space */
1863         draw_textscroll(st, &scroll, &back);
1864         draw_documentation(st, ar);
1865         draw_suggestion_list(st, ar);
1866         
1867         text_font_end(st);
1868 }
1869
1870 /************************** update ***************************/
1871
1872 void text_update_character_width(SpaceText *st)
1873 {
1874         text_font_begin(st);
1875         st->cwidth = BLF_fixed_width(mono);
1876         st->cwidth = MAX2(st->cwidth, 1);
1877         text_font_end(st);
1878 }
1879
1880 /* Moves the view to the cursor location,
1881  * also used to make sure the view isn't outside the file */
1882 void text_scroll_to_cursor(SpaceText *st, ScrArea *sa)
1883 {
1884         Text *text;
1885         ARegion *ar = NULL;
1886         int i, x, winx = 0;
1887
1888         if (ELEM3(NULL, st, st->text, st->text->curl)) return;
1889
1890         text = st->text;
1891
1892         for (ar = sa->regionbase.first; ar; ar = ar->next)
1893                 if (ar->regiontype == RGN_TYPE_WINDOW) {
1894                         winx = ar->winx;
1895                         break;
1896                 }
1897         
1898         winx -= TXT_SCROLL_WIDTH;
1899
1900         text_update_character_width(st);
1901
1902         i = txt_get_span(text->lines.first, text->sell);
1903         if (st->wordwrap) {
1904                 int offl, offc;
1905                 wrap_offset(st, ar, text->sell, text->selc, &offl, &offc);
1906                 i += offl;
1907         }
1908
1909         if (st->top + st->viewlines <= i || st->top > i)
1910                 st->top = i - st->viewlines / 2;
1911         
1912         if (st->wordwrap) {
1913                 st->left = 0;
1914         }
1915         else {
1916                 x = text_draw(st, text->sell->line, st->left, text->selc, 0, 0, 0, NULL);
1917
1918                 if (x == 0 || x > winx)
1919                         st->left = text->curc - 0.5 * winx / st->cwidth;
1920         }
1921
1922         if (st->top < 0) st->top = 0;
1923         if (st->left < 0) st->left = 0;
1924 }
1925
1926 void text_update_cursor_moved(bContext *C)
1927 {
1928         ScrArea *sa = CTX_wm_area(C);
1929         SpaceText *st = CTX_wm_space_text(C);
1930
1931         text_scroll_to_cursor(st, sa);
1932 }