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