Cleanup: style, use braces for editors
[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 /**
34  * Checks the specified source string for a Python built-in function name. This
35  * name must start at the beginning of the source string and must be followed by
36  * a non-identifier (see #text_check_identifier(char)) or null character.
37  *
38  * If a built-in function is found, the length of the matching name is returned.
39  * Otherwise, -1 is returned.
40  *
41  * See:
42  * http://docs.python.org/py3k/reference/lexical_analysis.html#keywords
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   /* Keep aligned args for readability. */
59   /* clang-format off */
60
61   if      (STR_LITERAL_STARTSWITH(string, "and",      len)) { i = len;
62   } else if (STR_LITERAL_STARTSWITH(string, "as",       len)) { i = len;
63   } else if (STR_LITERAL_STARTSWITH(string, "assert",   len)) { i = len;
64   } else if (STR_LITERAL_STARTSWITH(string, "async",    len)) { i = len;
65   } else if (STR_LITERAL_STARTSWITH(string, "await",    len)) { i = len;
66   } else if (STR_LITERAL_STARTSWITH(string, "break",    len)) { i = len;
67   } else if (STR_LITERAL_STARTSWITH(string, "continue", len)) { i = len;
68   } else if (STR_LITERAL_STARTSWITH(string, "del",      len)) { i = len;
69   } else if (STR_LITERAL_STARTSWITH(string, "elif",     len)) { i = len;
70   } else if (STR_LITERAL_STARTSWITH(string, "else",     len)) { i = len;
71   } else if (STR_LITERAL_STARTSWITH(string, "except",   len)) { i = len;
72   } else if (STR_LITERAL_STARTSWITH(string, "finally",  len)) { i = len;
73   } else if (STR_LITERAL_STARTSWITH(string, "for",      len)) { i = len;
74   } else if (STR_LITERAL_STARTSWITH(string, "from",     len)) { i = len;
75   } else if (STR_LITERAL_STARTSWITH(string, "global",   len)) { i = len;
76   } else if (STR_LITERAL_STARTSWITH(string, "if",       len)) { i = len;
77   } else if (STR_LITERAL_STARTSWITH(string, "import",   len)) { i = len;
78   } else if (STR_LITERAL_STARTSWITH(string, "in",       len)) { i = len;
79   } else if (STR_LITERAL_STARTSWITH(string, "is",       len)) { i = len;
80   } else if (STR_LITERAL_STARTSWITH(string, "lambda",   len)) { i = len;
81   } else if (STR_LITERAL_STARTSWITH(string, "nonlocal", len)) { i = len;
82   } else if (STR_LITERAL_STARTSWITH(string, "not",      len)) { i = len;
83   } else if (STR_LITERAL_STARTSWITH(string, "or",       len)) { i = len;
84   } else if (STR_LITERAL_STARTSWITH(string, "pass",     len)) { i = len;
85   } else if (STR_LITERAL_STARTSWITH(string, "raise",    len)) { i = len;
86   } else if (STR_LITERAL_STARTSWITH(string, "return",   len)) { i = len;
87   } else if (STR_LITERAL_STARTSWITH(string, "try",      len)) { i = len;
88   } else if (STR_LITERAL_STARTSWITH(string, "while",    len)) { i = len;
89   } else if (STR_LITERAL_STARTSWITH(string, "with",     len)) { i = len;
90   } else if (STR_LITERAL_STARTSWITH(string, "yield",    len)) { i = len;
91   } else {                                                      i = 0;
92 }
93
94   /* clang-format on */
95
96   /* If next source char is an identifier (eg. 'i' in "definite") no match */
97   if (i == 0 || text_check_identifier(string[i])) {
98     return -1;
99   }
100   return i;
101 }
102
103 /* Checks the specified source string for a Python special name. This name must
104  * start at the beginning of the source string and must be followed by a non-
105  * identifier (see text_check_identifier(char)) or null character.
106  *
107  * If a special name is found, the length of the matching name is returned.
108  * Otherwise, -1 is returned. */
109
110 static int txtfmt_py_find_specialvar(const char *string)
111 {
112   int i, len;
113
114   /* Keep aligned args for readability. */
115   /* clang-format off */
116
117   if      (STR_LITERAL_STARTSWITH(string, "def", len)) {   i = len;
118   } else if (STR_LITERAL_STARTSWITH(string, "class", len)) { i = len;
119   } else {                                                   i = 0;
120 }
121
122   /* clang-format on */
123
124   /* If next source char is an identifier (eg. 'i' in "definite") no match */
125   if (i == 0 || text_check_identifier(string[i])) {
126     return -1;
127   }
128   return i;
129 }
130
131 static int txtfmt_py_find_decorator(const char *string)
132 {
133   if (string[0] != '@') {
134     return -1;
135   }
136   if (!text_check_identifier(string[1])) {
137     return -1;
138   }
139   /* Interpret as matrix multiplication when followed by whitespace. */
140   if (text_check_whitespace(string[1])) {
141     return -1;
142   }
143
144   int i = 1;
145   while (text_check_identifier(string[i])) {
146     i++;
147   }
148   return i;
149 }
150
151 static int txtfmt_py_find_bool(const char *string)
152 {
153   int i, len;
154
155   /* Keep aligned args for readability. */
156   /* clang-format off */
157
158   if      (STR_LITERAL_STARTSWITH(string, "None",  len)) {  i = len;
159   } else if (STR_LITERAL_STARTSWITH(string, "True",  len)) {  i = len;
160   } else if (STR_LITERAL_STARTSWITH(string, "False", len)) {  i = len;
161   } else {                                                    i = 0;
162 }
163
164   /* clang-format on */
165
166   /* If next source char is an identifier (eg. 'i' in "Nonetheless") no match */
167   if (i == 0 || text_check_identifier(string[i])) {
168     return -1;
169   }
170   return i;
171 }
172
173 static char txtfmt_py_format_identifier(const char *str)
174 {
175   char fmt;
176
177   /* Keep aligned args for readability. */
178   /* clang-format off */
179
180   if      ((txtfmt_py_find_specialvar(str))   != -1) { fmt = FMT_TYPE_SPECIAL;
181   } else if ((txtfmt_py_find_builtinfunc(str))  != -1) { fmt = FMT_TYPE_KEYWORD;
182   } else if ((txtfmt_py_find_decorator(str))    != -1) { fmt = FMT_TYPE_RESERVED;
183   } else {                                               fmt = FMT_TYPE_DEFAULT;
184 }
185
186   /* clang-format on */
187   return fmt;
188 }
189
190 static void txtfmt_py_format_line(SpaceText *st, TextLine *line, const bool do_next)
191 {
192   FlattenString fs;
193   const char *str;
194   char *fmt;
195   char cont_orig, cont, find, prev = ' ';
196   int len, i;
197
198   /* Get continuation from previous line */
199   if (line->prev && line->prev->format != NULL) {
200     fmt = line->prev->format;
201     cont = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
202     BLI_assert((FMT_CONT_ALL & cont) == cont);
203   }
204   else {
205     cont = FMT_CONT_NOP;
206   }
207
208   /* Get original continuation from this line */
209   if (line->format != NULL) {
210     fmt = line->format;
211     cont_orig = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */
212     BLI_assert((FMT_CONT_ALL & cont_orig) == cont_orig);
213   }
214   else {
215     cont_orig = 0xFF;
216   }
217
218   len = flatten_string(st, &fs, line->line);
219   str = fs.buf;
220   if (!text_check_format_len(line, len)) {
221     flatten_string_free(&fs);
222     return;
223   }
224   fmt = line->format;
225
226   while (*str) {
227     /* Handle escape sequences by skipping both \ and next char */
228     if (*str == '\\') {
229       *fmt = prev;
230       fmt++;
231       str++;
232       if (*str == '\0') {
233         break;
234       }
235       *fmt = prev;
236       fmt++;
237       str += BLI_str_utf8_size_safe(str);
238       continue;
239     }
240     /* Handle continuations */
241     else if (cont) {
242       /* Triple strings ("""...""" or '''...''') */
243       if (cont & FMT_CONT_TRIPLE) {
244         find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
245         if (*str == find && *(str + 1) == find && *(str + 2) == find) {
246           *fmt = FMT_TYPE_STRING;
247           fmt++;
248           str++;
249           *fmt = FMT_TYPE_STRING;
250           fmt++;
251           str++;
252           cont = FMT_CONT_NOP;
253         }
254         /* Handle other strings */
255       }
256       else {
257         find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\'';
258         if (*str == find) {
259           cont = FMT_CONT_NOP;
260         }
261       }
262
263       *fmt = FMT_TYPE_STRING;
264       str += BLI_str_utf8_size_safe(str) - 1;
265     }
266     /* Not in a string... */
267     else {
268       /* Deal with comments first */
269       if (*str == '#') {
270         /* fill the remaining line */
271         text_format_fill(&str, &fmt, FMT_TYPE_COMMENT, len - (int)(fmt - line->format));
272       }
273       else if (*str == '"' || *str == '\'') {
274         /* Strings */
275         find = *str;
276         cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
277         if (*(str + 1) == find && *(str + 2) == find) {
278           *fmt = FMT_TYPE_STRING;
279           fmt++;
280           str++;
281           *fmt = FMT_TYPE_STRING;
282           fmt++;
283           str++;
284           cont |= FMT_CONT_TRIPLE;
285         }
286         *fmt = FMT_TYPE_STRING;
287       }
288       /* Whitespace (all ws. has been converted to spaces) */
289       else if (*str == ' ') {
290         *fmt = FMT_TYPE_WHITESPACE;
291       }
292       /* Numbers (digits not part of an identifier and periods followed by digits) */
293       else if ((prev != FMT_TYPE_DEFAULT && text_check_digit(*str)) ||
294                (*str == '.' && text_check_digit(*(str + 1)))) {
295         *fmt = FMT_TYPE_NUMERAL;
296       }
297       /* Booleans */
298       else if (prev != FMT_TYPE_DEFAULT && (i = txtfmt_py_find_bool(str)) != -1) {
299         if (i > 0) {
300           text_format_fill_ascii(&str, &fmt, FMT_TYPE_NUMERAL, i);
301         }
302         else {
303           str += BLI_str_utf8_size_safe(str) - 1;
304           *fmt = FMT_TYPE_DEFAULT;
305         }
306       }
307       /* Punctuation */
308       else if ((*str != '@') && text_check_delim(*str)) {
309         *fmt = FMT_TYPE_SYMBOL;
310       }
311       /* Identifiers and other text (no previous ws. or delims. so text continues) */
312       else if (prev == FMT_TYPE_DEFAULT) {
313         str += BLI_str_utf8_size_safe(str) - 1;
314         *fmt = FMT_TYPE_DEFAULT;
315       }
316       /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
317       else {
318         /* Keep aligned args for readability. */
319         /* clang-format off */
320
321         /* Special vars(v) or built-in keywords(b) */
322         /* keep in sync with 'txtfmt_py_format_identifier()' */
323         if      ((i = txtfmt_py_find_specialvar(str))   != -1) { prev = FMT_TYPE_SPECIAL;
324         } else if ((i = txtfmt_py_find_builtinfunc(str))  != -1) { prev = FMT_TYPE_KEYWORD;
325         } else if ((i = txtfmt_py_find_decorator(str))    != -1) { prev = FMT_TYPE_DIRECTIVE;
326 }
327
328         /* clang-format on */
329
330         if (i > 0) {
331           if (prev == FMT_TYPE_DIRECTIVE) { /* can contain utf8 */
332             text_format_fill(&str, &fmt, prev, i);
333           }
334           else {
335             text_format_fill_ascii(&str, &fmt, prev, i);
336           }
337         }
338         else {
339           str += BLI_str_utf8_size_safe(str) - 1;
340           *fmt = FMT_TYPE_DEFAULT;
341         }
342       }
343     }
344     prev = *fmt;
345     fmt++;
346     str++;
347   }
348
349   /* Terminate and add continuation char */
350   *fmt = '\0';
351   fmt++;
352   *fmt = cont;
353
354   /* If continuation has changed and we're allowed, process the next line */
355   if (cont != cont_orig && do_next && line->next) {
356     txtfmt_py_format_line(st, line->next, do_next);
357   }
358
359   flatten_string_free(&fs);
360 }
361
362 void ED_text_format_register_py(void)
363 {
364   static TextFormatType tft = {NULL};
365   static const char *ext[] = {"py", NULL};
366
367   tft.format_identifier = txtfmt_py_format_identifier;
368   tft.format_line = txtfmt_py_format_line;
369   tft.ext = ext;
370
371   ED_text_format_register(&tft);
372 }