Merge branch 'master' into blender2.8
[blender.git] / source / blender / editors / space_text / text_format_py.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  * ***** END GPL LICENSE BLOCK *****
18  */
19
20 /** \file blender/editors/space_text/text_format_py.c
21  *  \ingroup sptext
22  */
23
24 #include <string.h>
25
26 #include "BLI_blenlib.h"
27
28 #include "DNA_text_types.h"
29 #include "DNA_space_types.h"
30
31 #include "BKE_text.h"
32
33 #include "text_format.h"
34
35 /* *** Local Functions (for format_line) *** */
36
37 /* Checks the specified source string for a Python built-in function name. This
38  * name must start at the beginning of the source string and must be followed by
39  * a non-identifier (see text_check_identifier(char)) or null character.
40  *
41  * If a built-in function is found, the length of the matching name is returned.
42  * Otherwise, -1 is returned.
43  *
44  * See:
45  * http://docs.python.org/py3k/reference/lexical_analysis.html#keywords
46  */
47
48 static int txtfmt_py_find_builtinfunc(const char *string)
49 {
50         int i, len;
51         /* list is from...
52          * ", ".join(['"%s"' % kw
53          *            for kw in  __import__("keyword").kwlist
54          *            if kw not in {"False", "None", "True", "def", "class"}])
55          *
56          * ... and for this code:
57          * print("\n".join(['else if (STR_LITERAL_STARTSWITH(string, "%s", len)) i = len;' % kw
58          *                  for kw in  __import__("keyword").kwlist
59          *                  if kw not in {"False", "None", "True", "def", "class"}]))
60          */
61
62         if      (STR_LITERAL_STARTSWITH(string, "and",      len)) i = len;
63         else if (STR_LITERAL_STARTSWITH(string, "as",       len)) i = len;
64         else if (STR_LITERAL_STARTSWITH(string, "assert",   len)) i = len;
65         else if (STR_LITERAL_STARTSWITH(string, "break",    len)) i = len;
66         else if (STR_LITERAL_STARTSWITH(string, "continue", len)) i = len;
67         else if (STR_LITERAL_STARTSWITH(string, "del",      len)) i = len;
68         else if (STR_LITERAL_STARTSWITH(string, "elif",     len)) i = len;
69         else if (STR_LITERAL_STARTSWITH(string, "else",     len)) i = len;
70         else if (STR_LITERAL_STARTSWITH(string, "except",   len)) i = len;
71         else if (STR_LITERAL_STARTSWITH(string, "finally",  len)) i = len;
72         else if (STR_LITERAL_STARTSWITH(string, "for",      len)) i = len;
73         else if (STR_LITERAL_STARTSWITH(string, "from",     len)) i = len;
74         else if (STR_LITERAL_STARTSWITH(string, "global",   len)) i = len;
75         else if (STR_LITERAL_STARTSWITH(string, "if",       len)) i = len;
76         else if (STR_LITERAL_STARTSWITH(string, "import",   len)) i = len;
77         else if (STR_LITERAL_STARTSWITH(string, "in",       len)) i = len;
78         else if (STR_LITERAL_STARTSWITH(string, "is",       len)) i = len;
79         else if (STR_LITERAL_STARTSWITH(string, "lambda",   len)) i = len;
80         else if (STR_LITERAL_STARTSWITH(string, "nonlocal", len)) i = len;
81         else if (STR_LITERAL_STARTSWITH(string, "not",      len)) i = len;
82         else if (STR_LITERAL_STARTSWITH(string, "or",       len)) i = len;
83         else if (STR_LITERAL_STARTSWITH(string, "pass",     len)) i = len;
84         else if (STR_LITERAL_STARTSWITH(string, "raise",    len)) i = len;
85         else if (STR_LITERAL_STARTSWITH(string, "return",   len)) i = len;
86         else if (STR_LITERAL_STARTSWITH(string, "try",      len)) i = len;
87         else if (STR_LITERAL_STARTSWITH(string, "while",    len)) i = len;
88         else if (STR_LITERAL_STARTSWITH(string, "with",     len)) i = len;
89         else if (STR_LITERAL_STARTSWITH(string, "yield",    len)) i = len;
90         else                                                      i = 0;
91
92         /* If next source char is an identifier (eg. 'i' in "definate") no match */
93         if (i == 0 || text_check_identifier(string[i]))
94                 return -1;
95         return i;
96 }
97
98 /* Checks the specified source string for a Python special name. This name must
99  * start at the beginning of the source string and must be followed by a non-
100  * identifier (see text_check_identifier(char)) or null character.
101  *
102  * If a special name is found, the length of the matching name is returned.
103  * Otherwise, -1 is returned. */
104
105 static int txtfmt_py_find_specialvar(const char *string)
106 {
107         int i, len;
108
109         if      (STR_LITERAL_STARTSWITH(string, "def", len))   i = len;
110         else if (STR_LITERAL_STARTSWITH(string, "class", len)) i = len;
111         else                                                   i = 0;
112
113         /* If next source char is an identifier (eg. 'i' in "definate") no match */
114         if (i == 0 || text_check_identifier(string[i]))
115                 return -1;
116         return i;
117 }
118
119 static int txtfmt_py_find_decorator(const char *string)
120 {
121         if (string[0] == '@') {
122                 int i = 1;
123                 /* Whitespace is ok '@  foo' */
124                 while (text_check_whitespace(string[i])) {
125                         i++;
126                 }
127                 while (text_check_identifier(string[i])) {
128                         i++;
129                 }
130                 return i;
131         }
132         return -1;
133 }
134
135 static int txtfmt_py_find_bool(const char *string)
136 {
137         int i, len;
138
139         if      (STR_LITERAL_STARTSWITH(string, "None",  len))  i = len;
140         else if (STR_LITERAL_STARTSWITH(string, "True",  len))  i = len;
141         else if (STR_LITERAL_STARTSWITH(string, "False", len))  i = len;
142         else                                                    i = 0;
143
144         /* If next source char is an identifier (eg. 'i' in "Nonetheless") no match */
145         if (i == 0 || text_check_identifier(string[i]))
146                 return -1;
147         return i;
148 }
149
150 static char txtfmt_py_format_identifier(const char *str)
151 {
152         char fmt;
153         if      ((txtfmt_py_find_specialvar(str))   != -1) fmt = FMT_TYPE_SPECIAL;
154         else if ((txtfmt_py_find_builtinfunc(str))  != -1) fmt = FMT_TYPE_KEYWORD;
155         else if ((txtfmt_py_find_decorator(str))    != -1) fmt = FMT_TYPE_RESERVED;
156         else                                               fmt = FMT_TYPE_DEFAULT;
157         return fmt;
158 }
159
160 static void txtfmt_py_format_line(SpaceText *st, TextLine *line, const bool 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                         /* Triple strings ("""...""" or '''...''') */
207                         if (cont & FMT_CONT_TRIPLE) {
208                                 find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
209                                 if (*str == find && *(str + 1) == find && *(str + 2) == find) {
210                                         *fmt = FMT_TYPE_STRING; fmt++; str++;
211                                         *fmt = FMT_TYPE_STRING; fmt++; str++;
212                                         cont = FMT_CONT_NOP;
213                                 }
214                                 /* Handle other strings */
215                         }
216                         else {
217                                 find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
218                                 if (*str == find) cont = FMT_CONT_NOP;
219                         }
220
221                         *fmt = FMT_TYPE_STRING;
222                         str += BLI_str_utf8_size_safe(str) - 1;
223                 }
224                 /* Not in a string... */
225                 else {
226                         /* Deal with comments first */
227                         if (*str == '#') {
228                                 /* fill the remaining line */
229                                 text_format_fill(&str, &fmt, FMT_TYPE_COMMENT, len - (int)(fmt - line->format));
230                         }
231                         else if (*str == '"' || *str == '\'') {
232                                 /* Strings */
233                                 find = *str;
234                                 cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
235                                 if (*(str + 1) == find && *(str + 2) == find) {
236                                         *fmt = FMT_TYPE_STRING; fmt++; str++;
237                                         *fmt = FMT_TYPE_STRING; fmt++; str++;
238                                         cont |= FMT_CONT_TRIPLE;
239                                 }
240                                 *fmt = FMT_TYPE_STRING;
241                         }
242                         /* Whitespace (all ws. has been converted to spaces) */
243                         else if (*str == ' ') {
244                                 *fmt = FMT_TYPE_WHITESPACE;
245                         }
246                         /* Numbers (digits not part of an identifier and periods followed by digits) */
247                         else if ((prev != FMT_TYPE_DEFAULT && text_check_digit(*str)) ||
248                                  (*str == '.' && text_check_digit(*(str + 1))))
249                         {
250                                 *fmt = FMT_TYPE_NUMERAL;
251                         }
252                         /* Booleans */
253                         else if (prev != FMT_TYPE_DEFAULT && (i = txtfmt_py_find_bool(str)) != -1) {
254                                 if (i > 0) {
255                                         text_format_fill_ascii(&str, &fmt, FMT_TYPE_NUMERAL, i);
256                                 }
257                                 else {
258                                         str += BLI_str_utf8_size_safe(str) - 1;
259                                         *fmt = FMT_TYPE_DEFAULT;
260                                 }
261                         }
262                         /* Punctuation */
263                         else if ((*str != '@') && text_check_delim(*str)) {
264                                 *fmt = FMT_TYPE_SYMBOL;
265                         }
266                         /* Identifiers and other text (no previous ws. or delims. so text continues) */
267                         else if (prev == FMT_TYPE_DEFAULT) {
268                                 str += BLI_str_utf8_size_safe(str) - 1;
269                                 *fmt = FMT_TYPE_DEFAULT;
270                         }
271                         /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
272                         else {
273                                 /* Special vars(v) or built-in keywords(b) */
274                                 /* keep in sync with 'txtfmt_py_format_identifier()' */
275                                 if      ((i = txtfmt_py_find_specialvar(str))   != -1) prev = FMT_TYPE_SPECIAL;
276                                 else if ((i = txtfmt_py_find_builtinfunc(str))  != -1) prev = FMT_TYPE_KEYWORD;
277                                 else if ((i = txtfmt_py_find_decorator(str))    != -1) prev = FMT_TYPE_DIRECTIVE;
278
279                                 if (i > 0) {
280                                         if (prev == FMT_TYPE_DIRECTIVE) {  /* can contain utf8 */
281                                                 text_format_fill(&str, &fmt, prev, i);
282                                         }
283                                         else {
284                                                 text_format_fill_ascii(&str, &fmt, prev, i);
285                                         }
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_py_format_line(st, line->next, do_next);
303         }
304
305         flatten_string_free(&fs);
306 }
307
308 void ED_text_format_register_py(void)
309 {
310         static TextFormatType tft = {NULL};
311         static const char *ext[] = {"py", NULL};
312
313         tft.format_identifier = txtfmt_py_format_identifier;
314         tft.format_line       = txtfmt_py_format_line;
315         tft.ext = ext;
316
317         ED_text_format_register(&tft);
318 }