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  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup bke
22  */
23 
24 #include <stdlib.h> /* abort */
25 #include <string.h> /* strstr */
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <wctype.h>
29 
30 #include "MEM_guardedalloc.h"
31 
32 #include "BLI_fileops.h"
33 #include "BLI_listbase.h"
34 #include "BLI_path_util.h"
35 #include "BLI_string.h"
36 #include "BLI_string_cursor_utf8.h"
37 #include "BLI_string_utf8.h"
38 #include "BLI_utildefines.h"
39 
40 #include "BLT_translation.h"
41 
42 #include "DNA_constraint_types.h"
43 #include "DNA_material_types.h"
44 #include "DNA_node_types.h"
45 #include "DNA_object_types.h"
46 #include "DNA_scene_types.h"
47 #include "DNA_screen_types.h"
48 #include "DNA_space_types.h"
49 #include "DNA_text_types.h"
50 #include "DNA_userdef_types.h"
51 
52 #include "BKE_idtype.h"
53 #include "BKE_lib_id.h"
54 #include "BKE_main.h"
55 #include "BKE_node.h"
56 #include "BKE_text.h"
57 
58 #include "BLO_read_write.h"
59 
60 #ifdef WITH_PYTHON
61 #  include "BPY_extern.h"
62 #endif
63 
64 /* -------------------------------------------------------------------- */
65 /** \name Prototypes
66  * \{ */
67 
68 static void txt_pop_first(Text *text);
69 static void txt_pop_last(Text *text);
70 static void txt_delete_line(Text *text, TextLine *line);
71 static void txt_delete_sel(Text *text);
72 static void txt_make_dirty(Text *text);
73 
74 /** \} */
75 
76 /* -------------------------------------------------------------------- */
77 /** \name Text Data-Block
78  * \{ */
79 
text_init_data(ID * id)80 static void text_init_data(ID *id)
81 {
82   Text *text = (Text *)id;
83   TextLine *tmp;
84 
85   BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(text, id));
86 
87   text->filepath = NULL;
88 
89   text->flags = TXT_ISDIRTY | TXT_ISMEM;
90   if ((U.flag & USER_TXT_TABSTOSPACES_DISABLE) == 0) {
91     text->flags |= TXT_TABSTOSPACES;
92   }
93 
94   BLI_listbase_clear(&text->lines);
95 
96   tmp = (TextLine *)MEM_mallocN(sizeof(TextLine), "textline");
97   tmp->line = (char *)MEM_mallocN(1, "textline_string");
98   tmp->format = NULL;
99 
100   tmp->line[0] = 0;
101   tmp->len = 0;
102 
103   tmp->next = NULL;
104   tmp->prev = NULL;
105 
106   BLI_addhead(&text->lines, tmp);
107 
108   text->curl = text->lines.first;
109   text->curc = 0;
110   text->sell = text->lines.first;
111   text->selc = 0;
112 }
113 
114 /**
115  * Only copy internal data of Text ID from source
116  * to already allocated/initialized destination.
117  * You probably never want to use that directly,
118  * use #BKE_id_copy or #BKE_id_copy_ex for typical needs.
119  *
120  * WARNING! This function will not handle ID user count!
121  *
122  * \param flag: Copying options (see BKE_lib_id.h's LIB_ID_COPY_... flags for more).
123  */
text_copy_data(Main * UNUSED (bmain),ID * id_dst,const ID * id_src,const int UNUSED (flag))124 static void text_copy_data(Main *UNUSED(bmain),
125                            ID *id_dst,
126                            const ID *id_src,
127                            const int UNUSED(flag))
128 {
129   Text *text_dst = (Text *)id_dst;
130   const Text *text_src = (Text *)id_src;
131 
132   /* File name can be NULL. */
133   if (text_src->filepath) {
134     text_dst->filepath = BLI_strdup(text_src->filepath);
135   }
136 
137   text_dst->flags |= TXT_ISDIRTY;
138 
139   BLI_listbase_clear(&text_dst->lines);
140   text_dst->curl = text_dst->sell = NULL;
141   text_dst->compiled = NULL;
142 
143   /* Walk down, reconstructing. */
144   LISTBASE_FOREACH (TextLine *, line_src, &text_src->lines) {
145     TextLine *line_dst = MEM_mallocN(sizeof(*line_dst), __func__);
146 
147     line_dst->line = BLI_strdup(line_src->line);
148     line_dst->format = NULL;
149     line_dst->len = line_src->len;
150 
151     BLI_addtail(&text_dst->lines, line_dst);
152   }
153 
154   text_dst->curl = text_dst->sell = text_dst->lines.first;
155   text_dst->curc = text_dst->selc = 0;
156 }
157 
158 /** Free (or release) any data used by this text (does not free the text itself). */
text_free_data(ID * id)159 static void text_free_data(ID *id)
160 {
161   /* No animdata here. */
162   Text *text = (Text *)id;
163 
164   BKE_text_free_lines(text);
165 
166   MEM_SAFE_FREE(text->filepath);
167 #ifdef WITH_PYTHON
168   BPY_text_free_code(text);
169 #endif
170 }
171 
text_blend_write(BlendWriter * writer,ID * id,const void * id_address)172 static void text_blend_write(BlendWriter *writer, ID *id, const void *id_address)
173 {
174   Text *text = (Text *)id;
175 
176   /* Note: we are clearing local temp data here, *not* the flag in the actual 'real' ID. */
177   if ((text->flags & TXT_ISMEM) && (text->flags & TXT_ISEXT)) {
178     text->flags &= ~TXT_ISEXT;
179   }
180 
181   /* Clean up, important in undo case to reduce false detection of changed datablocks. */
182   text->compiled = NULL;
183 
184   /* write LibData */
185   BLO_write_id_struct(writer, Text, id_address, &text->id);
186   BKE_id_blend_write(writer, &text->id);
187 
188   if (text->filepath) {
189     BLO_write_string(writer, text->filepath);
190   }
191 
192   if (!(text->flags & TXT_ISEXT)) {
193     /* now write the text data, in two steps for optimization in the readfunction */
194     LISTBASE_FOREACH (TextLine *, tmp, &text->lines) {
195       BLO_write_struct(writer, TextLine, tmp);
196     }
197 
198     LISTBASE_FOREACH (TextLine *, tmp, &text->lines) {
199       BLO_write_raw(writer, tmp->len + 1, tmp->line);
200     }
201   }
202 }
203 
text_blend_read_data(BlendDataReader * reader,ID * id)204 static void text_blend_read_data(BlendDataReader *reader, ID *id)
205 {
206   Text *text = (Text *)id;
207   BLO_read_data_address(reader, &text->filepath);
208 
209   text->compiled = NULL;
210 
211 #if 0
212   if (text->flags & TXT_ISEXT) {
213     BKE_text_reload(text);
214   }
215   /* else { */
216 #endif
217 
218   BLO_read_list(reader, &text->lines);
219 
220   BLO_read_data_address(reader, &text->curl);
221   BLO_read_data_address(reader, &text->sell);
222 
223   LISTBASE_FOREACH (TextLine *, ln, &text->lines) {
224     BLO_read_data_address(reader, &ln->line);
225     ln->format = NULL;
226 
227     if (ln->len != (int)strlen(ln->line)) {
228       printf("Error loading text, line lengths differ\n");
229       ln->len = strlen(ln->line);
230     }
231   }
232 
233   text->flags = (text->flags) & ~TXT_ISEXT;
234 
235   id_us_ensure_real(&text->id);
236 }
237 
238 IDTypeInfo IDType_ID_TXT = {
239     .id_code = ID_TXT,
240     .id_filter = FILTER_ID_TXT,
241     .main_listbase_index = INDEX_ID_TXT,
242     .struct_size = sizeof(Text),
243     .name = "Text",
244     .name_plural = "texts",
245     .translation_context = BLT_I18NCONTEXT_ID_TEXT,
246     .flags = IDTYPE_FLAGS_NO_ANIMDATA,
247 
248     .init_data = text_init_data,
249     .copy_data = text_copy_data,
250     .free_data = text_free_data,
251     .make_local = NULL,
252     .foreach_id = NULL,
253     .foreach_cache = NULL,
254 
255     .blend_write = text_blend_write,
256     .blend_read_data = text_blend_read_data,
257     .blend_read_lib = NULL,
258     .blend_read_expand = NULL,
259 };
260 
261 /** \} */
262 
263 /* -------------------------------------------------------------------- */
264 /** \name Text Add, Free, Validation
265  * \{ */
266 
267 /**
268  * \note caller must handle `compiled` member.
269  */
BKE_text_free_lines(Text * text)270 void BKE_text_free_lines(Text *text)
271 {
272   for (TextLine *tmp = text->lines.first, *tmp_next; tmp; tmp = tmp_next) {
273     tmp_next = tmp->next;
274     MEM_freeN(tmp->line);
275     if (tmp->format) {
276       MEM_freeN(tmp->format);
277     }
278     MEM_freeN(tmp);
279   }
280 
281   BLI_listbase_clear(&text->lines);
282 
283   text->curl = text->sell = NULL;
284 }
285 
BKE_text_add(Main * bmain,const char * name)286 Text *BKE_text_add(Main *bmain, const char *name)
287 {
288   Text *ta;
289 
290   ta = BKE_id_new(bmain, ID_TXT, name);
291   /* Texts always have 'real' user (see also read code). */
292   id_us_ensure_real(&ta->id);
293 
294   return ta;
295 }
296 
297 /* this function replaces extended ascii characters */
298 /* to a valid utf-8 sequences */
txt_extended_ascii_as_utf8(char ** str)299 int txt_extended_ascii_as_utf8(char **str)
300 {
301   ptrdiff_t bad_char, i = 0;
302   const ptrdiff_t length = (ptrdiff_t)strlen(*str);
303   int added = 0;
304 
305   while ((*str)[i]) {
306     if ((bad_char = BLI_utf8_invalid_byte(*str + i, length - i)) == -1) {
307       break;
308     }
309 
310     added++;
311     i += bad_char + 1;
312   }
313 
314   if (added != 0) {
315     char *newstr = MEM_mallocN(length + added + 1, "text_line");
316     ptrdiff_t mi = 0;
317     i = 0;
318 
319     while ((*str)[i]) {
320       if ((bad_char = BLI_utf8_invalid_byte((*str) + i, length - i)) == -1) {
321         memcpy(newstr + mi, (*str) + i, length - i + 1);
322         break;
323       }
324 
325       memcpy(newstr + mi, (*str) + i, bad_char);
326 
327       BLI_str_utf8_from_unicode((*str)[i + bad_char], newstr + mi + bad_char);
328       i += bad_char + 1;
329       mi += bad_char + 2;
330     }
331     newstr[length + added] = '\0';
332     MEM_freeN(*str);
333     *str = newstr;
334   }
335 
336   return added;
337 }
338 
339 // this function removes any control characters from
340 // a textline and fixes invalid utf-8 sequences
341 
cleanup_textline(TextLine * tl)342 static void cleanup_textline(TextLine *tl)
343 {
344   int i;
345 
346   for (i = 0; i < tl->len; i++) {
347     if (tl->line[i] < ' ' && tl->line[i] != '\t') {
348       memmove(tl->line + i, tl->line + i + 1, tl->len - i);
349       tl->len--;
350       i--;
351     }
352   }
353   tl->len += txt_extended_ascii_as_utf8(&tl->line);
354 }
355 
356 /**
357  * used for load and reload (unlike txt_insert_buf)
358  * assumes all fields are empty
359  */
text_from_buf(Text * text,const unsigned char * buffer,const int len)360 static void text_from_buf(Text *text, const unsigned char *buffer, const int len)
361 {
362   int i, llen, lines_count;
363 
364   BLI_assert(BLI_listbase_is_empty(&text->lines));
365 
366   llen = 0;
367   lines_count = 0;
368   for (i = 0; i < len; i++) {
369     if (buffer[i] == '\n') {
370       TextLine *tmp;
371 
372       tmp = (TextLine *)MEM_mallocN(sizeof(TextLine), "textline");
373       tmp->line = (char *)MEM_mallocN(llen + 1, "textline_string");
374       tmp->format = NULL;
375 
376       if (llen) {
377         memcpy(tmp->line, &buffer[i - llen], llen);
378       }
379       tmp->line[llen] = 0;
380       tmp->len = llen;
381 
382       cleanup_textline(tmp);
383 
384       BLI_addtail(&text->lines, tmp);
385       lines_count += 1;
386 
387       llen = 0;
388       continue;
389     }
390     llen++;
391   }
392 
393   /* create new line in cases:
394    * - rest of line (if last line in file hasn't got \n terminator).
395    *   in this case content of such line would be used to fill text line buffer
396    * - file is empty. in this case new line is needed to start editing from.
397    * - last character in buffer is \n. in this case new line is needed to
398    *   deal with newline at end of file. (see T28087) (sergey) */
399   if (llen != 0 || lines_count == 0 || buffer[len - 1] == '\n') {
400     TextLine *tmp;
401 
402     tmp = (TextLine *)MEM_mallocN(sizeof(TextLine), "textline");
403     tmp->line = (char *)MEM_mallocN(llen + 1, "textline_string");
404     tmp->format = NULL;
405 
406     if (llen) {
407       memcpy(tmp->line, &buffer[i - llen], llen);
408     }
409 
410     tmp->line[llen] = 0;
411     tmp->len = llen;
412 
413     cleanup_textline(tmp);
414 
415     BLI_addtail(&text->lines, tmp);
416     /* lines_count += 1; */ /* UNUSED */
417   }
418 
419   text->curl = text->sell = text->lines.first;
420   text->curc = text->selc = 0;
421 }
422 
BKE_text_reload(Text * text)423 bool BKE_text_reload(Text *text)
424 {
425   unsigned char *buffer;
426   size_t buffer_len;
427   char filepath_abs[FILE_MAX];
428   BLI_stat_t st;
429 
430   if (!text->filepath) {
431     return false;
432   }
433 
434   BLI_strncpy(filepath_abs, text->filepath, FILE_MAX);
435   BLI_path_abs(filepath_abs, ID_BLEND_PATH_FROM_GLOBAL(&text->id));
436 
437   buffer = BLI_file_read_text_as_mem(filepath_abs, 0, &buffer_len);
438   if (buffer == NULL) {
439     return false;
440   }
441 
442   /* free memory: */
443   BKE_text_free_lines(text);
444   txt_make_dirty(text);
445 
446   /* clear undo buffer */
447   if (BLI_stat(filepath_abs, &st) != -1) {
448     text->mtime = st.st_mtime;
449   }
450   else {
451     text->mtime = 0;
452   }
453 
454   text_from_buf(text, buffer, buffer_len);
455 
456   MEM_freeN(buffer);
457   return true;
458 }
459 
BKE_text_load_ex(Main * bmain,const char * file,const char * relpath,const bool is_internal)460 Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const bool is_internal)
461 {
462   unsigned char *buffer;
463   size_t buffer_len;
464   Text *ta;
465   char filepath_abs[FILE_MAX];
466   BLI_stat_t st;
467 
468   BLI_strncpy(filepath_abs, file, FILE_MAX);
469   if (relpath) { /* can be NULL (bg mode) */
470     BLI_path_abs(filepath_abs, relpath);
471   }
472 
473   buffer = BLI_file_read_text_as_mem(filepath_abs, 0, &buffer_len);
474   if (buffer == NULL) {
475     return NULL;
476   }
477 
478   ta = BKE_libblock_alloc(bmain, ID_TXT, BLI_path_basename(filepath_abs), 0);
479   /* Texts always have 'real' user (see also read code). */
480   id_us_ensure_real(&ta->id);
481 
482   BLI_listbase_clear(&ta->lines);
483   ta->curl = ta->sell = NULL;
484 
485   if ((U.flag & USER_TXT_TABSTOSPACES_DISABLE) == 0) {
486     ta->flags = TXT_TABSTOSPACES;
487   }
488 
489   if (is_internal == false) {
490     ta->filepath = MEM_mallocN(strlen(file) + 1, "text_name");
491     strcpy(ta->filepath, file);
492   }
493   else {
494     ta->flags |= TXT_ISMEM | TXT_ISDIRTY;
495   }
496 
497   /* clear undo buffer */
498   if (BLI_stat(filepath_abs, &st) != -1) {
499     ta->mtime = st.st_mtime;
500   }
501   else {
502     ta->mtime = 0;
503   }
504 
505   text_from_buf(ta, buffer, buffer_len);
506 
507   MEM_freeN(buffer);
508 
509   return ta;
510 }
511 
BKE_text_load(Main * bmain,const char * file,const char * relpath)512 Text *BKE_text_load(Main *bmain, const char *file, const char *relpath)
513 {
514   return BKE_text_load_ex(bmain, file, relpath, false);
515 }
516 
BKE_text_clear(Text * text)517 void BKE_text_clear(Text *text) /* called directly from rna */
518 {
519   txt_sel_all(text);
520   txt_delete_sel(text);
521   txt_make_dirty(text);
522 }
523 
BKE_text_write(Text * text,const char * str)524 void BKE_text_write(Text *text, const char *str) /* called directly from rna */
525 {
526   txt_insert_buf(text, str);
527   txt_move_eof(text, 0);
528   txt_make_dirty(text);
529 }
530 
531 /* returns 0 if file on disk is the same or Text is in memory only
532  * returns 1 if file has been modified on disk since last local edit
533  * returns 2 if file on disk has been deleted
534  * -1 is returned if an error occurs */
535 
BKE_text_file_modified_check(Text * text)536 int BKE_text_file_modified_check(Text *text)
537 {
538   BLI_stat_t st;
539   int result;
540   char file[FILE_MAX];
541 
542   if (!text->filepath) {
543     return 0;
544   }
545 
546   BLI_strncpy(file, text->filepath, FILE_MAX);
547   BLI_path_abs(file, ID_BLEND_PATH_FROM_GLOBAL(&text->id));
548 
549   if (!BLI_exists(file)) {
550     return 2;
551   }
552 
553   result = BLI_stat(file, &st);
554 
555   if (result == -1) {
556     return -1;
557   }
558 
559   if ((st.st_mode & S_IFMT) != S_IFREG) {
560     return -1;
561   }
562 
563   if (st.st_mtime > text->mtime) {
564     return 1;
565   }
566 
567   return 0;
568 }
569 
BKE_text_file_modified_ignore(Text * text)570 void BKE_text_file_modified_ignore(Text *text)
571 {
572   BLI_stat_t st;
573   int result;
574   char file[FILE_MAX];
575 
576   if (!text->filepath) {
577     return;
578   }
579 
580   BLI_strncpy(file, text->filepath, FILE_MAX);
581   BLI_path_abs(file, ID_BLEND_PATH_FROM_GLOBAL(&text->id));
582 
583   if (!BLI_exists(file)) {
584     return;
585   }
586 
587   result = BLI_stat(file, &st);
588 
589   if (result == -1 || (st.st_mode & S_IFMT) != S_IFREG) {
590     return;
591   }
592 
593   text->mtime = st.st_mtime;
594 }
595 
596 /** \} */
597 
598 /* -------------------------------------------------------------------- */
599 /** \name Editing Utility Functions
600  * \{ */
601 
make_new_line(TextLine * line,char * newline)602 static void make_new_line(TextLine *line, char *newline)
603 {
604   if (line->line) {
605     MEM_freeN(line->line);
606   }
607   if (line->format) {
608     MEM_freeN(line->format);
609   }
610 
611   line->line = newline;
612   line->len = strlen(newline);
613   line->format = NULL;
614 }
615 
txt_new_line(const char * str)616 static TextLine *txt_new_line(const char *str)
617 {
618   TextLine *tmp;
619 
620   if (!str) {
621     str = "";
622   }
623 
624   tmp = (TextLine *)MEM_mallocN(sizeof(TextLine), "textline");
625   tmp->line = MEM_mallocN(strlen(str) + 1, "textline_string");
626   tmp->format = NULL;
627 
628   strcpy(tmp->line, str);
629 
630   tmp->len = strlen(str);
631   tmp->next = tmp->prev = NULL;
632 
633   return tmp;
634 }
635 
txt_new_linen(const char * str,int n)636 static TextLine *txt_new_linen(const char *str, int n)
637 {
638   TextLine *tmp;
639 
640   tmp = (TextLine *)MEM_mallocN(sizeof(TextLine), "textline");
641   tmp->line = MEM_mallocN(n + 1, "textline_string");
642   tmp->format = NULL;
643 
644   BLI_strncpy(tmp->line, (str) ? str : "", n + 1);
645 
646   tmp->len = strlen(tmp->line);
647   tmp->next = tmp->prev = NULL;
648 
649   return tmp;
650 }
651 
txt_clean_text(Text * text)652 void txt_clean_text(Text *text)
653 {
654   TextLine **top, **bot;
655 
656   if (!text->lines.first) {
657     if (text->lines.last) {
658       text->lines.first = text->lines.last;
659     }
660     else {
661       text->lines.first = text->lines.last = txt_new_line(NULL);
662     }
663   }
664 
665   if (!text->lines.last) {
666     text->lines.last = text->lines.first;
667   }
668 
669   top = (TextLine **)&text->lines.first;
670   bot = (TextLine **)&text->lines.last;
671 
672   while ((*top)->prev) {
673     *top = (*top)->prev;
674   }
675   while ((*bot)->next) {
676     *bot = (*bot)->next;
677   }
678 
679   if (!text->curl) {
680     if (text->sell) {
681       text->curl = text->sell;
682     }
683     else {
684       text->curl = text->lines.first;
685     }
686     text->curc = 0;
687   }
688 
689   if (!text->sell) {
690     text->sell = text->curl;
691     text->selc = 0;
692   }
693 }
694 
txt_get_span(TextLine * from,TextLine * to)695 int txt_get_span(TextLine *from, TextLine *to)
696 {
697   int ret = 0;
698   TextLine *tmp = from;
699 
700   if (!to || !from) {
701     return 0;
702   }
703   if (from == to) {
704     return 0;
705   }
706 
707   /* Look forwards */
708   while (tmp) {
709     if (tmp == to) {
710       return ret;
711     }
712     ret++;
713     tmp = tmp->next;
714   }
715 
716   /* Look backwards */
717   if (!tmp) {
718     tmp = from;
719     ret = 0;
720     while (tmp) {
721       if (tmp == to) {
722         break;
723       }
724       ret--;
725       tmp = tmp->prev;
726     }
727     if (!tmp) {
728       ret = 0;
729     }
730   }
731 
732   return ret;
733 }
734 
txt_make_dirty(Text * text)735 static void txt_make_dirty(Text *text)
736 {
737   text->flags |= TXT_ISDIRTY;
738 #ifdef WITH_PYTHON
739   if (text->compiled) {
740     BPY_text_free_code(text);
741   }
742 #endif
743 }
744 
745 /** \} */
746 
747 /* -------------------------------------------------------------------- */
748 /** \name Cursor Utility Functions
749  * \{ */
750 
txt_curs_cur(Text * text,TextLine *** linep,int ** charp)751 static void txt_curs_cur(Text *text, TextLine ***linep, int **charp)
752 {
753   *linep = &text->curl;
754   *charp = &text->curc;
755 }
756 
txt_curs_sel(Text * text,TextLine *** linep,int ** charp)757 static void txt_curs_sel(Text *text, TextLine ***linep, int **charp)
758 {
759   *linep = &text->sell;
760   *charp = &text->selc;
761 }
762 
txt_cursor_is_line_start(Text * text)763 bool txt_cursor_is_line_start(Text *text)
764 {
765   return (text->selc == 0);
766 }
767 
txt_cursor_is_line_end(Text * text)768 bool txt_cursor_is_line_end(Text *text)
769 {
770   return (text->selc == text->sell->len);
771 }
772 
773 /** \} */
774 
775 /* -------------------------------------------------------------------- */
776 /** \name Cursor Movement Functions
777  *
778  * \note If the user moves the cursor the space containing that cursor should be popped
779  * See #txt_pop_first, #txt_pop_last
780  * Other space-types retain their own top location.
781  * \{ */
782 
txt_move_up(Text * text,const bool sel)783 void txt_move_up(Text *text, const bool sel)
784 {
785   TextLine **linep;
786   int *charp;
787 
788   if (sel) {
789     txt_curs_sel(text, &linep, &charp);
790   }
791   else {
792     txt_pop_first(text);
793     txt_curs_cur(text, &linep, &charp);
794   }
795   if (!*linep) {
796     return;
797   }
798 
799   if ((*linep)->prev) {
800     int column = BLI_str_utf8_offset_to_column((*linep)->line, *charp);
801     *linep = (*linep)->prev;
802     *charp = BLI_str_utf8_offset_from_column((*linep)->line, column);
803   }
804   else {
805     txt_move_bol(text, sel);
806   }
807 
808   if (!sel) {
809     txt_pop_sel(text);
810   }
811 }
812 
txt_move_down(Text * text,const bool sel)813 void txt_move_down(Text *text, const bool sel)
814 {
815   TextLine **linep;
816   int *charp;
817 
818   if (sel) {
819     txt_curs_sel(text, &linep, &charp);
820   }
821   else {
822     txt_pop_last(text);
823     txt_curs_cur(text, &linep, &charp);
824   }
825   if (!*linep) {
826     return;
827   }
828 
829   if ((*linep)->next) {
830     int column = BLI_str_utf8_offset_to_column((*linep)->line, *charp);
831     *linep = (*linep)->next;
832     *charp = BLI_str_utf8_offset_from_column((*linep)->line, column);
833   }
834   else {
835     txt_move_eol(text, sel);
836   }
837 
838   if (!sel) {
839     txt_pop_sel(text);
840   }
841 }
842 
txt_calc_tab_left(TextLine * tl,int ch)843 int txt_calc_tab_left(TextLine *tl, int ch)
844 {
845   /* do nice left only if there are only spaces */
846 
847   int tabsize = (ch < TXT_TABSIZE) ? ch : TXT_TABSIZE;
848 
849   for (int i = 0; i < ch; i++) {
850     if (tl->line[i] != ' ') {
851       tabsize = 0;
852       break;
853     }
854   }
855 
856   /* if in the middle of the space-tab */
857   if (tabsize && ch % TXT_TABSIZE != 0) {
858     tabsize = (ch % TXT_TABSIZE);
859   }
860   return tabsize;
861 }
862 
txt_calc_tab_right(TextLine * tl,int ch)863 int txt_calc_tab_right(TextLine *tl, int ch)
864 {
865   if (tl->line[ch] == ' ') {
866     int i;
867     for (i = 0; i < ch; i++) {
868       if (tl->line[i] != ' ') {
869         return 0;
870       }
871     }
872 
873     int tabsize = (ch) % TXT_TABSIZE + 1;
874     for (i = ch + 1; tl->line[i] == ' ' && tabsize < TXT_TABSIZE; i++) {
875       tabsize++;
876     }
877 
878     return i - ch;
879   }
880 
881   return 0;
882 }
883 
txt_move_left(Text * text,const bool sel)884 void txt_move_left(Text *text, const bool sel)
885 {
886   TextLine **linep;
887   int *charp;
888   int tabsize = 0;
889 
890   if (sel) {
891     txt_curs_sel(text, &linep, &charp);
892   }
893   else {
894     txt_pop_first(text);
895     txt_curs_cur(text, &linep, &charp);
896   }
897   if (!*linep) {
898     return;
899   }
900 
901   if (*charp == 0) {
902     if ((*linep)->prev) {
903       txt_move_up(text, sel);
904       *charp = (*linep)->len;
905     }
906   }
907   else {
908     /* do nice left only if there are only spaces */
909     /* #TXT_TABSIZE hard-coded in DNA_text_types.h */
910     if (text->flags & TXT_TABSTOSPACES) {
911       tabsize = txt_calc_tab_left(*linep, *charp);
912     }
913 
914     if (tabsize) {
915       (*charp) -= tabsize;
916     }
917     else {
918       const char *prev = BLI_str_prev_char_utf8((*linep)->line + *charp);
919       *charp = prev - (*linep)->line;
920     }
921   }
922 
923   if (!sel) {
924     txt_pop_sel(text);
925   }
926 }
927 
txt_move_right(Text * text,const bool sel)928 void txt_move_right(Text *text, const bool sel)
929 {
930   TextLine **linep;
931   int *charp;
932 
933   if (sel) {
934     txt_curs_sel(text, &linep, &charp);
935   }
936   else {
937     txt_pop_last(text);
938     txt_curs_cur(text, &linep, &charp);
939   }
940   if (!*linep) {
941     return;
942   }
943 
944   if (*charp == (*linep)->len) {
945     if ((*linep)->next) {
946       txt_move_down(text, sel);
947       *charp = 0;
948     }
949   }
950   else {
951     /* do nice right only if there are only spaces */
952     /* spaces hardcoded in DNA_text_types.h */
953     int tabsize = 0;
954 
955     if (text->flags & TXT_TABSTOSPACES) {
956       tabsize = txt_calc_tab_right(*linep, *charp);
957     }
958 
959     if (tabsize) {
960       (*charp) += tabsize;
961     }
962     else {
963       (*charp) += BLI_str_utf8_size((*linep)->line + *charp);
964     }
965   }
966 
967   if (!sel) {
968     txt_pop_sel(text);
969   }
970 }
971 
txt_jump_left(Text * text,const bool sel,const bool use_init_step)972 void txt_jump_left(Text *text, const bool sel, const bool use_init_step)
973 {
974   TextLine **linep;
975   int *charp;
976 
977   if (sel) {
978     txt_curs_sel(text, &linep, &charp);
979   }
980   else {
981     txt_pop_first(text);
982     txt_curs_cur(text, &linep, &charp);
983   }
984   if (!*linep) {
985     return;
986   }
987 
988   BLI_str_cursor_step_utf8(
989       (*linep)->line, (*linep)->len, charp, STRCUR_DIR_PREV, STRCUR_JUMP_DELIM, use_init_step);
990 
991   if (!sel) {
992     txt_pop_sel(text);
993   }
994 }
995 
txt_jump_right(Text * text,const bool sel,const bool use_init_step)996 void txt_jump_right(Text *text, const bool sel, const bool use_init_step)
997 {
998   TextLine **linep;
999   int *charp;
1000 
1001   if (sel) {
1002     txt_curs_sel(text, &linep, &charp);
1003   }
1004   else {
1005     txt_pop_last(text);
1006     txt_curs_cur(text, &linep, &charp);
1007   }
1008   if (!*linep) {
1009     return;
1010   }
1011 
1012   BLI_str_cursor_step_utf8(
1013       (*linep)->line, (*linep)->len, charp, STRCUR_DIR_NEXT, STRCUR_JUMP_DELIM, use_init_step);
1014 
1015   if (!sel) {
1016     txt_pop_sel(text);
1017   }
1018 }
1019 
txt_move_bol(Text * text,const bool sel)1020 void txt_move_bol(Text *text, const bool sel)
1021 {
1022   TextLine **linep;
1023   int *charp;
1024 
1025   if (sel) {
1026     txt_curs_sel(text, &linep, &charp);
1027   }
1028   else {
1029     txt_curs_cur(text, &linep, &charp);
1030   }
1031   if (!*linep) {
1032     return;
1033   }
1034 
1035   *charp = 0;
1036 
1037   if (!sel) {
1038     txt_pop_sel(text);
1039   }
1040 }
1041 
txt_move_eol(Text * text,const bool sel)1042 void txt_move_eol(Text *text, const bool sel)
1043 {
1044   TextLine **linep;
1045   int *charp;
1046 
1047   if (sel) {
1048     txt_curs_sel(text, &linep, &charp);
1049   }
1050   else {
1051     txt_curs_cur(text, &linep, &charp);
1052   }
1053   if (!*linep) {
1054     return;
1055   }
1056 
1057   *charp = (*linep)->len;
1058 
1059   if (!sel) {
1060     txt_pop_sel(text);
1061   }
1062 }
1063 
txt_move_bof(Text * text,const bool sel)1064 void txt_move_bof(Text *text, const bool sel)
1065 {
1066   TextLine **linep;
1067   int *charp;
1068 
1069   if (sel) {
1070     txt_curs_sel(text, &linep, &charp);
1071   }
1072   else {
1073     txt_curs_cur(text, &linep, &charp);
1074   }
1075   if (!*linep) {
1076     return;
1077   }
1078 
1079   *linep = text->lines.first;
1080   *charp = 0;
1081 
1082   if (!sel) {
1083     txt_pop_sel(text);
1084   }
1085 }
1086 
txt_move_eof(Text * text,const bool sel)1087 void txt_move_eof(Text *text, const bool sel)
1088 {
1089   TextLine **linep;
1090   int *charp;
1091 
1092   if (sel) {
1093     txt_curs_sel(text, &linep, &charp);
1094   }
1095   else {
1096     txt_curs_cur(text, &linep, &charp);
1097   }
1098   if (!*linep) {
1099     return;
1100   }
1101 
1102   *linep = text->lines.last;
1103   *charp = (*linep)->len;
1104 
1105   if (!sel) {
1106     txt_pop_sel(text);
1107   }
1108 }
1109 
txt_move_toline(Text * text,unsigned int line,const bool sel)1110 void txt_move_toline(Text *text, unsigned int line, const bool sel)
1111 {
1112   txt_move_to(text, line, 0, sel);
1113 }
1114 
1115 /* Moves to a certain byte in a line, not a certain utf8-character! */
txt_move_to(Text * text,unsigned int line,unsigned int ch,const bool sel)1116 void txt_move_to(Text *text, unsigned int line, unsigned int ch, const bool sel)
1117 {
1118   TextLine **linep;
1119   int *charp;
1120   unsigned int i;
1121 
1122   if (sel) {
1123     txt_curs_sel(text, &linep, &charp);
1124   }
1125   else {
1126     txt_curs_cur(text, &linep, &charp);
1127   }
1128   if (!*linep) {
1129     return;
1130   }
1131 
1132   *linep = text->lines.first;
1133   for (i = 0; i < line; i++) {
1134     if ((*linep)->next) {
1135       *linep = (*linep)->next;
1136     }
1137     else {
1138       break;
1139     }
1140   }
1141   if (ch > (unsigned int)((*linep)->len)) {
1142     ch = (unsigned int)((*linep)->len);
1143   }
1144   *charp = ch;
1145 
1146   if (!sel) {
1147     txt_pop_sel(text);
1148   }
1149 }
1150 
1151 /** \} */
1152 
1153 /* -------------------------------------------------------------------- */
1154 /** \name Text Selection Functions
1155  * \{ */
1156 
txt_curs_swap(Text * text)1157 static void txt_curs_swap(Text *text)
1158 {
1159   TextLine *tmpl;
1160   int tmpc;
1161 
1162   tmpl = text->curl;
1163   text->curl = text->sell;
1164   text->sell = tmpl;
1165 
1166   tmpc = text->curc;
1167   text->curc = text->selc;
1168   text->selc = tmpc;
1169 }
1170 
txt_pop_first(Text * text)1171 static void txt_pop_first(Text *text)
1172 {
1173   if (txt_get_span(text->curl, text->sell) < 0 ||
1174       (text->curl == text->sell && text->curc > text->selc)) {
1175     txt_curs_swap(text);
1176   }
1177 
1178   txt_pop_sel(text);
1179 }
1180 
txt_pop_last(Text * text)1181 static void txt_pop_last(Text *text)
1182 {
1183   if (txt_get_span(text->curl, text->sell) > 0 ||
1184       (text->curl == text->sell && text->curc < text->selc)) {
1185     txt_curs_swap(text);
1186   }
1187 
1188   txt_pop_sel(text);
1189 }
1190 
txt_pop_sel(Text * text)1191 void txt_pop_sel(Text *text)
1192 {
1193   text->sell = text->curl;
1194   text->selc = text->curc;
1195 }
1196 
txt_order_cursors(Text * text,const bool reverse)1197 void txt_order_cursors(Text *text, const bool reverse)
1198 {
1199   if (!text->curl) {
1200     return;
1201   }
1202   if (!text->sell) {
1203     return;
1204   }
1205 
1206   /* Flip so text->curl is before/after text->sell */
1207   if (reverse == false) {
1208     if ((txt_get_span(text->curl, text->sell) < 0) ||
1209         (text->curl == text->sell && text->curc > text->selc)) {
1210       txt_curs_swap(text);
1211     }
1212   }
1213   else {
1214     if ((txt_get_span(text->curl, text->sell) > 0) ||
1215         (text->curl == text->sell && text->curc < text->selc)) {
1216       txt_curs_swap(text);
1217     }
1218   }
1219 }
1220 
txt_has_sel(Text * text)1221 bool txt_has_sel(Text *text)
1222 {
1223   return ((text->curl != text->sell) || (text->curc != text->selc));
1224 }
1225 
txt_delete_sel(Text * text)1226 static void txt_delete_sel(Text *text)
1227 {
1228   TextLine *tmpl;
1229   char *buf;
1230 
1231   if (!text->curl) {
1232     return;
1233   }
1234   if (!text->sell) {
1235     return;
1236   }
1237 
1238   if (!txt_has_sel(text)) {
1239     return;
1240   }
1241 
1242   txt_order_cursors(text, false);
1243 
1244   buf = MEM_mallocN(text->curc + (text->sell->len - text->selc) + 1, "textline_string");
1245 
1246   strncpy(buf, text->curl->line, text->curc);
1247   strcpy(buf + text->curc, text->sell->line + text->selc);
1248   buf[text->curc + (text->sell->len - text->selc)] = 0;
1249 
1250   make_new_line(text->curl, buf);
1251 
1252   tmpl = text->sell;
1253   while (tmpl != text->curl) {
1254     tmpl = tmpl->prev;
1255     if (!tmpl) {
1256       break;
1257     }
1258 
1259     txt_delete_line(text, tmpl->next);
1260   }
1261 
1262   text->sell = text->curl;
1263   text->selc = text->curc;
1264 }
1265 
txt_sel_all(Text * text)1266 void txt_sel_all(Text *text)
1267 {
1268   text->curl = text->lines.first;
1269   text->curc = 0;
1270 
1271   text->sell = text->lines.last;
1272   text->selc = text->sell->len;
1273 }
1274 
1275 /**
1276  * Reverse of #txt_pop_sel
1277  * Clears the selection and ensures the cursor is located
1278  * at the selection (where the cursor is visually while editing).
1279  */
txt_sel_clear(Text * text)1280 void txt_sel_clear(Text *text)
1281 {
1282   if (text->sell) {
1283     text->curl = text->sell;
1284     text->curc = text->selc;
1285   }
1286 }
1287 
txt_sel_line(Text * text)1288 void txt_sel_line(Text *text)
1289 {
1290   if (!text->curl) {
1291     return;
1292   }
1293 
1294   text->curc = 0;
1295   text->sell = text->curl;
1296   text->selc = text->sell->len;
1297 }
1298 
txt_sel_set(Text * text,int startl,int startc,int endl,int endc)1299 void txt_sel_set(Text *text, int startl, int startc, int endl, int endc)
1300 {
1301   TextLine *froml, *tol;
1302   int fromllen, tollen;
1303 
1304   /* Support negative indices. */
1305   if (startl < 0 || endl < 0) {
1306     int end = BLI_listbase_count(&text->lines) - 1;
1307     if (startl < 0) {
1308       startl = end + startl + 1;
1309     }
1310     if (endl < 0) {
1311       endl = end + endl + 1;
1312     }
1313   }
1314   CLAMP_MIN(startl, 0);
1315   CLAMP_MIN(endl, 0);
1316 
1317   froml = BLI_findlink(&text->lines, startl);
1318   if (froml == NULL) {
1319     froml = text->lines.last;
1320   }
1321   if (startl == endl) {
1322     tol = froml;
1323   }
1324   else {
1325     tol = BLI_findlink(&text->lines, endl);
1326     if (tol == NULL) {
1327       tol = text->lines.last;
1328     }
1329   }
1330 
1331   fromllen = BLI_strlen_utf8(froml->line);
1332   tollen = BLI_strlen_utf8(tol->line);
1333 
1334   /* Support negative indices. */
1335   if (startc < 0) {
1336     startc = fromllen + startc + 1;
1337   }
1338   if (endc < 0) {
1339     endc = tollen + endc + 1;
1340   }
1341 
1342   CLAMP(startc, 0, fromllen);
1343   CLAMP(endc, 0, tollen);
1344 
1345   text->curl = froml;
1346   text->curc = BLI_str_utf8_offset_from_index(froml->line, startc);
1347   text->sell = tol;
1348   text->selc = BLI_str_utf8_offset_from_index(tol->line, endc);
1349 }
1350 
1351 /** \} */
1352 
1353 /* -------------------------------------------------------------------- */
1354 /** \name Buffer Conversion for Undo/Redo
1355  *
1356  * Buffer conversion functions that rely on the buffer already being validated.
1357  *
1358  * The only requirement for these functions is that they're reverse-able,
1359  * the undo logic doesn't inspect their content.
1360  *
1361  * Currently buffers:
1362  * - Always ends with a new-line.
1363  * - Are not null terminated.
1364  * \{ */
1365 
1366 /**
1367  * Create a buffer, the only requirement is #txt_from_buf_for_undo can decode it.
1368  */
txt_to_buf_for_undo(Text * text,int * r_buf_len)1369 char *txt_to_buf_for_undo(Text *text, int *r_buf_len)
1370 {
1371   int buf_len = 0;
1372   LISTBASE_FOREACH (const TextLine *, l, &text->lines) {
1373     buf_len += l->len + 1;
1374   }
1375   char *buf = MEM_mallocN(buf_len, __func__);
1376   char *buf_step = buf;
1377   LISTBASE_FOREACH (const TextLine *, l, &text->lines) {
1378     memcpy(buf_step, l->line, l->len);
1379     buf_step += l->len;
1380     *buf_step++ = '\n';
1381   }
1382   *r_buf_len = buf_len;
1383   return buf;
1384 }
1385 
1386 /**
1387  * Decode a buffer from #txt_to_buf_for_undo.
1388  */
txt_from_buf_for_undo(Text * text,const char * buf,int buf_len)1389 void txt_from_buf_for_undo(Text *text, const char *buf, int buf_len)
1390 {
1391   const char *buf_end = buf + buf_len;
1392   const char *buf_step = buf;
1393 
1394   /* First re-use existing lines.
1395    * Good for undo since it means in practice many operations re-use all
1396    * except for the modified line. */
1397   TextLine *l_src = text->lines.first;
1398   BLI_listbase_clear(&text->lines);
1399   while (buf_step != buf_end && l_src) {
1400     /* New lines are ensured by #txt_to_buf_for_undo. */
1401     const char *buf_step_next = strchr(buf_step, '\n');
1402     const int len = buf_step_next - buf_step;
1403 
1404     TextLine *l = l_src;
1405     l_src = l_src->next;
1406     if (l->len != len) {
1407       l->line = MEM_reallocN(l->line, len + 1);
1408       l->len = len;
1409     }
1410     MEM_SAFE_FREE(l->format);
1411 
1412     memcpy(l->line, buf_step, len);
1413     l->line[len] = '\0';
1414     BLI_addtail(&text->lines, l);
1415     buf_step = buf_step_next + 1;
1416   }
1417 
1418   /* If we have extra lines. */
1419   while (l_src != NULL) {
1420     TextLine *l_src_next = l_src->next;
1421     MEM_freeN(l_src->line);
1422     if (l_src->format) {
1423       MEM_freeN(l_src->format);
1424     }
1425     MEM_freeN(l_src);
1426     l_src = l_src_next;
1427   }
1428 
1429   while (buf_step != buf_end) {
1430     /* New lines are ensured by #txt_to_buf_for_undo. */
1431     const char *buf_step_next = strchr(buf_step, '\n');
1432     const int len = buf_step_next - buf_step;
1433 
1434     TextLine *l = MEM_mallocN(sizeof(TextLine), "textline");
1435     l->line = MEM_mallocN(len + 1, "textline_string");
1436     l->len = len;
1437     l->format = NULL;
1438 
1439     memcpy(l->line, buf_step, len);
1440     l->line[len] = '\0';
1441     BLI_addtail(&text->lines, l);
1442     buf_step = buf_step_next + 1;
1443   }
1444 
1445   text->curl = text->sell = text->lines.first;
1446   text->curc = text->selc = 0;
1447 
1448   txt_make_dirty(text);
1449 }
1450 
1451 /** \} */
1452 
1453 /* -------------------------------------------------------------------- */
1454 /** \name Cut and Paste Functions
1455  * \{ */
1456 
txt_to_buf(Text * text,int * r_buf_strlen)1457 char *txt_to_buf(Text *text, int *r_buf_strlen)
1458 {
1459   int length;
1460   TextLine *tmp, *linef, *linel;
1461   int charf, charl;
1462   char *buf;
1463 
1464   if (r_buf_strlen) {
1465     *r_buf_strlen = 0;
1466   }
1467 
1468   if (!text->curl) {
1469     return NULL;
1470   }
1471   if (!text->sell) {
1472     return NULL;
1473   }
1474   if (!text->lines.first) {
1475     return NULL;
1476   }
1477 
1478   linef = text->lines.first;
1479   charf = 0;
1480 
1481   linel = text->lines.last;
1482   charl = linel->len;
1483 
1484   if (linef == text->lines.last) {
1485     length = charl - charf;
1486 
1487     buf = MEM_mallocN(length + 2, "text buffer");
1488 
1489     BLI_strncpy(buf, linef->line + charf, length + 1);
1490     buf[length] = 0;
1491   }
1492   else {
1493     length = linef->len - charf;
1494     length += charl;
1495     length += 2; /* For the 2 '\n' */
1496 
1497     tmp = linef->next;
1498     while (tmp && tmp != linel) {
1499       length += tmp->len + 1;
1500       tmp = tmp->next;
1501     }
1502 
1503     buf = MEM_mallocN(length + 1, "cut buffer");
1504 
1505     strncpy(buf, linef->line + charf, linef->len - charf);
1506     length = linef->len - charf;
1507 
1508     buf[length++] = '\n';
1509 
1510     tmp = linef->next;
1511     while (tmp && tmp != linel) {
1512       strncpy(buf + length, tmp->line, tmp->len);
1513       length += tmp->len;
1514 
1515       buf[length++] = '\n';
1516 
1517       tmp = tmp->next;
1518     }
1519     strncpy(buf + length, linel->line, charl);
1520     length += charl;
1521 
1522     /* python compiler wants an empty end line */
1523     buf[length++] = '\n';
1524     buf[length] = 0;
1525   }
1526 
1527   if (r_buf_strlen) {
1528     *r_buf_strlen = length;
1529   }
1530 
1531   return buf;
1532 }
1533 
txt_sel_to_buf(Text * text,int * r_buf_strlen)1534 char *txt_sel_to_buf(Text *text, int *r_buf_strlen)
1535 {
1536   char *buf;
1537   int length = 0;
1538   TextLine *tmp, *linef, *linel;
1539   int charf, charl;
1540 
1541   if (r_buf_strlen) {
1542     *r_buf_strlen = 0;
1543   }
1544 
1545   if (!text->curl) {
1546     return NULL;
1547   }
1548   if (!text->sell) {
1549     return NULL;
1550   }
1551 
1552   if (text->curl == text->sell) {
1553     linef = linel = text->curl;
1554 
1555     if (text->curc < text->selc) {
1556       charf = text->curc;
1557       charl = text->selc;
1558     }
1559     else {
1560       charf = text->selc;
1561       charl = text->curc;
1562     }
1563   }
1564   else if (txt_get_span(text->curl, text->sell) < 0) {
1565     linef = text->sell;
1566     linel = text->curl;
1567 
1568     charf = text->selc;
1569     charl = text->curc;
1570   }
1571   else {
1572     linef = text->curl;
1573     linel = text->sell;
1574 
1575     charf = text->curc;
1576     charl = text->selc;
1577   }
1578 
1579   if (linef == linel) {
1580     length = charl - charf;
1581 
1582     buf = MEM_mallocN(length + 1, "sel buffer");
1583 
1584     BLI_strncpy(buf, linef->line + charf, length + 1);
1585   }
1586   else {
1587     length += linef->len - charf;
1588     length += charl;
1589     length++; /* For the '\n' */
1590 
1591     tmp = linef->next;
1592     while (tmp && tmp != linel) {
1593       length += tmp->len + 1;
1594       tmp = tmp->next;
1595     }
1596 
1597     buf = MEM_mallocN(length + 1, "sel buffer");
1598 
1599     strncpy(buf, linef->line + charf, linef->len - charf);
1600     length = linef->len - charf;
1601 
1602     buf[length++] = '\n';
1603 
1604     tmp = linef->next;
1605     while (tmp && tmp != linel) {
1606       strncpy(buf + length, tmp->line, tmp->len);
1607       length += tmp->len;
1608 
1609       buf[length++] = '\n';
1610 
1611       tmp = tmp->next;
1612     }
1613     strncpy(buf + length, linel->line, charl);
1614     length += charl;
1615 
1616     buf[length] = 0;
1617   }
1618 
1619   if (r_buf_strlen) {
1620     *r_buf_strlen = length;
1621   }
1622 
1623   return buf;
1624 }
1625 
txt_insert_buf(Text * text,const char * in_buffer)1626 void txt_insert_buf(Text *text, const char *in_buffer)
1627 {
1628   int l = 0, len;
1629   size_t i = 0, j;
1630   TextLine *add;
1631   char *buffer;
1632 
1633   if (!in_buffer) {
1634     return;
1635   }
1636 
1637   txt_delete_sel(text);
1638 
1639   len = strlen(in_buffer);
1640   buffer = BLI_strdupn(in_buffer, len);
1641   len += txt_extended_ascii_as_utf8(&buffer);
1642 
1643   /* Read the first line (or as close as possible */
1644   while (buffer[i] && buffer[i] != '\n') {
1645     txt_add_raw_char(text, BLI_str_utf8_as_unicode_step(buffer, &i));
1646   }
1647 
1648   if (buffer[i] == '\n') {
1649     txt_split_curline(text);
1650     i++;
1651 
1652     while (i < len) {
1653       l = 0;
1654 
1655       while (buffer[i] && buffer[i] != '\n') {
1656         i++;
1657         l++;
1658       }
1659 
1660       if (buffer[i] == '\n') {
1661         add = txt_new_linen(buffer + (i - l), l);
1662         BLI_insertlinkbefore(&text->lines, text->curl, add);
1663         i++;
1664       }
1665       else {
1666         for (j = i - l; j < i && j < len;) {
1667           txt_add_raw_char(text, BLI_str_utf8_as_unicode_step(buffer, &j));
1668         }
1669         break;
1670       }
1671     }
1672   }
1673 
1674   MEM_freeN(buffer);
1675 }
1676 
1677 /** \} */
1678 
1679 /* -------------------------------------------------------------------- */
1680 /** \name Find String in Text
1681  * \{ */
1682 
txt_find_string(Text * text,const char * findstr,int wrap,int match_case)1683 int txt_find_string(Text *text, const char *findstr, int wrap, int match_case)
1684 {
1685   TextLine *tl, *startl;
1686   const char *s = NULL;
1687 
1688   if (!text->curl || !text->sell) {
1689     return 0;
1690   }
1691 
1692   txt_order_cursors(text, false);
1693 
1694   tl = startl = text->sell;
1695 
1696   if (match_case) {
1697     s = strstr(&tl->line[text->selc], findstr);
1698   }
1699   else {
1700     s = BLI_strcasestr(&tl->line[text->selc], findstr);
1701   }
1702   while (!s) {
1703     tl = tl->next;
1704     if (!tl) {
1705       if (wrap) {
1706         tl = text->lines.first;
1707       }
1708       else {
1709         break;
1710       }
1711     }
1712 
1713     if (match_case) {
1714       s = strstr(tl->line, findstr);
1715     }
1716     else {
1717       s = BLI_strcasestr(tl->line, findstr);
1718     }
1719     if (tl == startl) {
1720       break;
1721     }
1722   }
1723 
1724   if (s) {
1725     int newl = txt_get_span(text->lines.first, tl);
1726     int newc = (int)(s - tl->line);
1727     txt_move_to(text, newl, newc, 0);
1728     txt_move_to(text, newl, newc + strlen(findstr), 1);
1729     return 1;
1730   }
1731 
1732   return 0;
1733 }
1734 
1735 /** \} */
1736 
1737 /* -------------------------------------------------------------------- */
1738 /** \name Line Editing Functions
1739  * \{ */
1740 
txt_split_curline(Text * text)1741 void txt_split_curline(Text *text)
1742 {
1743   TextLine *ins;
1744   char *left, *right;
1745 
1746   if (!text->curl) {
1747     return;
1748   }
1749 
1750   txt_delete_sel(text);
1751 
1752   /* Make the two half strings */
1753 
1754   left = MEM_mallocN(text->curc + 1, "textline_string");
1755   if (text->curc) {
1756     memcpy(left, text->curl->line, text->curc);
1757   }
1758   left[text->curc] = 0;
1759 
1760   right = MEM_mallocN(text->curl->len - text->curc + 1, "textline_string");
1761   memcpy(right, text->curl->line + text->curc, text->curl->len - text->curc + 1);
1762 
1763   MEM_freeN(text->curl->line);
1764   if (text->curl->format) {
1765     MEM_freeN(text->curl->format);
1766   }
1767 
1768   /* Make the new TextLine */
1769 
1770   ins = MEM_mallocN(sizeof(TextLine), "textline");
1771   ins->line = left;
1772   ins->format = NULL;
1773   ins->len = text->curc;
1774 
1775   text->curl->line = right;
1776   text->curl->format = NULL;
1777   text->curl->len = text->curl->len - text->curc;
1778 
1779   BLI_insertlinkbefore(&text->lines, text->curl, ins);
1780 
1781   text->curc = 0;
1782 
1783   txt_make_dirty(text);
1784   txt_clean_text(text);
1785 
1786   txt_pop_sel(text);
1787 }
1788 
txt_delete_line(Text * text,TextLine * line)1789 static void txt_delete_line(Text *text, TextLine *line)
1790 {
1791   if (!text->curl) {
1792     return;
1793   }
1794 
1795   BLI_remlink(&text->lines, line);
1796 
1797   if (line->line) {
1798     MEM_freeN(line->line);
1799   }
1800   if (line->format) {
1801     MEM_freeN(line->format);
1802   }
1803 
1804   MEM_freeN(line);
1805 
1806   txt_make_dirty(text);
1807   txt_clean_text(text);
1808 }
1809 
txt_combine_lines(Text * text,TextLine * linea,TextLine * lineb)1810 static void txt_combine_lines(Text *text, TextLine *linea, TextLine *lineb)
1811 {
1812   char *tmp, *s;
1813 
1814   if (!linea || !lineb) {
1815     return;
1816   }
1817 
1818   tmp = MEM_mallocN(linea->len + lineb->len + 1, "textline_string");
1819 
1820   s = tmp;
1821   s += BLI_strcpy_rlen(s, linea->line);
1822   s += BLI_strcpy_rlen(s, lineb->line);
1823   (void)s;
1824 
1825   make_new_line(linea, tmp);
1826 
1827   txt_delete_line(text, lineb);
1828 
1829   txt_make_dirty(text);
1830   txt_clean_text(text);
1831 }
1832 
txt_duplicate_line(Text * text)1833 void txt_duplicate_line(Text *text)
1834 {
1835   TextLine *textline;
1836 
1837   if (!text->curl) {
1838     return;
1839   }
1840 
1841   if (text->curl == text->sell) {
1842     textline = txt_new_line(text->curl->line);
1843     BLI_insertlinkafter(&text->lines, text->curl, textline);
1844 
1845     txt_make_dirty(text);
1846     txt_clean_text(text);
1847   }
1848 }
1849 
txt_delete_char(Text * text)1850 void txt_delete_char(Text *text)
1851 {
1852   unsigned int c = '\n';
1853 
1854   if (!text->curl) {
1855     return;
1856   }
1857 
1858   if (txt_has_sel(text)) { /* deleting a selection */
1859     txt_delete_sel(text);
1860     txt_make_dirty(text);
1861     return;
1862   }
1863   if (text->curc == text->curl->len) { /* Appending two lines */
1864     if (text->curl->next) {
1865       txt_combine_lines(text, text->curl, text->curl->next);
1866       txt_pop_sel(text);
1867     }
1868     else {
1869       return;
1870     }
1871   }
1872   else { /* Just deleting a char */
1873     size_t c_len = 0;
1874     c = BLI_str_utf8_as_unicode_and_size(text->curl->line + text->curc, &c_len);
1875     UNUSED_VARS(c);
1876 
1877     memmove(text->curl->line + text->curc,
1878             text->curl->line + text->curc + c_len,
1879             text->curl->len - text->curc - c_len + 1);
1880 
1881     text->curl->len -= c_len;
1882 
1883     txt_pop_sel(text);
1884   }
1885 
1886   txt_make_dirty(text);
1887   txt_clean_text(text);
1888 }
1889 
txt_delete_word(Text * text)1890 void txt_delete_word(Text *text)
1891 {
1892   txt_jump_right(text, true, true);
1893   txt_delete_sel(text);
1894   txt_make_dirty(text);
1895 }
1896 
txt_backspace_char(Text * text)1897 void txt_backspace_char(Text *text)
1898 {
1899   unsigned int c = '\n';
1900 
1901   if (!text->curl) {
1902     return;
1903   }
1904 
1905   if (txt_has_sel(text)) { /* deleting a selection */
1906     txt_delete_sel(text);
1907     txt_make_dirty(text);
1908     return;
1909   }
1910   if (text->curc == 0) { /* Appending two lines */
1911     if (!text->curl->prev) {
1912       return;
1913     }
1914 
1915     text->curl = text->curl->prev;
1916     text->curc = text->curl->len;
1917 
1918     txt_combine_lines(text, text->curl, text->curl->next);
1919     txt_pop_sel(text);
1920   }
1921   else { /* Just backspacing a char */
1922     size_t c_len = 0;
1923     const char *prev = BLI_str_prev_char_utf8(text->curl->line + text->curc);
1924     c = BLI_str_utf8_as_unicode_and_size(prev, &c_len);
1925     UNUSED_VARS(c);
1926 
1927     /* source and destination overlap, don't use memcpy() */
1928     memmove(text->curl->line + text->curc - c_len,
1929             text->curl->line + text->curc,
1930             text->curl->len - text->curc + 1);
1931 
1932     text->curl->len -= c_len;
1933     text->curc -= c_len;
1934 
1935     txt_pop_sel(text);
1936   }
1937 
1938   txt_make_dirty(text);
1939   txt_clean_text(text);
1940 }
1941 
txt_backspace_word(Text * text)1942 void txt_backspace_word(Text *text)
1943 {
1944   txt_jump_left(text, true, true);
1945   txt_delete_sel(text);
1946   txt_make_dirty(text);
1947 }
1948 
1949 /* Max spaces to replace a tab with, currently hardcoded to TXT_TABSIZE = 4.
1950  * Used by txt_convert_tab_to_spaces, indent and unindent.
1951  * Remember to change this string according to max tab size */
1952 static char tab_to_spaces[] = "    ";
1953 
txt_convert_tab_to_spaces(Text * text)1954 static void txt_convert_tab_to_spaces(Text *text)
1955 {
1956   /* sb aims to pad adjust the tab-width needed so that the right number of spaces
1957    * is added so that the indention of the line is the right width (i.e. aligned
1958    * to multiples of TXT_TABSIZE)
1959    */
1960   const char *sb = &tab_to_spaces[text->curc % TXT_TABSIZE];
1961   txt_insert_buf(text, sb);
1962 }
1963 
txt_add_char_intern(Text * text,unsigned int add,bool replace_tabs)1964 static bool txt_add_char_intern(Text *text, unsigned int add, bool replace_tabs)
1965 {
1966   char *tmp, ch[BLI_UTF8_MAX];
1967   size_t add_len;
1968 
1969   if (!text->curl) {
1970     return 0;
1971   }
1972 
1973   if (add == '\n') {
1974     txt_split_curline(text);
1975     return true;
1976   }
1977 
1978   /* insert spaces rather than tabs */
1979   if (add == '\t' && replace_tabs) {
1980     txt_convert_tab_to_spaces(text);
1981     return true;
1982   }
1983 
1984   txt_delete_sel(text);
1985 
1986   add_len = BLI_str_utf8_from_unicode(add, ch);
1987 
1988   tmp = MEM_mallocN(text->curl->len + add_len + 1, "textline_string");
1989 
1990   memcpy(tmp, text->curl->line, text->curc);
1991   memcpy(tmp + text->curc, ch, add_len);
1992   memcpy(
1993       tmp + text->curc + add_len, text->curl->line + text->curc, text->curl->len - text->curc + 1);
1994 
1995   make_new_line(text->curl, tmp);
1996 
1997   text->curc += add_len;
1998 
1999   txt_pop_sel(text);
2000 
2001   txt_make_dirty(text);
2002   txt_clean_text(text);
2003 
2004   return 1;
2005 }
2006 
txt_add_char(Text * text,unsigned int add)2007 bool txt_add_char(Text *text, unsigned int add)
2008 {
2009   return txt_add_char_intern(text, add, (text->flags & TXT_TABSTOSPACES) != 0);
2010 }
2011 
txt_add_raw_char(Text * text,unsigned int add)2012 bool txt_add_raw_char(Text *text, unsigned int add)
2013 {
2014   return txt_add_char_intern(text, add, 0);
2015 }
2016 
txt_delete_selected(Text * text)2017 void txt_delete_selected(Text *text)
2018 {
2019   txt_delete_sel(text);
2020   txt_make_dirty(text);
2021 }
2022 
txt_replace_char(Text * text,unsigned int add)2023 bool txt_replace_char(Text *text, unsigned int add)
2024 {
2025   unsigned int del;
2026   size_t del_size = 0, add_size;
2027   char ch[BLI_UTF8_MAX];
2028 
2029   if (!text->curl) {
2030     return false;
2031   }
2032 
2033   /* If text is selected or we're at the end of the line just use txt_add_char */
2034   if (text->curc == text->curl->len || txt_has_sel(text) || add == '\n') {
2035     return txt_add_char(text, add);
2036   }
2037 
2038   del = BLI_str_utf8_as_unicode_and_size(text->curl->line + text->curc, &del_size);
2039   UNUSED_VARS(del);
2040   add_size = BLI_str_utf8_from_unicode(add, ch);
2041 
2042   if (add_size > del_size) {
2043     char *tmp = MEM_mallocN(text->curl->len + add_size - del_size + 1, "textline_string");
2044     memcpy(tmp, text->curl->line, text->curc);
2045     memcpy(tmp + text->curc + add_size,
2046            text->curl->line + text->curc + del_size,
2047            text->curl->len - text->curc - del_size + 1);
2048     MEM_freeN(text->curl->line);
2049     text->curl->line = tmp;
2050   }
2051   else if (add_size < del_size) {
2052     char *tmp = text->curl->line;
2053     memmove(tmp + text->curc + add_size,
2054             tmp + text->curc + del_size,
2055             text->curl->len - text->curc - del_size + 1);
2056   }
2057 
2058   memcpy(text->curl->line + text->curc, ch, add_size);
2059   text->curc += add_size;
2060   text->curl->len += add_size - del_size;
2061 
2062   txt_pop_sel(text);
2063   txt_make_dirty(text);
2064   txt_clean_text(text);
2065   return true;
2066 }
2067 
2068 /**
2069  * Generic prefix operation, use for comment & indent.
2070  *
2071  * \note caller must handle undo.
2072  */
txt_select_prefix(Text * text,const char * add,bool skip_blank_lines)2073 static void txt_select_prefix(Text *text, const char *add, bool skip_blank_lines)
2074 {
2075   int len, num, curc_old, selc_old;
2076   char *tmp;
2077 
2078   const int indentlen = strlen(add);
2079 
2080   BLI_assert(!ELEM(NULL, text->curl, text->sell));
2081 
2082   curc_old = text->curc;
2083   selc_old = text->selc;
2084 
2085   num = 0;
2086   while (true) {
2087 
2088     /* don't indent blank lines */
2089     if ((text->curl->len != 0) || (skip_blank_lines == 0)) {
2090       tmp = MEM_mallocN(text->curl->len + indentlen + 1, "textline_string");
2091 
2092       text->curc = 0;
2093       if (text->curc) {
2094         memcpy(tmp, text->curl->line, text->curc); /* XXX never true, check prev line */
2095       }
2096       memcpy(tmp + text->curc, add, indentlen);
2097 
2098       len = text->curl->len - text->curc;
2099       if (len > 0) {
2100         memcpy(tmp + text->curc + indentlen, text->curl->line + text->curc, len);
2101       }
2102       tmp[text->curl->len + indentlen] = 0;
2103 
2104       make_new_line(text->curl, tmp);
2105 
2106       text->curc += indentlen;
2107 
2108       txt_make_dirty(text);
2109       txt_clean_text(text);
2110     }
2111 
2112     if (text->curl == text->sell) {
2113       if (text->curl->len != 0) {
2114         text->selc += indentlen;
2115       }
2116       break;
2117     }
2118 
2119     text->curl = text->curl->next;
2120     num++;
2121   }
2122 
2123   while (num > 0) {
2124     text->curl = text->curl->prev;
2125     num--;
2126   }
2127 
2128   /* Keep the cursor left aligned if we don't have a selection. */
2129   if (curc_old == 0 && !(text->curl == text->sell && curc_old == selc_old)) {
2130     if (text->curl == text->sell) {
2131       if (text->curc == text->selc) {
2132         text->selc = 0;
2133       }
2134     }
2135     text->curc = 0;
2136   }
2137   else {
2138     if (text->curl->len != 0) {
2139       text->curc = curc_old + indentlen;
2140     }
2141   }
2142 }
2143 
2144 /**
2145  * Generic un-prefix operation, use for comment & indent.
2146  *
2147  * \param require_all: When true, all non-empty lines must have this prefix.
2148  * Needed for comments where we might want to un-comment a block which contains some comments.
2149  *
2150  * \note caller must handle undo.
2151  */
txt_select_unprefix(Text * text,const char * remove,const bool require_all)2152 static bool txt_select_unprefix(Text *text, const char *remove, const bool require_all)
2153 {
2154   int num = 0;
2155   const int indentlen = strlen(remove);
2156   bool unindented_first = false;
2157   bool changed_any = false;
2158 
2159   BLI_assert(!ELEM(NULL, text->curl, text->sell));
2160 
2161   if (require_all) {
2162     /* Check all non-empty lines use this 'remove',
2163      * so the operation is applied equally or not at all. */
2164     TextLine *l = text->curl;
2165     while (true) {
2166       if (STREQLEN(l->line, remove, indentlen)) {
2167         /* pass */
2168       }
2169       else {
2170         /* Blank lines or whitespace can be skipped. */
2171         for (int i = 0; i < l->len; i++) {
2172           if (!ELEM(l->line[i], '\t', ' ')) {
2173             return false;
2174           }
2175         }
2176       }
2177       if (l == text->sell) {
2178         break;
2179       }
2180       l = l->next;
2181     }
2182   }
2183 
2184   while (true) {
2185     bool changed = false;
2186     if (STREQLEN(text->curl->line, remove, indentlen)) {
2187       if (num == 0) {
2188         unindented_first = true;
2189       }
2190       text->curl->len -= indentlen;
2191       memmove(text->curl->line, text->curl->line + indentlen, text->curl->len + 1);
2192       changed = true;
2193       changed_any = true;
2194     }
2195 
2196     txt_make_dirty(text);
2197     txt_clean_text(text);
2198 
2199     if (text->curl == text->sell) {
2200       if (changed) {
2201         text->selc = MAX2(text->selc - indentlen, 0);
2202       }
2203       break;
2204     }
2205 
2206     text->curl = text->curl->next;
2207     num++;
2208   }
2209 
2210   if (unindented_first) {
2211     text->curc = MAX2(text->curc - indentlen, 0);
2212   }
2213 
2214   while (num > 0) {
2215     text->curl = text->curl->prev;
2216     num--;
2217   }
2218 
2219   /* caller must handle undo */
2220   return changed_any;
2221 }
2222 
txt_comment(Text * text)2223 void txt_comment(Text *text)
2224 {
2225   const char *prefix = "#";
2226 
2227   if (ELEM(NULL, text->curl, text->sell)) {
2228     return;
2229   }
2230 
2231   const bool skip_blank_lines = txt_has_sel(text);
2232   txt_select_prefix(text, prefix, skip_blank_lines);
2233 }
2234 
txt_uncomment(Text * text)2235 bool txt_uncomment(Text *text)
2236 {
2237   const char *prefix = "#";
2238 
2239   if (ELEM(NULL, text->curl, text->sell)) {
2240     return false;
2241   }
2242 
2243   return txt_select_unprefix(text, prefix, true);
2244 }
2245 
txt_indent(Text * text)2246 void txt_indent(Text *text)
2247 {
2248   const char *prefix = (text->flags & TXT_TABSTOSPACES) ? tab_to_spaces : "\t";
2249 
2250   if (ELEM(NULL, text->curl, text->sell)) {
2251     return;
2252   }
2253 
2254   txt_select_prefix(text, prefix, true);
2255 }
2256 
txt_unindent(Text * text)2257 bool txt_unindent(Text *text)
2258 {
2259   const char *prefix = (text->flags & TXT_TABSTOSPACES) ? tab_to_spaces : "\t";
2260 
2261   if (ELEM(NULL, text->curl, text->sell)) {
2262     return false;
2263   }
2264 
2265   return txt_select_unprefix(text, prefix, false);
2266 }
2267 
txt_move_lines(struct Text * text,const int direction)2268 void txt_move_lines(struct Text *text, const int direction)
2269 {
2270   TextLine *line_other;
2271 
2272   BLI_assert(ELEM(direction, TXT_MOVE_LINE_UP, TXT_MOVE_LINE_DOWN));
2273 
2274   if (!text->curl || !text->sell) {
2275     return;
2276   }
2277 
2278   txt_order_cursors(text, false);
2279 
2280   line_other = (direction == TXT_MOVE_LINE_DOWN) ? text->sell->next : text->curl->prev;
2281 
2282   if (!line_other) {
2283     return;
2284   }
2285 
2286   BLI_remlink(&text->lines, line_other);
2287 
2288   if (direction == TXT_MOVE_LINE_DOWN) {
2289     BLI_insertlinkbefore(&text->lines, text->curl, line_other);
2290   }
2291   else {
2292     BLI_insertlinkafter(&text->lines, text->sell, line_other);
2293   }
2294 
2295   txt_make_dirty(text);
2296   txt_clean_text(text);
2297 }
2298 
txt_setcurr_tab_spaces(Text * text,int space)2299 int txt_setcurr_tab_spaces(Text *text, int space)
2300 {
2301   int i = 0;
2302   int test = 0;
2303   const char *word = ":";
2304   const char *comm = "#";
2305   const char indent = (text->flags & TXT_TABSTOSPACES) ? ' ' : '\t';
2306   static const char *back_words[] = {"return", "break", "continue", "pass", "yield", NULL};
2307 
2308   if (!text->curl) {
2309     return 0;
2310   }
2311 
2312   while (text->curl->line[i] == indent) {
2313     // we only count those tabs/spaces that are before any text or before the curs;
2314     if (i == text->curc) {
2315       return i;
2316     }
2317 
2318     i++;
2319   }
2320   if (strstr(text->curl->line, word)) {
2321     /* if we find a ':' on this line, then add a tab but not if it is:
2322      * 1) in a comment
2323      * 2) within an identifier
2324      * 3) after the cursor (text->curc), i.e. when creating space before a function def T25414.
2325      */
2326     int a;
2327     bool is_indent = false;
2328     for (a = 0; (a < text->curc) && (text->curl->line[a] != '\0'); a++) {
2329       char ch = text->curl->line[a];
2330       if (ch == '#') {
2331         break;
2332       }
2333       if (ch == ':') {
2334         is_indent = 1;
2335       }
2336       else if (ch != ' ' && ch != '\t') {
2337         is_indent = 0;
2338       }
2339     }
2340     if (is_indent) {
2341       i += space;
2342     }
2343   }
2344 
2345   for (test = 0; back_words[test]; test++) {
2346     /* if there are these key words then remove a tab because we are done with the block */
2347     if (strstr(text->curl->line, back_words[test]) && i > 0) {
2348       if (strcspn(text->curl->line, back_words[test]) < strcspn(text->curl->line, comm)) {
2349         i -= space;
2350       }
2351     }
2352   }
2353   return i;
2354 }
2355 
2356 /** \} */
2357 
2358 /* -------------------------------------------------------------------- */
2359 /** \name Character Queries
2360  * \{ */
2361 
text_check_bracket(const char ch)2362 int text_check_bracket(const char ch)
2363 {
2364   int a;
2365   char opens[] = "([{";
2366   char close[] = ")]}";
2367 
2368   for (a = 0; a < (sizeof(opens) - 1); a++) {
2369     if (ch == opens[a]) {
2370       return a + 1;
2371     }
2372     if (ch == close[a]) {
2373       return -(a + 1);
2374     }
2375   }
2376   return 0;
2377 }
2378 
2379 /* TODO, have a function for operators -
2380  * http://docs.python.org/py3k/reference/lexical_analysis.html#operators */
text_check_delim(const char ch)2381 bool text_check_delim(const char ch)
2382 {
2383   int a;
2384   char delims[] = "():\"\' ~!%^&*-+=[]{};/<>|.#\t,@";
2385 
2386   for (a = 0; a < (sizeof(delims) - 1); a++) {
2387     if (ch == delims[a]) {
2388       return true;
2389     }
2390   }
2391   return false;
2392 }
2393 
text_check_digit(const char ch)2394 bool text_check_digit(const char ch)
2395 {
2396   if (ch < '0') {
2397     return false;
2398   }
2399   if (ch <= '9') {
2400     return true;
2401   }
2402   return false;
2403 }
2404 
text_check_identifier(const char ch)2405 bool text_check_identifier(const char ch)
2406 {
2407   if (ch < '0') {
2408     return false;
2409   }
2410   if (ch <= '9') {
2411     return true;
2412   }
2413   if (ch < 'A') {
2414     return false;
2415   }
2416   if (ch <= 'Z' || ch == '_') {
2417     return true;
2418   }
2419   if (ch < 'a') {
2420     return false;
2421   }
2422   if (ch <= 'z') {
2423     return true;
2424   }
2425   return false;
2426 }
2427 
text_check_identifier_nodigit(const char ch)2428 bool text_check_identifier_nodigit(const char ch)
2429 {
2430   if (ch <= '9') {
2431     return false;
2432   }
2433   if (ch < 'A') {
2434     return false;
2435   }
2436   if (ch <= 'Z' || ch == '_') {
2437     return true;
2438   }
2439   if (ch < 'a') {
2440     return false;
2441   }
2442   if (ch <= 'z') {
2443     return true;
2444   }
2445   return false;
2446 }
2447 
2448 #ifndef WITH_PYTHON
text_check_identifier_unicode(const unsigned int ch)2449 int text_check_identifier_unicode(const unsigned int ch)
2450 {
2451   return (ch < 255 && text_check_identifier((unsigned int)ch));
2452 }
2453 
text_check_identifier_nodigit_unicode(const unsigned int ch)2454 int text_check_identifier_nodigit_unicode(const unsigned int ch)
2455 {
2456   return (ch < 255 && text_check_identifier_nodigit((char)ch));
2457 }
2458 #endif /* WITH_PYTHON */
2459 
text_check_whitespace(const char ch)2460 bool text_check_whitespace(const char ch)
2461 {
2462   if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') {
2463     return true;
2464   }
2465   return false;
2466 }
2467 
text_find_identifier_start(const char * str,int i)2468 int text_find_identifier_start(const char *str, int i)
2469 {
2470   if (UNLIKELY(i <= 0)) {
2471     return 0;
2472   }
2473 
2474   while (i--) {
2475     if (!text_check_identifier(str[i])) {
2476       break;
2477     }
2478   }
2479   i++;
2480   return i;
2481 }
2482 
2483 /** \} */
2484