Fix #19580: text editor - current line is dependent on the
[blender-staging.git] / source / blender / editors / space_text / text_draw.c
1 /**
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19  *
20  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
21  * All rights reserved.
22  *
23  * The Original Code is: all of this file.
24  *
25  * Contributor(s): none yet.
26  *
27  * ***** END GPL LICENSE BLOCK *****
28  */
29
30 #include <math.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/stat.h>
34
35 #include "MEM_guardedalloc.h"
36
37 #include "BLF_api.h"
38
39 #include "BLI_blenlib.h"
40
41 #include "DNA_text_types.h"
42 #include "DNA_space_types.h"
43 #include "DNA_screen_types.h"
44 #include "DNA_userdef_types.h"
45
46 #include "BKE_context.h"
47 #include "BKE_global.h"
48 #include "BKE_main.h"
49 #include "BKE_suggestions.h"
50 #include "BKE_text.h"
51 #include "BKE_utildefines.h"
52
53 #include "BIF_gl.h"
54 #include "BIF_glutil.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
64 static void text_font_begin(SpaceText *st)
65 {
66         static int mono= -1; // XXX needs proper storage
67
68         if(mono == -1)
69                 mono= BLF_load_mem("monospace", (unsigned char*)datatoc_bmonofont_ttf, datatoc_bmonofont_ttf_size);
70
71         BLF_set(mono);
72         BLF_aspect(1.0);
73
74         BLF_size(st->lheight, 72);
75 }
76
77 static void text_font_end(SpaceText *st)
78 {
79 }
80
81 static int text_font_draw(SpaceText *st, int x, int y, char *str)
82 {
83         BLF_position(x, y, 0);
84         BLF_draw(str);
85
86         return BLF_width(str);
87 }
88
89 static int text_font_draw_character(SpaceText *st, int x, int y, char c)
90 {
91         char str[2];
92
93         str[0]= c;
94         str[1]= '\0';
95
96         BLF_position(x, y, 0);
97         BLF_draw(str);
98
99         return st->cwidth;
100 }
101
102 int text_font_width(SpaceText *st, char *str)
103 {
104         return BLF_width(str);
105 }
106
107 /****************** flatten string **********************/
108
109 static void flatten_string_append(FlattenString *fs, char c, int accum) 
110 {
111         if(fs->pos>=fs->len && fs->pos>=sizeof(fs->fixedbuf)-1) {
112                 char *nbuf; int *naccum;
113                 if(fs->len) fs->len*= 2;
114                 else fs->len= sizeof(fs->fixedbuf) * 2;
115
116                 nbuf= MEM_callocN(sizeof(*fs->buf)*fs->len, "fs->buf");
117                 naccum= MEM_callocN(sizeof(*fs->accum)*fs->len, "fs->accum");
118
119                 memcpy(nbuf, fs->buf, fs->pos);
120                 memcpy(naccum, fs->accum, fs->pos);
121                 
122                 if(fs->buf != fs->fixedbuf) {
123                         MEM_freeN(fs->buf);
124                         MEM_freeN(fs->accum);
125                 }
126                 
127                 fs->buf= nbuf;
128                 fs->accum= naccum;
129         }
130         
131         fs->buf[fs->pos]= c;    
132         fs->accum[fs->pos]= accum;
133         
134         fs->pos++;
135 }
136
137 int flatten_string(SpaceText *st, FlattenString *fs, char *in)
138 {
139         int r = 0, i = 0;
140
141         memset(fs, 0, sizeof(FlattenString));
142         fs->buf= fs->fixedbuf;
143         fs->accum= fs->fixedaccum;
144         
145         for(r=0, i=0; *in; r++, in++) {
146                 if(*in=='\t') {
147                         if(fs->pos && *(in-1)=='\t')
148                                 i= st->tabnumber;
149                         else if(st->tabnumber > 0)
150                                 i= st->tabnumber - (fs->pos%st->tabnumber);
151
152                         while(i--)
153                                 flatten_string_append(fs, ' ', r);
154                 }
155                 else
156                         flatten_string_append(fs, *in, r);
157         }
158
159         return fs->pos;
160 }
161
162 void flatten_string_free(FlattenString *fs)
163 {
164         if(fs->buf != fs->fixedbuf)
165                 MEM_freeN(fs->buf);
166         if(fs->accum != fs->fixedaccum)
167                 MEM_freeN(fs->accum);
168 }
169
170 /* Checks the specified source string for a Python built-in function name. This
171  name must start at the beginning of the source string and must be followed by
172  a non-identifier (see text_check_identifier(char)) or null character.
173  
174  If a built-in function is found, the length of the matching name is returned.
175  Otherwise, -1 is returned. */
176
177 static int find_builtinfunc(char *string)
178 {
179         int a, i;
180         char builtinfuncs[][11] = {"and", "as", "assert", "break", "class", "continue", "def",
181                                                                 "del", "elif", "else", "except", "exec", "finally",
182                                                                 "for", "from", "global", "if", "import", "in",
183                                                                 "is", "lambda", "not", "or", "pass", "print",
184                                                                 "raise", "return", "try", "while", "yield"};
185         for(a=0; a<30; a++) {
186                 i = 0;
187                 while(1) {
188                         /* If we hit the end of a keyword... (eg. "def") */
189                         if(builtinfuncs[a][i]=='\0') {
190                                 /* If we still have identifier chars in the source (eg. "definate") */
191                                 if(text_check_identifier(string[i]))
192                                         i = -1; /* No match */
193                                 break; /* Next keyword if no match, otherwise we're done */
194                                 
195                         /* If chars mismatch, move on to next keyword */
196                         }
197                         else if(string[i]!=builtinfuncs[a][i]) {
198                                 i = -1;
199                                 break; /* Break inner loop, start next keyword */
200                         }
201                         i++;
202                 }
203                 if(i>0) break; /* If we have a match, we're done */
204         }
205         return i;
206 }
207
208 /* Checks the specified source string for a Python special name. This name must
209  start at the beginning of the source string and must be followed by a non-
210  identifier (see text_check_identifier(char)) or null character.
211  
212  If a special name is found, the length of the matching name is returned.
213  Otherwise, -1 is returned. */
214
215 static int find_specialvar(char *string) 
216 {
217         int i = 0;
218         /* Check for "def" */
219         if(string[0]=='d' && string[1]=='e' && string[2]=='f')
220                 i = 3;
221         /* Check for "class" */
222         else if(string[0]=='c' && string[1]=='l' && string[2]=='a' && string[3]=='s' && string[4]=='s')
223                 i = 5;
224         /* If next source char is an identifier (eg. 'i' in "definate") no match */
225         if(i==0 || text_check_identifier(string[i]))
226                 return -1;
227         return i;
228 }
229
230 /* Ensures the format string for the given line is long enough, reallocating
231  as needed. Allocation is done here, alone, to ensure consistency. */
232 int text_check_format_len(TextLine *line, unsigned int len)
233 {
234         if(line->format) {
235                 if(strlen(line->format) < len) {
236                         MEM_freeN(line->format);
237                         line->format = MEM_mallocN(len+2, "SyntaxFormat");
238                         if(!line->format) return 0;
239                 }
240         }
241         else {
242                 line->format = MEM_mallocN(len+2, "SyntaxFormat");
243                 if(!line->format) return 0;
244         }
245
246         return 1;
247 }
248
249 /* Formats the specified line. If do_next is set, the process will move on to
250  the succeeding line if it is affected (eg. multiline strings). Format strings
251  may contain any of the following characters:
252         '_'             Whitespace
253         '#'             Comment text
254         '!'             Punctuation and other symbols
255         'n'             Numerals
256         'l'             String letters
257         'v'             Special variables (class, def)
258         'b'             Built-in names (print, for, etc.)
259         'q'             Other text (identifiers, etc.)
260  It is terminated with a null-terminator '\0' followed by a continuation
261  flag indicating whether the line is part of a multi-line string. */
262
263 static void txt_format_line(SpaceText *st, TextLine *line, int do_next)
264 {
265         FlattenString fs;
266         char *str, *fmt, orig, cont, find, prev = ' ';
267         int len, i;
268
269         /* Get continuation from previous line */
270         if(line->prev && line->prev->format != NULL) {
271                 fmt= line->prev->format;
272                 cont = fmt[strlen(fmt)+1]; /* Just after the null-terminator */
273         }
274         else cont = 0;
275
276         /* Get original continuation from this line */
277         if(line->format != NULL) {
278                 fmt= line->format;
279                 orig = fmt[strlen(fmt)+1]; /* Just after the null-terminator */
280         }
281         else orig = 0xFF;
282
283         flatten_string(st, &fs, line->line);
284         str = fs.buf;
285         len = strlen(str);
286         if(!text_check_format_len(line, len)) {
287                 flatten_string_free(&fs);
288                 return;
289         }
290         fmt = line->format;
291
292         while(*str) {
293                 /* Handle escape sequences by skipping both \ and next char */
294                 if(*str == '\\') {
295                         *fmt = prev; fmt++; str++;
296                         if(*str == '\0') break;
297                         *fmt = prev; fmt++; str++;
298                         continue;
299                 }
300                 /* Handle continuations */
301                 else if(cont) {
302                         /* Triple strings ("""...""" or '''...''') */
303                         if(cont & TXT_TRISTR) {
304                                 find = (cont & TXT_DBLQUOTSTR) ? '"' : '\'';
305                                 if(*str==find && *(str+1)==find && *(str+2)==find) {
306                                         *fmt = 'l'; fmt++; str++;
307                                         *fmt = 'l'; fmt++; str++;
308                                         cont = 0;
309                                 }
310                         /* Handle other strings */
311                         }
312                         else {
313                                 find = (cont & TXT_DBLQUOTSTR) ? '"' : '\'';
314                                 if(*str == find) cont = 0;
315                         }
316
317                         *fmt = 'l';
318                 }
319                 /* Not in a string... */
320                 else {
321                         /* Deal with comments first */
322                         if(prev == '#' || *str == '#')
323                                 *fmt = '#';
324                         /* Strings */
325                         else if(*str == '"' || *str == '\'') {
326                                 find = *str;
327                                 cont = (*str== '"') ? TXT_DBLQUOTSTR : TXT_SNGQUOTSTR;
328                                 if(*(str+1) == find && *(str+2) == find) {
329                                         *fmt = 'l'; fmt++; str++;
330                                         *fmt = 'l'; fmt++; str++;
331                                         cont |= TXT_TRISTR;
332                                 }
333                                 *fmt = 'l';
334                         }
335                         /* Whitespace (all ws. has been converted to spaces) */
336                         else if(*str == ' ')
337                                 *fmt = '_';
338                         /* Numbers (digits not part of an identifier and periods followed by digits) */
339                         else if((prev != 'q' && text_check_digit(*str)) || (*str == '.' && text_check_digit(*(str+1))))
340                                 *fmt = 'n';
341                         /* Punctuation */
342                         else if(text_check_delim(*str))
343                                 *fmt = '!';
344                         /* Identifiers and other text (no previous ws. or delims. so text continues) */
345                         else if(prev == 'q')
346                                 *fmt = 'q';
347                         /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
348                         else {
349                                 /* Special vars(v) or built-in keywords(b) */
350                                 if((i=find_specialvar(str)) != -1)
351                                         prev = 'v';
352                                 else if((i=find_builtinfunc(str)) != -1)
353                                         prev = 'b';
354                                 if(i>0) {
355                                         while(i>1) {
356                                                 *fmt = prev; fmt++; str++;
357                                                 i--;
358                                         }
359                                         *fmt = prev;
360                                 }
361                                 else
362                                         *fmt = 'q';
363                         }
364                 }
365                 prev = *fmt;
366                 fmt++;
367                 str++;
368         }
369
370         /* Terminate and add continuation char */
371         *fmt = '\0'; fmt++;
372         *fmt = cont;
373
374         /* Debugging */
375         //print_format(st, line);
376
377         /* If continuation has changed and we're allowed, process the next line */
378         if(cont!=orig && do_next && line->next) {
379                 txt_format_line(st, line->next, do_next);
380         }
381
382         flatten_string_free(&fs);
383 }
384
385 #if 0
386 /* Formats every line of the current text */
387 static void txt_format_text(SpaceText *st) 
388 {
389         TextLine *linep;
390
391         if(!st->text) return;
392
393         for(linep=st->text->lines.first; linep; linep=linep->next)
394                 txt_format_line(st, linep, 0);
395 }
396 #endif
397
398 /* Sets the current drawing color based on the format character specified */
399 static void format_draw_color(char formatchar)
400 {
401         switch (formatchar) {
402                 case '_': /* Whitespace */
403                         break;
404                 case '!': /* Symbols */
405                         UI_ThemeColorBlend(TH_TEXT, TH_BACK, 0.5f);
406                         break;
407                 case '#': /* Comments */
408                         UI_ThemeColor(TH_SYNTAX_C);
409                         break;
410                 case 'n': /* Numerals */
411                         UI_ThemeColor(TH_SYNTAX_N);
412                         break;
413                 case 'l': /* Strings */
414                         UI_ThemeColor(TH_SYNTAX_L);
415                         break;
416                 case 'v': /* Specials: class, def */
417                         UI_ThemeColor(TH_SYNTAX_V);
418                         break;
419                 case 'b': /* Keywords: for, print, etc. */
420                         UI_ThemeColor(TH_SYNTAX_B);
421                         break;
422                 case 'q': /* Other text (identifiers) */
423                 default:
424                         UI_ThemeColor(TH_TEXT);
425                         break;
426         }
427 }
428
429 /************************** draw text *****************************/
430
431 /***********************/ /*
432
433 Notes on word-wrap
434 --
435 All word-wrap functions follow the algorithm below to maintain consistency.
436         line            The line to wrap (tabs converted to spaces)
437         view_width      The maximum number of characters displayable in the region
438                                 This equals region_width/font_width for the region
439         wrap_chars      Characters that allow wrapping. This equals [' ', '\t', '-']
440
441 def wrap(line, view_width, wrap_chars):
442         draw_start = 0
443         draw_end = view_width
444         pos = 0
445         for c in line:
446                 if pos-draw_start >= view_width:
447                         print line[draw_start:draw_end]
448                         draw_start = draw_end
449                         draw_end += view_width
450                 elif c in wrap_chars:
451                         draw_end = pos+1
452                 pos += 1
453         print line[draw_start:]
454
455 */ /***********************/
456
457 int wrap_width(SpaceText *st, ARegion *ar)
458 {
459         int x, max;
460         
461         x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
462         max= (ar->winx-x)/st->cwidth;
463         return max>8 ? max : 8;
464 }
465
466 /* Sets (offl, offc) for transforming (line, curs) to its wrapped position */
467 void wrap_offset(SpaceText *st, ARegion *ar, TextLine *linein, int cursin, int *offl, int *offc)
468 {
469         Text *text;
470         TextLine *linep;
471         int i, j, start, end, chars, max, chop;
472         char ch;
473
474         *offl= *offc= 0;
475
476         if(!st->text) return;
477         if(!st->wordwrap) return;
478
479         text= st->text;
480
481         /* Move pointer to first visible line (top) */
482         linep= text->lines.first;
483         i= st->top;
484         while(i>0 && linep) {
485                 if(linep == linein) return; /* Line before top */
486                 linep= linep->next;
487                 i--;
488         }
489
490         max= wrap_width(st, ar);
491
492         while(linep) {
493                 start= 0;
494                 end= max;
495                 chop= 1;
496                 chars= 0;
497                 *offc= 0;
498                 for(i=0, j=0; linep->line[j]!='\0'; j++) {
499
500                         /* Mimic replacement of tabs */
501                         ch= linep->line[j];
502                         if(ch=='\t') {
503                                 chars= st->tabnumber-i%st->tabnumber;
504                                 if(linep==linein && i<cursin) cursin += chars-1;
505                                 ch= ' ';
506                         }
507                         else
508                                 chars= 1;
509
510                         while(chars--) {
511                                 if(i-start>=max) {
512                                         if(chop && linep==linein && i >= cursin)
513                                                 return;
514                                         (*offl)++;
515                                         *offc -= end-start;
516                                         start= end;
517                                         end += max;
518                                         chop= 1;
519                                 }
520                                 else if(ch==' ' || ch=='-') {
521                                         end = i+1;
522                                         chop= 0;
523                                         if(linep==linein && i >= cursin)
524                                                 return;
525                                 }
526                                 i++;
527                         }
528                 }
529                 if(linep==linein) break;
530                 linep= linep->next;
531         }
532 }
533
534 static int get_char_pos(SpaceText *st, char *line, int cur)
535 {
536         int a=0, i;
537         
538         for(i=0; i<cur && line[i]; i++) {
539                 if(line[i]=='\t')
540                         a += st->tabnumber-a%st->tabnumber;
541                 else
542                         a++;
543         }
544         return a;
545 }
546
547 static int text_draw_wrapped(SpaceText *st, char *str, int x, int y, int w, char *format)
548 {
549         FlattenString fs;
550         int basex, i, a, len, start, end, max, lines;
551         
552         len= flatten_string(st, &fs, str);
553         str= fs.buf;
554         max= w/st->cwidth;
555         if(max<8) max= 8;
556         basex= x;
557
558         lines= 1;
559         start= 0;
560         end= max;
561         for(i=0; i<len; i++) {
562                 if(i-start >= max) {
563                         /* Draw the visible portion of text on the overshot line */
564                         for(a=start; a<end; a++) {
565                                 if(st->showsyntax && format) format_draw_color(format[a]);
566                                 x += text_font_draw_character(st, x, y, str[a]);
567                         }
568                         y -= st->lheight;
569                         x= basex;
570                         lines++;
571                         start= end;
572                         end += max;
573                 }
574                 else if(str[i]==' ' || str[i]=='-') {
575                         end = i+1;
576                 }
577         }
578
579         /* Draw the remaining text */
580         for(a=start; a<len; a++) {
581                 if(st->showsyntax && format)
582                         format_draw_color(format[a]);
583
584                 x += text_font_draw_character(st, x, y, str[a]);
585         }
586
587         flatten_string_free(&fs);
588
589         return lines;
590 }
591
592 static int text_draw(SpaceText *st, char *str, int cshift, int maxwidth, int draw, int x, int y, char *format)
593 {
594         FlattenString fs;
595         int r=0, w= 0;
596         int *acc;
597         char *in;
598
599         w= flatten_string(st, &fs, str);
600         if(w < cshift) {
601                 flatten_string_free(&fs);
602                 return 0; /* String is shorter than shift */
603         }
604         
605         in= fs.buf+cshift;
606         acc= fs.accum+cshift;
607         w= w-cshift;
608
609         if(draw) {
610                 if(st->showsyntax && format) {
611                         int amount, a;
612                         format = format+cshift;
613                 
614                         amount = strlen(in);
615                         
616                         for(a = 0; a < amount; a++) {
617                                 format_draw_color(format[a]);
618                                 x += text_font_draw_character(st, x, y, in[a]);
619                         }
620                 }
621                 else
622                         text_font_draw(st, x, y, in);
623         }
624         else {
625                 while(w-- && *acc++ < maxwidth)
626                         r+= st->cwidth;
627         }
628
629         flatten_string_free(&fs);
630
631         if(cshift && r==0)
632                 return 0;
633         else if(st->showlinenrs)
634                 return r+TXT_OFFSET+TEXTXLOC;
635         else
636                 return r+TXT_OFFSET;
637 }
638
639 /************************ draw scrollbar *****************************/
640
641 static void calc_text_rcts(SpaceText *st, ARegion *ar, rcti *scroll)
642 {
643         int lhlstart, lhlend, ltexth;
644         short barheight, barstart, hlstart, hlend, blank_lines;
645         short pix_available, pix_top_margin, pix_bottom_margin, pix_bardiff;
646
647         pix_top_margin = 8;
648         pix_bottom_margin = 4;
649         pix_available = ar->winy - pix_top_margin - pix_bottom_margin;
650         ltexth= txt_get_span(st->text->lines.first, st->text->lines.last);
651         blank_lines = st->viewlines / 2;
652         
653         /* nicer code: use scroll rect for entire bar */
654         //scroll->xmin= 5;
655         //scroll->xmax= 17;
656         scroll->xmin= ar->winx - 17;
657         scroll->xmax= ar->winx - 5;
658         scroll->ymin= 4;
659         scroll->ymax= 4+pix_available;
660         
661         /* when resizing a vieport with the bar at the bottom to a greater height more blank lines will be added */
662         if(ltexth + blank_lines < st->top + st->viewlines) {
663                 blank_lines = st->top + st->viewlines - ltexth;
664         }
665         
666         ltexth += blank_lines;
667
668         barheight = (ltexth > 0)? (st->viewlines*pix_available)/ltexth: 0;
669         pix_bardiff = 0;
670         if(barheight < 20) {
671                 pix_bardiff = 20 - barheight; /* take into account the now non-linear sizing of the bar */      
672                 barheight = 20;
673         }
674         barstart = (ltexth > 0)? ((pix_available - pix_bardiff) * st->top)/ltexth: 0;
675
676         st->txtbar= *scroll;
677         st->txtbar.ymax -= barstart;
678         st->txtbar.ymin = st->txtbar.ymax - barheight;
679
680         CLAMP(st->txtbar.ymin, pix_bottom_margin, ar->winy - pix_top_margin);
681         CLAMP(st->txtbar.ymax, pix_bottom_margin, ar->winy - pix_top_margin);
682
683         st->pix_per_line= (pix_available > 0)? (float) ltexth/pix_available: 0;
684         if(st->pix_per_line<.1) st->pix_per_line=.1f;
685
686         lhlstart = MIN2(txt_get_span(st->text->lines.first, st->text->curl), 
687                                 txt_get_span(st->text->lines.first, st->text->sell));
688         lhlend = MAX2(txt_get_span(st->text->lines.first, st->text->curl), 
689                                 txt_get_span(st->text->lines.first, st->text->sell));
690
691         if(ltexth > 0) {
692                 hlstart = (lhlstart * pix_available)/ltexth;
693                 hlend = (lhlend * pix_available)/ltexth;
694
695                 /* the scrollbar is non-linear sized */
696                 if(pix_bardiff > 0) {
697                         /* the start of the highlight is in the current viewport */
698                         if(ltexth && st->viewlines && lhlstart >= st->top && lhlstart <= st->top + st->viewlines) { 
699                                 /* speed the progresion of the start of the highlight through the scrollbar */
700                                 hlstart = ( ( (pix_available - pix_bardiff) * lhlstart) / ltexth) + (pix_bardiff * (lhlstart - st->top) / st->viewlines);       
701                         }
702                         else if(lhlstart > st->top + st->viewlines && hlstart < barstart + barheight && hlstart > barstart) {
703                                 /* push hl start down */
704                                 hlstart = barstart + barheight;
705                         }
706                         else if(lhlend > st->top  && lhlstart < st->top && hlstart > barstart) {
707                                 /*fill out start */
708                                 hlstart = barstart;
709                         }
710
711                         if(hlend <= hlstart) { 
712                                 hlend = hlstart + 2;
713                         }
714
715                         /* the end of the highlight is in the current viewport */
716                         if(ltexth && st->viewlines && lhlend >= st->top && lhlend <= st->top + st->viewlines) { 
717                                 /* speed the progresion of the end of the highlight through the scrollbar */
718                                 hlend = (((pix_available - pix_bardiff )*lhlend)/ltexth) + (pix_bardiff * (lhlend - st->top)/st->viewlines);    
719                         }
720                         else if(lhlend < st->top && hlend >= barstart - 2 && hlend < barstart + barheight) {
721                                 /* push hl end up */
722                                 hlend = barstart;
723                         }                                       
724                         else if(lhlend > st->top + st->viewlines && lhlstart < st->top + st->viewlines && hlend < barstart + barheight) {
725                                 /* fill out end */
726                                 hlend = barstart + barheight;
727                         }
728
729                         if(hlend <= hlstart) { 
730                                 hlstart = hlend - 2;
731                         }       
732                 }       
733         }
734         else {
735                 hlstart = 0;
736                 hlend = 0;
737         }
738
739         if(hlend - hlstart < 2) { 
740                 hlend = hlstart + 2;
741         }
742         
743         st->txtscroll= *scroll;
744         st->txtscroll.ymax= ar->winy - pix_top_margin - hlstart;
745         st->txtscroll.ymin= ar->winy - pix_top_margin - hlend;
746
747         CLAMP(st->txtscroll.ymin, pix_bottom_margin, ar->winy - pix_top_margin);
748         CLAMP(st->txtscroll.ymax, pix_bottom_margin, ar->winy - pix_top_margin);
749 }
750
751 static void draw_textscroll(SpaceText *st, ARegion *ar, rcti *scroll)
752 {
753         bTheme *btheme= U.themes.first;
754         uiWidgetColors wcol= btheme->tui.wcol_scroll;
755         char col[3];
756         float rad;
757         
758         uiWidgetScrollDraw(&wcol, scroll, &st->txtbar, (st->flags & ST_SCROLL_SELECT)?UI_SCROLL_PRESSED:0);
759
760         uiSetRoundBox(15);
761         rad= 0.4f*MIN2(st->txtscroll.xmax - st->txtscroll.xmin, st->txtscroll.ymax - st->txtscroll.ymin);
762         UI_GetThemeColor3ubv(TH_HILITE, col);
763         glColor4ub(col[0], col[1], col[2], 48);
764         glEnable(GL_BLEND);
765         uiRoundBox(st->txtscroll.xmin+1, st->txtscroll.ymin, st->txtscroll.xmax-1, st->txtscroll.ymax, rad);
766         glDisable(GL_BLEND);
767 }
768
769 /************************** draw markers **************************/
770
771 static void draw_markers(SpaceText *st, ARegion *ar)
772 {
773         Text *text= st->text;
774         TextMarker *marker, *next;
775         TextLine *top, *bottom, *line;
776         int offl, offc, i, cy, x1, x2, y1, y2, x, y;
777
778         for(i=st->top, top= text->lines.first; top->next && i>0; i--)
779                 top= top->next;
780
781         for(i=st->viewlines-1, bottom=top; bottom->next && i>0; i--)
782                 bottom= bottom->next;
783         
784         for(marker= text->markers.first; marker; marker= next) {
785                 next= marker->next;
786
787                 for(cy= 0, line= top; line; cy++, line= line->next) {
788                         if(cy+st->top==marker->lineno) {
789                                 /* Remove broken markers */
790                                 if(marker->end>line->len || marker->start>marker->end) {
791                                         BLI_freelinkN(&text->markers, marker);
792                                         break;
793                                 }
794
795                                 wrap_offset(st, ar, line, marker->start, &offl, &offc);
796                                 x1= get_char_pos(st, line->line, marker->start) - st->left + offc;
797                                 y1= cy + offl;
798                                 wrap_offset(st, ar, line, marker->end, &offl, &offc);
799                                 x2= get_char_pos(st, line->line, marker->end) - st->left + offc;
800                                 y2= cy + offl;
801
802                                 glColor3ub(marker->color[0], marker->color[1], marker->color[2]);
803                                 x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
804                                 y= ar->winy-3;
805
806                                 if(y1==y2) {
807                                         y -= y1*st->lheight;
808                                         glBegin(GL_LINE_LOOP);
809                                         glVertex2i(x+x2*st->cwidth+1, y);
810                                         glVertex2i(x+x1*st->cwidth-2, y);
811                                         glVertex2i(x+x1*st->cwidth-2, y-st->lheight);
812                                         glVertex2i(x+x2*st->cwidth+1, y-st->lheight);
813                                         glEnd();
814                                 }
815                                 else {
816                                         y -= y1*st->lheight;
817                                         glBegin(GL_LINE_STRIP);
818                                         glVertex2i(ar->winx, y);
819                                         glVertex2i(x+x1*st->cwidth-2, y);
820                                         glVertex2i(x+x1*st->cwidth-2, y-st->lheight);
821                                         glVertex2i(ar->winx, y-st->lheight);
822                                         glEnd();
823                                         y-=st->lheight;
824
825                                         for(i=y1+1; i<y2; i++) {
826                                                 glBegin(GL_LINES);
827                                                 glVertex2i(x, y);
828                                                 glVertex2i(ar->winx, y);
829                                                 glVertex2i(x, y-st->lheight);
830                                                 glVertex2i(ar->winx, y-st->lheight);
831                                                 glEnd();
832                                                 y-=st->lheight;
833                                         }
834
835                                         glBegin(GL_LINE_STRIP);
836                                         glVertex2i(x, y);
837                                         glVertex2i(x+x2*st->cwidth+1, y);
838                                         glVertex2i(x+x2*st->cwidth+1, y-st->lheight);
839                                         glVertex2i(x, y-st->lheight);
840                                         glEnd();
841                                 }
842
843                                 break;
844                         }
845
846                         if(line==bottom) break;
847                 }
848         }
849 }
850
851 /*********************** draw documentation *******************************/
852
853 static void draw_documentation(SpaceText *st, ARegion *ar)
854 {
855         TextLine *tmp;
856         char *docs, buf[DOC_WIDTH+1], *p;
857         int len, i, br, lines;
858         int boxw, boxh, l, x, y, top;
859         
860         if(!st || !st->text) return;
861         if(!texttool_text_is_active(st->text)) return;
862         
863         docs = texttool_docs_get();
864
865         if(!docs) return;
866
867         /* Count the visible lines to the cursor */
868         for(tmp=st->text->curl, l=-st->top; tmp; tmp=tmp->prev, l++);
869         if(l<0) return;
870         
871         if(st->showlinenrs) {
872                 x= st->cwidth*(st->text->curc-st->left) + TXT_OFFSET + TEXTXLOC - 4;
873         }
874         else {
875                 x= st->cwidth*(st->text->curc-st->left) + TXT_OFFSET - 4;
876         }
877         if(texttool_suggest_first()) {
878                 x += SUGG_LIST_WIDTH*st->cwidth + 50;
879         }
880
881         top= y= ar->winy - st->lheight*l - 2;
882         len= strlen(docs);
883         boxw= DOC_WIDTH*st->cwidth + 20;
884         boxh= (DOC_HEIGHT+1)*st->lheight;
885
886         /* Draw panel */
887         UI_ThemeColor(TH_BACK);
888         glRecti(x, y, x+boxw, y-boxh);
889         UI_ThemeColor(TH_SHADE1);
890         glBegin(GL_LINE_LOOP);
891         glVertex2i(x, y);
892         glVertex2i(x+boxw, y);
893         glVertex2i(x+boxw, y-boxh);
894         glVertex2i(x, y-boxh);
895         glEnd();
896         glBegin(GL_LINE_LOOP);
897         glVertex2i(x+boxw-10, y-7);
898         glVertex2i(x+boxw-4, y-7);
899         glVertex2i(x+boxw-7, y-2);
900         glEnd();
901         glBegin(GL_LINE_LOOP);
902         glVertex2i(x+boxw-10, y-boxh+7);
903         glVertex2i(x+boxw-4, y-boxh+7);
904         glVertex2i(x+boxw-7, y-boxh+2);
905         glEnd();
906         UI_ThemeColor(TH_TEXT);
907
908         i= 0; br= DOC_WIDTH; lines= 0; // XXX -doc_scroll;
909         for(p=docs; *p; p++) {
910                 if(*p == '\r' && *(++p) != '\n') *(--p)= '\n'; /* Fix line endings */
911                 if(*p == ' ' || *p == '\t')
912                         br= i;
913                 else if(*p == '\n') {
914                         buf[i]= '\0';
915                         if(lines>=0) {
916                                 y -= st->lheight;
917                                 text_draw(st, buf, 0, 0, 1, x+4, y-3, NULL);
918                         }
919                         i= 0; br= DOC_WIDTH; lines++;
920                 }
921                 buf[i++]= *p;
922                 if(i == DOC_WIDTH) { /* Reached the width, go to last break and wrap there */
923                         buf[br]= '\0';
924                         if(lines>=0) {
925                                 y -= st->lheight;
926                                 text_draw(st, buf, 0, 0, 1, x+4, y-3, NULL);
927                         }
928                         p -= i-br-1; /* Rewind pointer to last break */
929                         i= 0; br= DOC_WIDTH; lines++;
930                 }
931                 if(lines >= DOC_HEIGHT) break;
932         }
933
934         if(0 /* XXX doc_scroll*/ > 0 && lines < DOC_HEIGHT) {
935                 // XXX doc_scroll--;
936                 draw_documentation(st, ar);
937         }
938 }
939
940 /*********************** draw suggestion list *******************************/
941
942 static void draw_suggestion_list(SpaceText *st, ARegion *ar)
943 {
944         SuggItem *item, *first, *last, *sel;
945         TextLine *tmp;
946         char str[SUGG_LIST_WIDTH+1];
947         int w, boxw=0, boxh, i, l, x, y, b, *top;
948         
949         if(!st || !st->text) return;
950         if(!texttool_text_is_active(st->text)) return;
951
952         first = texttool_suggest_first();
953         last = texttool_suggest_last();
954
955         if(!first || !last) return;
956
957         text_pop_suggest_list();
958         sel = texttool_suggest_selected();
959         top = texttool_suggest_top();
960
961         /* Count the visible lines to the cursor */
962         for(tmp=st->text->curl, l=-st->top; tmp; tmp=tmp->prev, l++);
963         if(l<0) return;
964         
965         if(st->showlinenrs) {
966                 x = st->cwidth*(st->text->curc-st->left) + TXT_OFFSET + TEXTXLOC - 4;
967         }
968         else {
969                 x = st->cwidth*(st->text->curc-st->left) + TXT_OFFSET - 4;
970         }
971         y = ar->winy - st->lheight*l - 2;
972
973         boxw = SUGG_LIST_WIDTH*st->cwidth + 20;
974         boxh = SUGG_LIST_SIZE*st->lheight + 8;
975         
976         UI_ThemeColor(TH_SHADE1);
977         glRecti(x-1, y+1, x+boxw+1, y-boxh-1);
978         UI_ThemeColor(TH_BACK);
979         glRecti(x, y, x+boxw, y-boxh);
980
981         /* Set the top 'item' of the visible list */
982         for(i=0, item=first; i<*top && item->next; i++, item=item->next);
983
984         for(i=0; i<SUGG_LIST_SIZE && item; i++, item=item->next) {
985
986                 y -= st->lheight;
987
988                 strncpy(str, item->name, SUGG_LIST_WIDTH);
989                 str[SUGG_LIST_WIDTH] = '\0';
990
991                 w = text_font_width(st, str);
992                 
993                 if(item == sel) {
994                         UI_ThemeColor(TH_SHADE2);
995                         glRecti(x+16, y-3, x+16+w, y+st->lheight-3);
996                 }
997                 b=1; /* b=1 colour block, text is default. b=0 no block, colour text */
998                 switch (item->type) {
999                         case 'k': UI_ThemeColor(TH_SYNTAX_B); b=0; break;
1000                         case 'm': UI_ThemeColor(TH_TEXT); break;
1001                         case 'f': UI_ThemeColor(TH_SYNTAX_L); break;
1002                         case 'v': UI_ThemeColor(TH_SYNTAX_N); break;
1003                         case '?': UI_ThemeColor(TH_TEXT); b=0; break;
1004                 }
1005                 if(b) {
1006                         glRecti(x+8, y+2, x+11, y+5);
1007                         UI_ThemeColor(TH_TEXT);
1008                 }
1009                 text_draw(st, str, 0, 0, 1, x+16, y-1, NULL);
1010
1011                 if(item == last) break;
1012         }
1013 }
1014
1015 /*********************** draw cursor ************************/
1016
1017 static void draw_cursor(SpaceText *st, ARegion *ar)
1018 {
1019         Text *text= st->text;
1020         int vcurl, vcurc, vsell, vselc, hidden=0;
1021         int offl, offc, x, y, w, i;
1022         
1023         /* Draw the selection */
1024         if(text->curl!=text->sell || text->curc!=text->selc) {
1025                 /* Convert all to view space character coordinates */
1026                 wrap_offset(st, ar, text->curl, text->curc, &offl, &offc);
1027                 vcurl = txt_get_span(text->lines.first, text->curl) - st->top + offl;
1028                 vcurc = get_char_pos(st, text->curl->line, text->curc) - st->left + offc;
1029                 wrap_offset(st, ar, text->sell, text->selc, &offl, &offc);
1030                 vsell = txt_get_span(text->lines.first, text->sell) - st->top + offl;
1031                 vselc = get_char_pos(st, text->sell->line, text->selc) - st->left + offc;
1032
1033                 if(vcurc<0) vcurc=0;
1034                 if(vselc<0) vselc=0, hidden=1;
1035                 
1036                 UI_ThemeColor(TH_SHADE2);
1037                 x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1038                 y= ar->winy-2;
1039
1040                 if(vcurl==vsell) {
1041                         y -= vcurl*st->lheight;
1042                         if(vcurc < vselc)
1043                                 glRecti(x+vcurc*st->cwidth-1, y, x+vselc*st->cwidth, y-st->lheight);
1044                         else
1045                                 glRecti(x+vselc*st->cwidth-1, y, x+vcurc*st->cwidth, y-st->lheight);
1046                 }
1047                 else {
1048                         int froml, fromc, tol, toc;
1049
1050                         if(vcurl < vsell) {
1051                                 froml= vcurl; tol= vsell;
1052                                 fromc= vcurc; toc= vselc;
1053                         }
1054                         else {
1055                                 froml= vsell; tol= vcurl;
1056                                 fromc= vselc; toc= vcurc;
1057                         }
1058
1059                         y -= froml*st->lheight;
1060                         glRecti(x+fromc*st->cwidth-1, y, ar->winx, y-st->lheight); y-=st->lheight;
1061                         for(i=froml+1; i<tol; i++)
1062                                 glRecti(x-4, y, ar->winx, y-st->lheight),  y-=st->lheight;
1063
1064                         glRecti(x-4, y, x+toc*st->cwidth, y-st->lheight);  y-=st->lheight;
1065                 }
1066         }
1067         else {
1068                 wrap_offset(st, ar, text->sell, text->selc, &offl, &offc);
1069                 vsell = txt_get_span(text->lines.first, text->sell) - st->top + offl;
1070                 vselc = get_char_pos(st, text->sell->line, text->selc) - st->left + offc;
1071
1072                 if(vselc<0) {
1073                         vselc= 0;
1074                         hidden= 1;
1075                 }
1076         }
1077
1078         if(!hidden) {
1079                 /* Draw the cursor itself (we draw the sel. cursor as this is the leading edge) */
1080                 x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1081                 x += vselc*st->cwidth;
1082                 y= ar->winy-2 - vsell*st->lheight;
1083                 
1084                 if(st->overwrite) {
1085                         char ch= text->sell->line[text->selc];
1086                         if(!ch) ch= ' ';
1087                         w= st->cwidth;
1088                         UI_ThemeColor(TH_HILITE);
1089                         glRecti(x, y-st->lheight-1, x+w, y-st->lheight+1);
1090                 }
1091                 else {
1092                         UI_ThemeColor(TH_HILITE);
1093                         glRecti(x-1, y, x+1, y-st->lheight);
1094                 }
1095         }
1096 }
1097
1098 /******************* draw matching brackets *********************/
1099
1100 static void draw_brackets(SpaceText *st, ARegion *ar)
1101 {
1102         TextLine *startl, *endl, *linep;
1103         Text *text = st->text;
1104         int b, c, startc, endc, find, stack;
1105         int viewc, viewl, offl, offc, x, y;
1106         char ch;
1107
1108         if(!text->curl) return;
1109
1110         startl= text->curl;
1111         startc= text->curc;
1112         b= text_check_bracket(startl->line[startc]);
1113         if(b==0 && startc>0) b = text_check_bracket(startl->line[--startc]);
1114         if(b==0) return;
1115         
1116         linep= startl;
1117         c= startc;
1118         endl= NULL;
1119         endc= -1;
1120         find= -b;
1121         stack= 0;
1122
1123         if(b>0) {
1124                 /* opening bracket, search forward for close */
1125                 c++;
1126                 while(linep) {
1127                         while(c<linep->len) {
1128                                 b= text_check_bracket(linep->line[c]);
1129                                 if(b==find) {
1130                                         if(stack==0) {
1131                                                 endl= linep;
1132                                                 endc= c;
1133                                                 break;
1134                                         }
1135                                         stack--;
1136                                 }
1137                                 else if(b==-find) {
1138                                         stack++;
1139                                 }
1140                                 c++;
1141                         }
1142                         if(endl) break;
1143                         linep= linep->next;
1144                         c= 0;
1145                 }
1146         }
1147         else {
1148                 /* closing bracket, search backward for open */
1149                 c--;
1150                 while(linep) {
1151                         while(c>=0) {
1152                                 b= text_check_bracket(linep->line[c]);
1153                                 if(b==find) {
1154                                         if(stack==0) {
1155                                                 endl= linep;
1156                                                 endc= c;
1157                                                 break;
1158                                         }
1159                                         stack--;
1160                                 }
1161                                 else if(b==-find) {
1162                                         stack++;
1163                                 }
1164                                 c--;
1165                         }
1166                         if(endl) break;
1167                         linep= linep->prev;
1168                         if(linep) c= linep->len-1;
1169                 }
1170         }
1171
1172         if(!endl || endc==-1)
1173                 return;
1174
1175         UI_ThemeColor(TH_HILITE);       
1176         x= st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
1177         y= ar->winy - st->lheight;
1178
1179         /* draw opening bracket */
1180         ch= startl->line[startc];
1181         wrap_offset(st, ar, startl, startc, &offl, &offc);
1182         viewc= get_char_pos(st, startl->line, startc) - st->left + offc;
1183
1184         if(viewc >= 0){
1185                 viewl= txt_get_span(text->lines.first, startl) - st->top + offl;
1186
1187                 text_font_draw_character(st, x+viewc*st->cwidth, y-viewl*st->lheight, ch);
1188                 text_font_draw_character(st, x+viewc*st->cwidth+1, y-viewl*st->lheight, ch);
1189         }
1190
1191         /* draw closing bracket */
1192         ch= endl->line[endc];
1193         wrap_offset(st, ar, endl, endc, &offl, &offc);
1194         viewc= get_char_pos(st, endl->line, endc) - st->left + offc;
1195
1196         if(viewc >= 0) {
1197                 viewl= txt_get_span(text->lines.first, endl) - st->top + offl;
1198
1199                 text_font_draw_character(st, x+viewc*st->cwidth, y-viewl*st->lheight, ch);
1200                 text_font_draw_character(st, x+viewc*st->cwidth+1, y-viewl*st->lheight, ch);
1201         }
1202 }
1203
1204 /*********************** main area drawing *************************/
1205
1206 void draw_text_main(SpaceText *st, ARegion *ar)
1207 {
1208         Text *text= st->text;
1209         TextLine *tmp;
1210         rcti scroll;
1211         char linenr[12];
1212         int i, x, y, linecount= 0;
1213
1214         /* if no text, nothing to do */
1215         if(!text)
1216                 return;
1217         
1218         /* make sure all the positional pointers exist */
1219         if(!text->curl || !text->sell || !text->lines.first || !text->lines.last)
1220                 txt_clean_text(text);
1221         
1222         if(st->lheight) st->viewlines= (int)ar->winy/st->lheight;
1223         else st->viewlines= 0;
1224         
1225         /* update rects for scroll */
1226         calc_text_rcts(st, ar, &scroll);        /* scroll will hold the entire bar size */
1227
1228         /* update syntax formatting if needed */
1229         tmp= text->lines.first;
1230         for(i= 0; i<st->top && tmp; i++) {
1231                 if(st->showsyntax && !tmp->format)
1232                         txt_format_line(st, tmp, 0);
1233
1234                 tmp= tmp->next;
1235                 linecount++;
1236         }
1237
1238         text_font_begin(st);
1239         st->cwidth= BLF_fixed_width();
1240         st->cwidth= MAX2(st->cwidth, 1);
1241
1242         /* draw line numbers background */
1243         if(st->showlinenrs) {
1244                 st->linenrs_tot = (int)floor(log10((float)(linecount + st->viewlines))) + 1;
1245                 x= TXT_OFFSET + TEXTXLOC;
1246
1247                 UI_ThemeColor(TH_GRID);
1248                 glRecti((TXT_OFFSET-12), 0, (TXT_OFFSET-5) + TEXTXLOC, ar->winy - 2);
1249         }
1250         else {
1251                 st->linenrs_tot= 0; /* not used */
1252                 x= TXT_OFFSET;
1253         }
1254         y= ar->winy-st->lheight;
1255
1256         /* draw cursor */
1257         draw_cursor(st, ar);
1258
1259         /* draw the text */
1260         UI_ThemeColor(TH_TEXT);
1261
1262         for(i=0; y>0 && i<st->viewlines && tmp; i++, tmp= tmp->next) {
1263                 if(st->showsyntax && !tmp->format)
1264                         txt_format_line(st, tmp, 0);
1265
1266                 if(st->showlinenrs) {
1267                         /* draw line number */
1268                         if(tmp == text->curl)
1269                                 UI_ThemeColor(TH_HILITE);
1270                         else
1271                                 UI_ThemeColor(TH_TEXT);
1272
1273                         sprintf(linenr, "%d", i + linecount + 1);
1274                         /* itoa(i + linecount + 1, linenr, 10); */ /* not ansi-c :/ */
1275                         text_font_draw(st, TXT_OFFSET - 7, y, linenr);
1276
1277                         UI_ThemeColor(TH_TEXT);
1278                 }
1279
1280                 if(st->wordwrap) {
1281                         /* draw word wrapped text */
1282                         int lines = text_draw_wrapped(st, tmp->line, x, y, ar->winx-x, tmp->format);
1283                         y -= lines*st->lheight;
1284                 }
1285                 else {
1286                         /* draw unwrapped text */
1287                         text_draw(st, tmp->line, st->left, 0, 1, x, y, tmp->format);
1288                         y -= st->lheight;
1289                 }
1290         }
1291         
1292         /* draw other stuff */
1293         draw_brackets(st, ar);
1294         draw_markers(st, ar);
1295         glTranslatef(0.375f, 0.375f, 0.0f); /* XXX scroll requires exact pixel space */
1296         draw_textscroll(st, ar, &scroll);
1297         draw_documentation(st, ar);
1298         draw_suggestion_list(st, ar);
1299         
1300         text_font_end(st);
1301 }
1302
1303 /************************** update ***************************/
1304
1305 void text_update_character_width(SpaceText *st)
1306 {
1307         text_font_begin(st);
1308         st->cwidth= BLF_fixed_width();
1309         st->cwidth= MAX2(st->cwidth, 1);
1310         text_font_end(st);
1311 }
1312
1313 /* Moves the view to the cursor location,
1314   also used to make sure the view isnt outside the file */
1315 void text_update_cursor_moved(bContext *C)
1316 {
1317         ScrArea *sa= CTX_wm_area(C);
1318         SpaceText *st= CTX_wm_space_text(C);
1319         Text *text= st->text;
1320         ARegion *ar;
1321         int i, x, winx= 0;
1322
1323         if(!st) return;
1324
1325         for(ar=sa->regionbase.first; ar; ar= ar->next)
1326                 if(ar->regiontype==RGN_TYPE_WINDOW)
1327                         winx= ar->winx;
1328
1329         if(!text || !text->curl) return;
1330
1331         text_update_character_width(st);
1332
1333         i= txt_get_span(text->lines.first, text->sell);
1334         if(st->top+st->viewlines <= i || st->top > i)
1335                 st->top= i - st->viewlines/2;
1336         
1337         if(st->wordwrap) {
1338                 st->left= 0;
1339         }
1340         else {
1341                 x= text_draw(st, text->sell->line, st->left, text->selc, 0, 0, 0, NULL);
1342
1343                 if(x==0 || x>winx)
1344                         st->left= text->curc-0.5*winx/st->cwidth;
1345         }
1346
1347         if(st->top < 0) st->top= 0;
1348         if(st->left <0) st->left= 0;
1349 }
1350