fix own regression in 2.66 [#34610] Text editor: Syntax highlighting freezes
[blender.git] / source / blender / editors / space_text / text_format_lua.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_format_lua.c
28  *  \ingroup sptext
29  */
30
31 #include <string.h>
32
33 #include "BLI_blenlib.h"
34
35 #include "DNA_text_types.h"
36 #include "DNA_space_types.h"
37
38 #include "BKE_text.h"
39
40 #include "text_format.h"
41
42 /* *** Lua Keywords (for format_line) *** */
43
44 /* Checks the specified source string for a Lua keyword (minus boolean & 'nil'). 
45  * This name must start at the beginning of the source string and must be 
46  * followed by a non-identifier (see text_check_identifier(char)) or null char.
47  *
48  * If a keyword is found, the length of the matching word is returned.
49  * Otherwise, -1 is returned.
50  *
51  * See:
52  * http://www.lua.org/manual/5.1/manual.html#2.1
53  */
54
55 static int txtfmt_lua_find_keyword(const char *string)
56 {
57         int i, len;
58
59         if      (STR_LITERAL_STARTSWITH(string, "and",      len)) i = len;
60         else if (STR_LITERAL_STARTSWITH(string, "break",    len)) i = len;
61         else if (STR_LITERAL_STARTSWITH(string, "do",       len)) i = len;
62         else if (STR_LITERAL_STARTSWITH(string, "else",     len)) i = len;
63         else if (STR_LITERAL_STARTSWITH(string, "elseif",   len)) i = len;
64         else if (STR_LITERAL_STARTSWITH(string, "end",      len)) i = len;
65         else if (STR_LITERAL_STARTSWITH(string, "for",      len)) i = len;
66         else if (STR_LITERAL_STARTSWITH(string, "function", len)) i = len;
67         else if (STR_LITERAL_STARTSWITH(string, "if",       len)) i = len;
68         else if (STR_LITERAL_STARTSWITH(string, "in",       len)) i = len;
69         else if (STR_LITERAL_STARTSWITH(string, "local",    len)) i = len;
70         else if (STR_LITERAL_STARTSWITH(string, "not",      len)) i = len;
71         else if (STR_LITERAL_STARTSWITH(string, "or",       len)) i = len;
72         else if (STR_LITERAL_STARTSWITH(string, "repeat",   len)) i = len;
73         else if (STR_LITERAL_STARTSWITH(string, "return",   len)) i = len;
74         else if (STR_LITERAL_STARTSWITH(string, "then",     len)) i = len;
75         else if (STR_LITERAL_STARTSWITH(string, "until",    len)) i = len;
76         else if (STR_LITERAL_STARTSWITH(string, "while",    len)) i = len;
77         else                                                      i = 0;
78
79         /* If next source char is an identifier (eg. 'i' in "definate") no match */
80         if (i == 0 || text_check_identifier(string[i]))
81                 return -1;
82         return i;
83 }
84
85 /* Checks the specified source string for a Lua special name/function. This 
86  * name must start at the beginning of the source string and must be followed 
87  * by a non-identifier (see text_check_identifier(char)) or null character.
88  *
89  * If a special name is found, the length of the matching name is returned.
90  * Otherwise, -1 is returned. 
91  * 
92  * See:
93  * http://www.lua.org/manual/5.1/manual.html#5.1
94  */
95
96 static int txtfmt_lua_find_specialvar(const char *string)
97 {
98         int i, len;
99
100         if      (STR_LITERAL_STARTSWITH(string, "assert",           len))   i = len;
101         else if (STR_LITERAL_STARTSWITH(string, "collectgarbage",   len))   i = len;
102         else if (STR_LITERAL_STARTSWITH(string, "dofile",           len))   i = len;
103         else if (STR_LITERAL_STARTSWITH(string, "error",            len))   i = len;
104         else if (STR_LITERAL_STARTSWITH(string, "_G",               len))   i = len;
105         else if (STR_LITERAL_STARTSWITH(string, "getfenv",          len))   i = len;
106         else if (STR_LITERAL_STARTSWITH(string, "getmetatable",     len))   i = len;
107         else if (STR_LITERAL_STARTSWITH(string, "__index",          len))   i = len;
108         else if (STR_LITERAL_STARTSWITH(string, "ipairs",           len))   i = len;
109         else if (STR_LITERAL_STARTSWITH(string, "load",             len))   i = len;
110         else if (STR_LITERAL_STARTSWITH(string, "loadfile",         len))   i = len;
111         else if (STR_LITERAL_STARTSWITH(string, "loadstring",       len))   i = len;
112         else if (STR_LITERAL_STARTSWITH(string, "next",             len))   i = len;
113         else if (STR_LITERAL_STARTSWITH(string, "pairs",            len))   i = len;
114         else if (STR_LITERAL_STARTSWITH(string, "pcall",            len))   i = len;
115         else if (STR_LITERAL_STARTSWITH(string, "print",            len))   i = len;
116         else if (STR_LITERAL_STARTSWITH(string, "rawequal",         len))   i = len;
117         else if (STR_LITERAL_STARTSWITH(string, "rawget",           len))   i = len;
118         else if (STR_LITERAL_STARTSWITH(string, "rawset",           len))   i = len;
119         else if (STR_LITERAL_STARTSWITH(string, "select",           len))   i = len;
120         else if (STR_LITERAL_STARTSWITH(string, "setfenv",          len))   i = len;
121         else if (STR_LITERAL_STARTSWITH(string, "setmetatable",     len))   i = len;
122         else if (STR_LITERAL_STARTSWITH(string, "tonumber",         len))   i = len;
123         else if (STR_LITERAL_STARTSWITH(string, "tostring",         len))   i = len;
124         else if (STR_LITERAL_STARTSWITH(string, "type",             len))   i = len;
125         else if (STR_LITERAL_STARTSWITH(string, "unpack",           len))   i = len;
126         else if (STR_LITERAL_STARTSWITH(string, "_VERSION",         len))   i = len;
127         else if (STR_LITERAL_STARTSWITH(string, "xpcall",           len))   i = len;
128         else                                                i = 0;
129
130         /* If next source char is an identifier (eg. 'i' in "definate") no match */
131         if (i == 0 || text_check_identifier(string[i]))
132                 return -1;
133         return i;
134 }
135
136 static int txtfmt_lua_find_bool(const char *string)
137 {
138         int i, len;
139
140         if      (STR_LITERAL_STARTSWITH(string, "nil",   len))  i = len;
141         else if (STR_LITERAL_STARTSWITH(string, "true",  len))  i = len;
142         else if (STR_LITERAL_STARTSWITH(string, "false", len))  i = len;
143         else                                                    i = 0;
144
145         /* If next source char is an identifier (eg. 'i' in "Nonetheless") no match */
146         if (i == 0 || text_check_identifier(string[i]))
147                 return -1;
148         return i;
149 }
150
151 static char txtfmt_lua_format_identifier(const char *str)
152 {
153         char fmt;
154         if      ((txtfmt_lua_find_specialvar(str))  != -1) fmt = FMT_TYPE_SPECIAL;
155         else if ((txtfmt_lua_find_keyword(str))     != -1) fmt = FMT_TYPE_KEYWORD;
156         else                                               fmt = FMT_TYPE_DEFAULT;
157         return fmt;
158 }
159
160 static void txtfmt_lua_format_line(SpaceText *st, TextLine *line, const int do_next)
161 {
162         FlattenString fs;
163         const char *str;
164         char *fmt;
165         char cont_orig, cont, find, prev = ' ';
166         int len, i;
167
168         /* Get continuation from previous line */
169         if (line->prev && line->prev->format != NULL) {
170                 fmt = line->prev->format;
171                 cont = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
172                 BLI_assert((FMT_CONT_ALL & cont) == cont);
173         }
174         else {
175                 cont = FMT_CONT_NOP;
176         }
177
178         /* Get original continuation from this line */
179         if (line->format != NULL) {
180                 fmt = line->format;
181                 cont_orig = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
182                 BLI_assert((FMT_CONT_ALL & cont_orig) == cont_orig);
183         }
184         else {
185                 cont_orig = 0xFF;
186         }
187
188         len = flatten_string(st, &fs, line->line);
189         str = fs.buf;
190         if (!text_check_format_len(line, len)) {
191                 flatten_string_free(&fs);
192                 return;
193         }
194         fmt = line->format;
195
196         while (*str) {
197                 /* Handle escape sequences by skipping both \ and next char */
198                 if (*str == '\\') {
199                         *fmt = prev; fmt++; str++;
200                         if (*str == '\0') break;
201                         *fmt = prev; fmt++; str += BLI_str_utf8_size_safe(str);
202                         continue;
203                 }
204                 /* Handle continuations */
205                 else if (cont) {
206                         /* Multi-line comments */
207                         if (cont & FMT_CONT_COMMENT_C) {
208                                 if (*str == ']' && *(str + 1) == ']') {
209                                         *fmt = FMT_TYPE_COMMENT; fmt++; str++;
210                                         *fmt = FMT_TYPE_COMMENT;
211                                         cont = FMT_CONT_NOP;
212                                 }
213                                 else {
214                                         *fmt = FMT_TYPE_COMMENT;
215                                 }
216                                 /* Handle other comments */
217                         }
218                         else {
219                                 find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
220                                 if (*str == find) cont = 0;
221                                 *fmt = FMT_TYPE_STRING;
222                         }
223
224                         str += BLI_str_utf8_size_safe(str) - 1;
225                 }
226                 /* Not in a string... */
227                 else {
228                         /* Multi-line comments */
229                         if (*str == '-'       && *(str + 1) == '-' &&
230                             *(str + 2) == '[' && *(str + 3) == '[')
231                         {
232                                 cont = FMT_CONT_COMMENT_C;
233                                 *fmt = FMT_TYPE_COMMENT; fmt++; str++;
234                                 *fmt = FMT_TYPE_COMMENT; fmt++; str++;
235                                 *fmt = FMT_TYPE_COMMENT; fmt++; str++;
236                                 *fmt = FMT_TYPE_COMMENT;
237                         }
238                         /* Single line comment */
239                         else if (*str == '-' && *(str + 1) == '-') {
240                                 text_format_fill(&str, &fmt, FMT_TYPE_COMMENT, len - (int)(fmt - line->format));
241                         }
242                         else if (*str == '"' || *str == '\'') {
243                                 /* Strings */
244                                 find = *str;
245                                 cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
246                                 *fmt = FMT_TYPE_STRING;
247                         }
248                         /* Whitespace (all ws. has been converted to spaces) */
249                         else if (*str == ' ') {
250                                 *fmt = FMT_TYPE_WHITESPACE;
251                         }
252                         /* Numbers (digits not part of an identifier and periods followed by digits) */
253                         else if ((prev != FMT_TYPE_DEFAULT && text_check_digit(*str)) ||
254                                  (*str == '.' && text_check_digit(*(str + 1))))
255                         {
256                                 *fmt = FMT_TYPE_NUMERAL;
257                         }
258                         /* Booleans */
259                         else if (prev != FMT_TYPE_DEFAULT && (i = txtfmt_lua_find_bool(str)) != -1) {
260                                 if (i > 0) {
261                                         text_format_fill_ascii(&str, &fmt, FMT_TYPE_NUMERAL, i);
262                                 }
263                                 else {
264                                         str += BLI_str_utf8_size_safe(str) - 1;
265                                         *fmt = FMT_TYPE_DEFAULT;
266                                 }
267                         }
268                         /* Punctuation */
269                         else if ((*str != '#') && text_check_delim(*str)) {
270                                 *fmt = FMT_TYPE_SYMBOL;
271                         }
272                         /* Identifiers and other text (no previous ws. or delims. so text continues) */
273                         else if (prev == FMT_TYPE_DEFAULT) {
274                                 str += BLI_str_utf8_size_safe(str) - 1;
275                                 *fmt = FMT_TYPE_DEFAULT;
276                         }
277                         /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
278                         else {
279                                 /* Special vars(v) or built-in keywords(b) */
280                                 /* keep in sync with 'txtfmt_osl_format_identifier()' */
281                                 if      ((i = txtfmt_lua_find_specialvar(str))   != -1) prev = FMT_TYPE_SPECIAL;
282                                 else if ((i = txtfmt_lua_find_keyword(str))      != -1) prev = FMT_TYPE_KEYWORD;
283
284                                 if (i > 0) {
285                                         text_format_fill_ascii(&str, &fmt, prev, i);
286                                 }
287                                 else {
288                                         str += BLI_str_utf8_size_safe(str) - 1;
289                                         *fmt = FMT_TYPE_DEFAULT;
290                                 }
291                         }
292                 }
293                 prev = *fmt; fmt++; str++;
294         }
295
296         /* Terminate and add continuation char */
297         *fmt = '\0'; fmt++;
298         *fmt = cont;
299
300         /* If continuation has changed and we're allowed, process the next line */
301         if (cont != cont_orig && do_next && line->next) {
302                 txtfmt_lua_format_line(st, line->next, do_next);
303         }
304
305         flatten_string_free(&fs);
306 }
307
308 void ED_text_format_register_lua(void)
309 {
310         static TextFormatType tft = {0};
311         static const char *ext[] = {"lua", NULL};
312
313         tft.format_identifier = txtfmt_lua_format_identifier;
314         tft.format_line       = txtfmt_lua_format_line;
315         tft.ext = ext;
316
317         ED_text_format_register(&tft);
318 }