Merge branch 'blender2.7'
[blender.git] / source / blender / editors / space_text / text_format_py.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * GNU General Public License for more details.
10  *
11  * You should have received a copy of the GNU General Public License
12  * along with this program; if not, write to the Free Software Foundation,
13  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14  */
15
16 /** \file
17  * \ingroup sptext
18  */
19
20 #include <string.h>
21
22 #include "BLI_blenlib.h"
23
24 #include "DNA_text_types.h"
25 #include "DNA_space_types.h"
26
27 #include "BKE_text.h"
28
29 #include "text_format.h"
30
31 /* *** Local Functions (for format_line) *** */
32
33 /* Checks the specified source string for a Python built-in function name. This
34  * name must start at the beginning of the source string and must be followed by
35  * a non-identifier (see text_check_identifier(char)) or null character.
36  *
37  * If a built-in function is found, the length of the matching name is returned.
38  * Otherwise, -1 is returned.
39  *
40  * See:
41  * http://docs.python.org/py3k/reference/lexical_analysis.html#keywords
42  */
43
44 static int txtfmt_py_find_builtinfunc(const char *string)
45 {
46         int i, len;
47         /* list is from...
48          * ", ".join(['"%s"' % kw
49          *            for kw in  __import__("keyword").kwlist
50          *            if kw not in {"False", "None", "True", "def", "class"}])
51          *
52          * ... and for this code:
53          * print("\n".join(['else if (STR_LITERAL_STARTSWITH(string, "%s", len)) i = len;' % kw
54          *                  for kw in  __import__("keyword").kwlist
55          *                  if kw not in {"False", "None", "True", "def", "class"}]))
56          */
57
58         if      (STR_LITERAL_STARTSWITH(string, "and",      len)) i = len;
59         else if (STR_LITERAL_STARTSWITH(string, "as",       len)) i = len;
60         else if (STR_LITERAL_STARTSWITH(string, "assert",   len)) i = len;
61         else if (STR_LITERAL_STARTSWITH(string, "async",    len)) i = len;
62         else if (STR_LITERAL_STARTSWITH(string, "await",    len)) i = len;
63         else if (STR_LITERAL_STARTSWITH(string, "break",    len)) i = len;
64         else if (STR_LITERAL_STARTSWITH(string, "continue", len)) i = len;
65         else if (STR_LITERAL_STARTSWITH(string, "del",      len)) i = len;
66         else if (STR_LITERAL_STARTSWITH(string, "elif",     len)) i = len;
67         else if (STR_LITERAL_STARTSWITH(string, "else",     len)) i = len;
68         else if (STR_LITERAL_STARTSWITH(string, "except",   len)) i = len;
69         else if (STR_LITERAL_STARTSWITH(string, "finally",  len)) i = len;
70         else if (STR_LITERAL_STARTSWITH(string, "for",      len)) i = len;
71         else if (STR_LITERAL_STARTSWITH(string, "from",     len)) i = len;
72         else if (STR_LITERAL_STARTSWITH(string, "global",   len)) i = len;
73         else if (STR_LITERAL_STARTSWITH(string, "if",       len)) i = len;
74         else if (STR_LITERAL_STARTSWITH(string, "import",   len)) i = len;
75         else if (STR_LITERAL_STARTSWITH(string, "in",       len)) i = len;
76         else if (STR_LITERAL_STARTSWITH(string, "is",       len)) i = len;
77         else if (STR_LITERAL_STARTSWITH(string, "lambda",   len)) i = len;
78         else if (STR_LITERAL_STARTSWITH(string, "nonlocal", len)) i = len;
79         else if (STR_LITERAL_STARTSWITH(string, "not",      len)) i = len;
80         else if (STR_LITERAL_STARTSWITH(string, "or",       len)) i = len;
81         else if (STR_LITERAL_STARTSWITH(string, "pass",     len)) i = len;
82         else if (STR_LITERAL_STARTSWITH(string, "raise",    len)) i = len;
83         else if (STR_LITERAL_STARTSWITH(string, "return",   len)) i = len;
84         else if (STR_LITERAL_STARTSWITH(string, "try",      len)) i = len;
85         else if (STR_LITERAL_STARTSWITH(string, "while",    len)) i = len;
86         else if (STR_LITERAL_STARTSWITH(string, "with",     len)) i = len;
87         else if (STR_LITERAL_STARTSWITH(string, "yield",    len)) i = len;
88         else                                                      i = 0;
89
90         /* If next source char is an identifier (eg. 'i' in "definite") no match */
91         if (i == 0 || text_check_identifier(string[i]))
92                 return -1;
93         return i;
94 }
95
96 /* Checks the specified source string for a Python special name. This name must
97  * start at the beginning of the source string and must be followed by a non-
98  * identifier (see text_check_identifier(char)) or null character.
99  *
100  * If a special name is found, the length of the matching name is returned.
101  * Otherwise, -1 is returned. */
102
103 static int txtfmt_py_find_specialvar(const char *string)
104 {
105         int i, len;
106
107         if      (STR_LITERAL_STARTSWITH(string, "def", len))   i = len;
108         else if (STR_LITERAL_STARTSWITH(string, "class", len)) i = len;
109         else                                                   i = 0;
110
111         /* If next source char is an identifier (eg. 'i' in "definite") no match */
112         if (i == 0 || text_check_identifier(string[i]))
113                 return -1;
114         return i;
115 }
116
117 static int txtfmt_py_find_decorator(const char *string)
118 {
119         if (string[0] != '@') {
120                 return -1;
121         }
122         if (!text_check_identifier(string[1])) {
123                 return -1;
124         }
125         /* Interpret as matrix multiplication when followed by whitespace. */
126         if (text_check_whitespace(string[1])) {
127                 return -1;
128         }
129
130         int i = 1;
131         while (text_check_identifier(string[i])) {
132                 i++;
133         }
134         return i;
135 }
136
137 static int txtfmt_py_find_bool(const char *string)
138 {
139         int i, len;
140
141         if      (STR_LITERAL_STARTSWITH(string, "None",  len))  i = len;
142         else if (STR_LITERAL_STARTSWITH(string, "True",  len))  i = len;
143         else if (STR_LITERAL_STARTSWITH(string, "False", len))  i = len;
144         else                                                    i = 0;
145
146         /* If next source char is an identifier (eg. 'i' in "Nonetheless") no match */
147         if (i == 0 || text_check_identifier(string[i]))
148                 return -1;
149         return i;
150 }
151
152 static char txtfmt_py_format_identifier(const char *str)
153 {
154         char fmt;
155         if      ((txtfmt_py_find_specialvar(str))   != -1) fmt = FMT_TYPE_SPECIAL;
156         else if ((txtfmt_py_find_builtinfunc(str))  != -1) fmt = FMT_TYPE_KEYWORD;
157         else if ((txtfmt_py_find_decorator(str))    != -1) fmt = FMT_TYPE_RESERVED;
158         else                                               fmt = FMT_TYPE_DEFAULT;
159         return fmt;
160 }
161
162 static void txtfmt_py_format_line(SpaceText *st, TextLine *line, const bool do_next)
163 {
164         FlattenString fs;
165         const char *str;
166         char *fmt;
167         char cont_orig, cont, find, prev = ' ';
168         int len, i;
169
170         /* Get continuation from previous line */
171         if (line->prev && line->prev->format != NULL) {
172                 fmt = line->prev->format;
173                 cont = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
174                 BLI_assert((FMT_CONT_ALL & cont) == cont);
175         }
176         else {
177                 cont = FMT_CONT_NOP;
178         }
179
180         /* Get original continuation from this line */
181         if (line->format != NULL) {
182                 fmt = line->format;
183                 cont_orig = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
184                 BLI_assert((FMT_CONT_ALL & cont_orig) == cont_orig);
185         }
186         else {
187                 cont_orig = 0xFF;
188         }
189
190         len = flatten_string(st, &fs, line->line);
191         str = fs.buf;
192         if (!text_check_format_len(line, len)) {
193                 flatten_string_free(&fs);
194                 return;
195         }
196         fmt = line->format;
197
198         while (*str) {
199                 /* Handle escape sequences by skipping both \ and next char */
200                 if (*str == '\\') {
201                         *fmt = prev; fmt++; str++;
202                         if (*str == '\0') break;
203                         *fmt = prev; fmt++; str += BLI_str_utf8_size_safe(str);
204                         continue;
205                 }
206                 /* Handle continuations */
207                 else if (cont) {
208                         /* Triple strings ("""...""" or '''...''') */
209                         if (cont & FMT_CONT_TRIPLE) {
210                                 find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
211                                 if (*str == find && *(str + 1) == find && *(str + 2) == find) {
212                                         *fmt = FMT_TYPE_STRING; fmt++; str++;
213                                         *fmt = FMT_TYPE_STRING; fmt++; str++;
214                                         cont = FMT_CONT_NOP;
215                                 }
216                                 /* Handle other strings */
217                         }
218                         else {
219                                 find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
220                                 if (*str == find) cont = FMT_CONT_NOP;
221                         }
222
223                         *fmt = FMT_TYPE_STRING;
224                         str += BLI_str_utf8_size_safe(str) - 1;
225                 }
226                 /* Not in a string... */
227                 else {
228                         /* Deal with comments first */
229                         if (*str == '#') {
230                                 /* fill the remaining line */
231                                 text_format_fill(&str, &fmt, FMT_TYPE_COMMENT, len - (int)(fmt - line->format));
232                         }
233                         else if (*str == '"' || *str == '\'') {
234                                 /* Strings */
235                                 find = *str;
236                                 cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
237                                 if (*(str + 1) == find && *(str + 2) == find) {
238                                         *fmt = FMT_TYPE_STRING; fmt++; str++;
239                                         *fmt = FMT_TYPE_STRING; fmt++; str++;
240                                         cont |= FMT_CONT_TRIPLE;
241                                 }
242                                 *fmt = FMT_TYPE_STRING;
243                         }
244                         /* Whitespace (all ws. has been converted to spaces) */
245                         else if (*str == ' ') {
246                                 *fmt = FMT_TYPE_WHITESPACE;
247                         }
248                         /* Numbers (digits not part of an identifier and periods followed by digits) */
249                         else if ((prev != FMT_TYPE_DEFAULT && text_check_digit(*str)) ||
250                                  (*str == '.' && text_check_digit(*(str + 1))))
251                         {
252                                 *fmt = FMT_TYPE_NUMERAL;
253                         }
254                         /* Booleans */
255                         else if (prev != FMT_TYPE_DEFAULT && (i = txtfmt_py_find_bool(str)) != -1) {
256                                 if (i > 0) {
257                                         text_format_fill_ascii(&str, &fmt, FMT_TYPE_NUMERAL, i);
258                                 }
259                                 else {
260                                         str += BLI_str_utf8_size_safe(str) - 1;
261                                         *fmt = FMT_TYPE_DEFAULT;
262                                 }
263                         }
264                         /* Punctuation */
265                         else if ((*str != '@') && text_check_delim(*str)) {
266                                 *fmt = FMT_TYPE_SYMBOL;
267                         }
268                         /* Identifiers and other text (no previous ws. or delims. so text continues) */
269                         else if (prev == FMT_TYPE_DEFAULT) {
270                                 str += BLI_str_utf8_size_safe(str) - 1;
271                                 *fmt = FMT_TYPE_DEFAULT;
272                         }
273                         /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
274                         else {
275                                 /* Special vars(v) or built-in keywords(b) */
276                                 /* keep in sync with 'txtfmt_py_format_identifier()' */
277                                 if      ((i = txtfmt_py_find_specialvar(str))   != -1) prev = FMT_TYPE_SPECIAL;
278                                 else if ((i = txtfmt_py_find_builtinfunc(str))  != -1) prev = FMT_TYPE_KEYWORD;
279                                 else if ((i = txtfmt_py_find_decorator(str))    != -1) prev = FMT_TYPE_DIRECTIVE;
280
281                                 if (i > 0) {
282                                         if (prev == FMT_TYPE_DIRECTIVE) {  /* can contain utf8 */
283                                                 text_format_fill(&str, &fmt, prev, i);
284                                         }
285                                         else {
286                                                 text_format_fill_ascii(&str, &fmt, prev, i);
287                                         }
288                                 }
289                                 else {
290                                         str += BLI_str_utf8_size_safe(str) - 1;
291                                         *fmt = FMT_TYPE_DEFAULT;
292                                 }
293                         }
294                 }
295                 prev = *fmt; fmt++; str++;
296         }
297
298         /* Terminate and add continuation char */
299         *fmt = '\0'; fmt++;
300         *fmt = cont;
301
302         /* If continuation has changed and we're allowed, process the next line */
303         if (cont != cont_orig && do_next && line->next) {
304                 txtfmt_py_format_line(st, line->next, do_next);
305         }
306
307         flatten_string_free(&fs);
308 }
309
310 void ED_text_format_register_py(void)
311 {
312         static TextFormatType tft = {NULL};
313         static const char *ext[] = {"py", NULL};
314
315         tft.format_identifier = txtfmt_py_format_identifier;
316         tft.format_line       = txtfmt_py_format_line;
317         tft.ext = ext;
318
319         ED_text_format_register(&tft);
320 }