Cleanup: Remove more #if 0 blocks
[blender.git] / source / blender / blenkernel / intern / text.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): none yet.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/blenkernel/intern/text.c
29  *  \ingroup bke
30  */
31
32 #include <stdlib.h> /* abort */
33 #include <string.h> /* strstr */
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <wchar.h>
37 #include <wctype.h>
38
39 #include "MEM_guardedalloc.h"
40
41 #include "BLI_utildefines.h"
42 #include "BLI_path_util.h"
43 #include "BLI_string.h"
44 #include "BLI_string_cursor_utf8.h"
45 #include "BLI_string_utf8.h"
46 #include "BLI_listbase.h"
47 #include "BLI_fileops.h"
48
49 #include "DNA_constraint_types.h"
50 #include "DNA_scene_types.h"
51 #include "DNA_screen_types.h"
52 #include "DNA_space_types.h"
53 #include "DNA_text_types.h"
54 #include "DNA_userdef_types.h"
55 #include "DNA_object_types.h"
56 #include "DNA_node_types.h"
57 #include "DNA_material_types.h"
58
59 #include "BKE_global.h"
60 #include "BKE_library.h"
61 #include "BKE_main.h"
62 #include "BKE_text.h"
63 #include "BKE_node.h"
64
65
66 #ifdef WITH_PYTHON
67 #include "BPY_extern.h"
68 #endif
69
70 /*
71  * How Texts should work
72  * --
73  * A text should relate to a file as follows -
74  * (Text *)->name should be the place where the
75  *     file will or has been saved.
76  *
77  * (Text *)->flags has the following bits
78  *     TXT_ISDIRTY - should always be set if the file in mem. differs from
79  *                     the file on disk, or if there is no file on disk.
80  *     TXT_ISMEM - should always be set if the Text has not been mapped to
81  *                     a file, in which case (Text *)->name may be NULL or garbage.
82  *     TXT_ISEXT - should always be set if the Text is not to be written into
83  *                     the .blend
84  *     TXT_ISSCRIPT - should be set if the user has designated the text
85  *                     as a script. (NEW: this was unused, but now it is needed by
86  *                     space handler script links (see header_view3d.c, for example)
87  *
88  * ->>> see also: /makesdna/DNA_text_types.h
89  *
90  * Display
91  * --
92  * The st->top determines at what line the top of the text is displayed.
93  * If the user moves the cursor the st containing that cursor should
94  * be popped ... other st's retain their own top location.
95  *
96  * Undo
97  * --
98  * Undo/Redo works by storing
99  * events in a queue, and a pointer
100  * to the current position in the
101  * queue...
102  *
103  * Events are stored using an
104  * arbitrary op-code system
105  * to keep track of
106  * a) the two cursors (normal and selected)
107  * b) input (visible and control (ie backspace))
108  *
109  * input data is stored as its
110  * ASCII value, the opcodes are
111  * then selected to not conflict.
112  *
113  * opcodes with data in between are
114  * written at the beginning and end
115  * of the data to allow undo and redo
116  * to simply check the code at the current
117  * undo position
118  *
119  */
120
121
122 /* Undo opcodes */
123
124 enum {
125         /* Complex editing */
126         /* 1 - opcode is followed by 1 byte for ascii character and opcode (repeat)) */
127         /* 2 - opcode is followed by 2 bytes for utf-8 character and opcode (repeat)) */
128         /* 3 - opcode is followed by 3 bytes for utf-8 character and opcode (repeat)) */
129         /* 4 - opcode is followed by 4 bytes for unicode character and opcode (repeat)) */
130         UNDO_INSERT_1   = 013,
131         UNDO_INSERT_2   = 014,
132         UNDO_INSERT_3   = 015,
133         UNDO_INSERT_4   = 016,
134
135         UNDO_BS_1       = 017,
136         UNDO_BS_2       = 020,
137         UNDO_BS_3       = 021,
138         UNDO_BS_4       = 022,
139
140         UNDO_DEL_1      = 023,
141         UNDO_DEL_2      = 024,
142         UNDO_DEL_3      = 025,
143         UNDO_DEL_4      = 026,
144
145         /* Text block (opcode is followed
146          * by 4 character length ID + the text
147          * block itself + the 4 character length
148          * ID (repeat) and opcode (repeat)) */
149         UNDO_DBLOCK     = 027, /* Delete block */
150         UNDO_IBLOCK     = 030, /* Insert block */
151
152         /* Misc */
153         UNDO_INDENT     = 032,
154         UNDO_UNINDENT   = 033,
155         UNDO_COMMENT    = 034,
156         UNDO_UNCOMMENT  = 035,
157
158         UNDO_MOVE_LINES_UP      = 036,
159         UNDO_MOVE_LINES_DOWN    = 037,
160
161         UNDO_DUPLICATE  = 040,
162 };
163
164 /***/
165
166 static void txt_pop_first(Text *text);
167 static void txt_pop_last(Text *text);
168 static void txt_undo_add_blockop(Text *text, TextUndoBuf *utxt, int op, const char *buf);
169 static void txt_delete_line(Text *text, TextLine *line);
170 static void txt_delete_sel(Text *text, TextUndoBuf *utxt);
171 static void txt_make_dirty(Text *text);
172
173 /***/
174
175 /**
176  * Set to true when undoing (so we don't generate undo steps while undoing).
177  *
178  * Also use to disable undo entirely.
179  */
180 static bool undoing;
181
182 /**
183  * \note caller must handle `undo_buf` and `compiled` members.
184  */
185 void BKE_text_free_lines(Text *text)
186 {
187         for (TextLine *tmp = text->lines.first, *tmp_next; tmp; tmp = tmp_next) {
188                 tmp_next = tmp->next;
189                 MEM_freeN(tmp->line);
190                 if (tmp->format) {
191                         MEM_freeN(tmp->format);
192                 }
193                 MEM_freeN(tmp);
194         }
195
196         BLI_listbase_clear(&text->lines);
197
198         text->curl = text->sell = NULL;
199 }
200
201 /** Free (or release) any data used by this text (does not free the text itself). */
202 void BKE_text_free(Text *text)
203 {
204         /* No animdata here. */
205
206         BKE_text_free_lines(text);
207
208         MEM_SAFE_FREE(text->name);
209 #ifdef WITH_PYTHON
210         BPY_text_free_code(text);
211 #endif
212 }
213
214 void BKE_text_init(Text *ta)
215 {
216         TextLine *tmp;
217
218         BLI_assert(MEMCMP_STRUCT_OFS_IS_ZERO(ta, id));
219
220         ta->name = NULL;
221
222         ta->nlines = 1;
223         ta->flags = TXT_ISDIRTY | TXT_ISMEM;
224         if ((U.flag & USER_TXT_TABSTOSPACES_DISABLE) == 0)
225                 ta->flags |= TXT_TABSTOSPACES;
226
227         BLI_listbase_clear(&ta->lines);
228
229         tmp = (TextLine *) MEM_mallocN(sizeof(TextLine), "textline");
230         tmp->line = (char *) MEM_mallocN(1, "textline_string");
231         tmp->format = NULL;
232
233         tmp->line[0] = 0;
234         tmp->len = 0;
235
236         tmp->next = NULL;
237         tmp->prev = NULL;
238
239         BLI_addhead(&ta->lines, tmp);
240
241         ta->curl = ta->lines.first;
242         ta->curc = 0;
243         ta->sell = ta->lines.first;
244         ta->selc = 0;
245 }
246
247 Text *BKE_text_add(Main *bmain, const char *name)
248 {
249         Text *ta;
250
251         ta = BKE_libblock_alloc(bmain, ID_TXT, name, 0);
252
253         BKE_text_init(ta);
254
255         return ta;
256 }
257
258 /* this function replaces extended ascii characters */
259 /* to a valid utf-8 sequences */
260 int txt_extended_ascii_as_utf8(char **str)
261 {
262         ptrdiff_t bad_char, i = 0;
263         const ptrdiff_t length = (ptrdiff_t)strlen(*str);
264         int added = 0;
265
266         while ((*str)[i]) {
267                 if ((bad_char = BLI_utf8_invalid_byte(*str + i, length - i)) == -1)
268                         break;
269
270                 added++;
271                 i += bad_char + 1;
272         }
273
274         if (added != 0) {
275                 char *newstr = MEM_mallocN(length + added + 1, "text_line");
276                 ptrdiff_t mi = 0;
277                 i = 0;
278
279                 while ((*str)[i]) {
280                         if ((bad_char = BLI_utf8_invalid_byte((*str) + i, length - i)) == -1) {
281                                 memcpy(newstr + mi, (*str) + i, length - i + 1);
282                                 break;
283                         }
284
285                         memcpy(newstr + mi, (*str) + i, bad_char);
286
287                         BLI_str_utf8_from_unicode((*str)[i + bad_char], newstr + mi + bad_char);
288                         i += bad_char + 1;
289                         mi += bad_char + 2;
290                 }
291                 newstr[length + added] = '\0';
292                 MEM_freeN(*str);
293                 *str = newstr;
294         }
295
296         return added;
297 }
298
299 // this function removes any control characters from
300 // a textline and fixes invalid utf-8 sequences
301
302 static void cleanup_textline(TextLine *tl)
303 {
304         int i;
305
306         for (i = 0; i < tl->len; i++) {
307                 if (tl->line[i] < ' ' && tl->line[i] != '\t') {
308                         memmove(tl->line + i, tl->line + i + 1, tl->len - i);
309                         tl->len--;
310                         i--;
311                 }
312         }
313         tl->len += txt_extended_ascii_as_utf8(&tl->line);
314 }
315
316 /**
317  * used for load and reload (unlike txt_insert_buf)
318  * assumes all fields are empty
319  */
320 static void text_from_buf(Text *text, const unsigned char *buffer, const int len)
321 {
322         int i, llen;
323
324         BLI_assert(BLI_listbase_is_empty(&text->lines));
325
326         text->nlines = 0;
327         llen = 0;
328         for (i = 0; i < len; i++) {
329                 if (buffer[i] == '\n') {
330                         TextLine *tmp;
331
332                         tmp = (TextLine *) MEM_mallocN(sizeof(TextLine), "textline");
333                         tmp->line = (char *) MEM_mallocN(llen + 1, "textline_string");
334                         tmp->format = NULL;
335
336                         if (llen) memcpy(tmp->line, &buffer[i - llen], llen);
337                         tmp->line[llen] = 0;
338                         tmp->len = llen;
339
340                         cleanup_textline(tmp);
341
342                         BLI_addtail(&text->lines, tmp);
343                         text->nlines++;
344
345                         llen = 0;
346                         continue;
347                 }
348                 llen++;
349         }
350
351         /* create new line in cases:
352          * - rest of line (if last line in file hasn't got \n terminator).
353          *   in this case content of such line would be used to fill text line buffer
354          * - file is empty. in this case new line is needed to start editing from.
355          * - last character in buffer is \n. in this case new line is needed to
356          *   deal with newline at end of file. (see [#28087]) (sergey) */
357         if (llen != 0 || text->nlines == 0 || buffer[len - 1] == '\n') {
358                 TextLine *tmp;
359
360                 tmp = (TextLine *) MEM_mallocN(sizeof(TextLine), "textline");
361                 tmp->line = (char *) MEM_mallocN(llen + 1, "textline_string");
362                 tmp->format = NULL;
363
364                 if (llen) memcpy(tmp->line, &buffer[i - llen], llen);
365
366                 tmp->line[llen] = 0;
367                 tmp->len = llen;
368
369                 cleanup_textline(tmp);
370
371                 BLI_addtail(&text->lines, tmp);
372                 text->nlines++;
373         }
374
375         text->curl = text->sell = text->lines.first;
376         text->curc = text->selc = 0;
377 }
378
379 bool BKE_text_reload(Text *text)
380 {
381         unsigned char *buffer;
382         size_t buffer_len;
383         char filepath_abs[FILE_MAX];
384         BLI_stat_t st;
385
386         if (!text->name) {
387                 return false;
388         }
389
390         BLI_strncpy(filepath_abs, text->name, FILE_MAX);
391         BLI_path_abs(filepath_abs, BKE_main_blendfile_path_from_global());
392
393         buffer = BLI_file_read_text_as_mem(filepath_abs, 0, &buffer_len);
394         if (buffer == NULL) {
395                 return false;
396         }
397
398         /* free memory: */
399         BKE_text_free_lines(text);
400         txt_make_dirty(text);
401
402         /* clear undo buffer */
403         if (BLI_stat(filepath_abs, &st) != -1) {
404                 text->mtime = st.st_mtime;
405         }
406         else {
407                 text->mtime = 0;
408         }
409
410         text_from_buf(text, buffer, buffer_len);
411
412         MEM_freeN(buffer);
413         return true;
414 }
415
416 Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const bool is_internal)
417 {
418         unsigned char *buffer;
419         size_t buffer_len;
420         Text *ta;
421         char filepath_abs[FILE_MAX];
422         BLI_stat_t st;
423
424         BLI_strncpy(filepath_abs, file, FILE_MAX);
425         if (relpath) /* can be NULL (bg mode) */
426                 BLI_path_abs(filepath_abs, relpath);
427
428         buffer = BLI_file_read_text_as_mem(filepath_abs, 0, &buffer_len);
429         if (buffer == NULL) {
430                 return false;
431         }
432
433         ta = BKE_libblock_alloc(bmain, ID_TXT, BLI_path_basename(filepath_abs), 0);
434         ta->id.us = 0;
435
436         BLI_listbase_clear(&ta->lines);
437         ta->curl = ta->sell = NULL;
438
439         if ((U.flag & USER_TXT_TABSTOSPACES_DISABLE) == 0)
440                 ta->flags = TXT_TABSTOSPACES;
441
442         if (is_internal == false) {
443                 ta->name = MEM_mallocN(strlen(file) + 1, "text_name");
444                 strcpy(ta->name, file);
445         }
446         else {
447                 ta->flags |= TXT_ISMEM | TXT_ISDIRTY;
448         }
449
450         /* clear undo buffer */
451         if (BLI_stat(filepath_abs, &st) != -1) {
452                 ta->mtime = st.st_mtime;
453         }
454         else {
455                 ta->mtime = 0;
456         }
457
458         text_from_buf(ta, buffer, buffer_len);
459
460         MEM_freeN(buffer);
461
462         return ta;
463 }
464
465 Text *BKE_text_load(Main *bmain, const char *file, const char *relpath)
466 {
467         return BKE_text_load_ex(bmain, file, relpath, false);
468 }
469
470 /**
471  * Only copy internal data of Text ID from source to already allocated/initialized destination.
472  * You probably nerver want to use that directly, use id_copy or BKE_id_copy_ex for typical needs.
473  *
474  * WARNING! This function will not handle ID user count!
475  *
476  * \param flag  Copying options (see BKE_library.h's LIB_ID_COPY_... flags for more).
477  */
478 void BKE_text_copy_data(Main *UNUSED(bmain), Text *ta_dst, const Text *ta_src, const int UNUSED(flag))
479 {
480         /* file name can be NULL */
481         if (ta_src->name) {
482                 ta_dst->name = BLI_strdup(ta_src->name);
483         }
484
485         ta_dst->flags |= TXT_ISDIRTY;
486
487         BLI_listbase_clear(&ta_dst->lines);
488         ta_dst->curl = ta_dst->sell = NULL;
489         ta_dst->compiled = NULL;
490
491         /* Walk down, reconstructing */
492         for (TextLine *line_src = ta_src->lines.first; line_src; line_src = line_src->next) {
493                 TextLine *line_dst = MEM_mallocN(sizeof(*line_dst), __func__);
494
495                 line_dst->line = BLI_strdup(line_src->line);
496                 line_dst->format = NULL;
497                 line_dst->len = line_src->len;
498
499                 BLI_addtail(&ta_dst->lines, line_dst);
500         }
501
502         ta_dst->curl = ta_dst->sell = ta_dst->lines.first;
503         ta_dst->curc = ta_dst->selc = 0;
504 }
505
506 Text *BKE_text_copy(Main *bmain, const Text *ta)
507 {
508         Text *ta_copy;
509         BKE_id_copy_ex(bmain, &ta->id, (ID **)&ta_copy, 0, false);
510         return ta_copy;
511 }
512
513 void BKE_text_make_local(Main *bmain, Text *text, const bool lib_local)
514 {
515         BKE_id_make_local_generic(bmain, &text->id, true, lib_local);
516 }
517
518 void BKE_text_clear(Text *text, TextUndoBuf *utxt) /* called directly from rna */
519 {
520         const bool undoing_orig = undoing;
521         undoing = (utxt == NULL);
522
523         txt_sel_all(text);
524         txt_delete_sel(text, utxt);
525
526         undoing = undoing_orig;
527
528         txt_make_dirty(text);
529 }
530
531 void BKE_text_write(Text *text, TextUndoBuf *utxt, const char *str) /* called directly from rna */
532 {
533         const bool undoing_orig = undoing;
534         undoing = (utxt == NULL);
535
536         txt_insert_buf(text, utxt, str);
537         txt_move_eof(text, 0);
538
539         undoing = undoing_orig;
540
541         txt_make_dirty(text);
542 }
543
544
545 /* returns 0 if file on disk is the same or Text is in memory only
546  * returns 1 if file has been modified on disk since last local edit
547  * returns 2 if file on disk has been deleted
548  * -1 is returned if an error occurs */
549
550 int BKE_text_file_modified_check(Text *text)
551 {
552         BLI_stat_t st;
553         int result;
554         char file[FILE_MAX];
555
556         if (!text->name)
557                 return 0;
558
559         BLI_strncpy(file, text->name, FILE_MAX);
560         BLI_path_abs(file, BKE_main_blendfile_path_from_global());
561
562         if (!BLI_exists(file))
563                 return 2;
564
565         result = BLI_stat(file, &st);
566
567         if (result == -1)
568                 return -1;
569
570         if ((st.st_mode & S_IFMT) != S_IFREG)
571                 return -1;
572
573         if (st.st_mtime > text->mtime)
574                 return 1;
575
576         return 0;
577 }
578
579 void BKE_text_file_modified_ignore(Text *text)
580 {
581         BLI_stat_t st;
582         int result;
583         char file[FILE_MAX];
584
585         if (!text->name) return;
586
587         BLI_strncpy(file, text->name, FILE_MAX);
588         BLI_path_abs(file, BKE_main_blendfile_path_from_global());
589
590         if (!BLI_exists(file)) return;
591
592         result = BLI_stat(file, &st);
593
594         if (result == -1 || (st.st_mode & S_IFMT) != S_IFREG)
595                 return;
596
597         text->mtime = st.st_mtime;
598 }
599
600 /*****************************/
601 /* Editing utility functions */
602 /*****************************/
603
604 static void make_new_line(TextLine *line, char *newline)
605 {
606         if (line->line) MEM_freeN(line->line);
607         if (line->format) MEM_freeN(line->format);
608
609         line->line = newline;
610         line->len = strlen(newline);
611         line->format = NULL;
612 }
613
614 static TextLine *txt_new_line(const char *str)
615 {
616         TextLine *tmp;
617
618         if (!str) str = "";
619
620         tmp = (TextLine *) MEM_mallocN(sizeof(TextLine), "textline");
621         tmp->line = MEM_mallocN(strlen(str) + 1, "textline_string");
622         tmp->format = NULL;
623
624         strcpy(tmp->line, str);
625
626         tmp->len = strlen(str);
627         tmp->next = tmp->prev = NULL;
628
629         return tmp;
630 }
631
632 static TextLine *txt_new_linen(const char *str, int n)
633 {
634         TextLine *tmp;
635
636         tmp = (TextLine *) MEM_mallocN(sizeof(TextLine), "textline");
637         tmp->line = MEM_mallocN(n + 1, "textline_string");
638         tmp->format = NULL;
639
640         BLI_strncpy(tmp->line, (str) ? str : "", n + 1);
641
642         tmp->len = strlen(tmp->line);
643         tmp->next = tmp->prev = NULL;
644
645         return tmp;
646 }
647
648 void txt_clean_text(Text *text)
649 {
650         TextLine **top, **bot;
651
652         if (!text->lines.first) {
653                 if (text->lines.last) text->lines.first = text->lines.last;
654                 else text->lines.first = text->lines.last = txt_new_line(NULL);
655         }
656
657         if (!text->lines.last) text->lines.last = text->lines.first;
658
659         top = (TextLine **) &text->lines.first;
660         bot = (TextLine **) &text->lines.last;
661
662         while ((*top)->prev) *top = (*top)->prev;
663         while ((*bot)->next) *bot = (*bot)->next;
664
665         if (!text->curl) {
666                 if (text->sell) text->curl = text->sell;
667                 else text->curl = text->lines.first;
668                 text->curc = 0;
669         }
670
671         if (!text->sell) {
672                 text->sell = text->curl;
673                 text->selc = 0;
674         }
675 }
676
677 int txt_get_span(TextLine *from, TextLine *to)
678 {
679         int ret = 0;
680         TextLine *tmp = from;
681
682         if (!to || !from) return 0;
683         if (from == to) return 0;
684
685         /* Look forwards */
686         while (tmp) {
687                 if (tmp == to) return ret;
688                 ret++;
689                 tmp = tmp->next;
690         }
691
692         /* Look backwards */
693         if (!tmp) {
694                 tmp = from;
695                 ret = 0;
696                 while (tmp) {
697                         if (tmp == to) break;
698                         ret--;
699                         tmp = tmp->prev;
700                 }
701                 if (!tmp) ret = 0;
702         }
703
704         return ret;
705 }
706
707 static void txt_make_dirty(Text *text)
708 {
709         text->flags |= TXT_ISDIRTY;
710 #ifdef WITH_PYTHON
711         if (text->compiled) BPY_text_free_code(text);
712 #endif
713 }
714
715 /****************************/
716 /* Cursor utility functions */
717 /****************************/
718
719 static void txt_curs_cur(Text *text, TextLine ***linep, int **charp)
720 {
721         *linep = &text->curl; *charp = &text->curc;
722 }
723
724 static void txt_curs_sel(Text *text, TextLine ***linep, int **charp)
725 {
726         *linep = &text->sell; *charp = &text->selc;
727 }
728
729 bool txt_cursor_is_line_start(Text *text)
730 {
731         return (text->selc == 0);
732 }
733
734 bool txt_cursor_is_line_end(Text *text)
735 {
736         return (text->selc == text->sell->len);
737 }
738
739 /*****************************/
740 /* Cursor movement functions */
741 /*****************************/
742
743 int txt_utf8_offset_to_index(const char *str, int offset)
744 {
745         int index = 0, pos = 0;
746         while (pos != offset) {
747                 pos += BLI_str_utf8_size(str + pos);
748                 index++;
749         }
750         return index;
751 }
752
753 int txt_utf8_index_to_offset(const char *str, int index)
754 {
755         int offset = 0, pos = 0;
756         while (pos != index) {
757                 offset += BLI_str_utf8_size(str + offset);
758                 pos++;
759         }
760         return offset;
761 }
762
763 int txt_utf8_offset_to_column(const char *str, int offset)
764 {
765         int column = 0, pos = 0;
766         while (pos < offset) {
767                 column += BLI_str_utf8_char_width_safe(str + pos);
768                 pos += BLI_str_utf8_size_safe(str + pos);
769         }
770         return column;
771 }
772
773 int txt_utf8_column_to_offset(const char *str, int column)
774 {
775         int offset = 0, pos = 0, col;
776         while (*(str + offset) && pos < column) {
777                 col = BLI_str_utf8_char_width_safe(str + offset);
778                 if (pos + col > column)
779                         break;
780                 offset += BLI_str_utf8_size_safe(str + offset);
781                 pos += col;
782         }
783         return offset;
784 }
785
786 void txt_move_up(Text *text, const bool sel)
787 {
788         TextLine **linep;
789         int *charp;
790
791         if (sel) txt_curs_sel(text, &linep, &charp);
792         else { txt_pop_first(text); txt_curs_cur(text, &linep, &charp); }
793         if (!*linep) return;
794
795         if ((*linep)->prev) {
796                 int column = txt_utf8_offset_to_column((*linep)->line, *charp);
797                 *linep = (*linep)->prev;
798                 *charp = txt_utf8_column_to_offset((*linep)->line, column);
799
800         }
801         else {
802                 txt_move_bol(text, sel);
803         }
804
805         if (!sel) txt_pop_sel(text);
806 }
807
808 void txt_move_down(Text *text, const bool sel)
809 {
810         TextLine **linep;
811         int *charp;
812
813         if (sel) txt_curs_sel(text, &linep, &charp);
814         else { txt_pop_last(text); txt_curs_cur(text, &linep, &charp); }
815         if (!*linep) return;
816
817         if ((*linep)->next) {
818                 int column = txt_utf8_offset_to_column((*linep)->line, *charp);
819                 *linep = (*linep)->next;
820                 *charp = txt_utf8_column_to_offset((*linep)->line, column);
821         }
822         else {
823                 txt_move_eol(text, sel);
824         }
825
826         if (!sel) txt_pop_sel(text);
827 }
828
829 int txt_calc_tab_left(TextLine *tl, int ch)
830 {
831         /* do nice left only if there are only spaces */
832
833         int tabsize = (ch < TXT_TABSIZE) ? ch : TXT_TABSIZE;
834
835         for (int i = 0; i < ch; i++)
836                 if (tl->line[i] != ' ') {
837                         tabsize = 0;
838                         break;
839                 }
840
841         /* if in the middle of the space-tab */
842         if (tabsize && ch % TXT_TABSIZE != 0)
843                 tabsize = (ch % TXT_TABSIZE);
844         return tabsize;
845 }
846
847 int txt_calc_tab_right(TextLine *tl, int ch)
848 {
849         if (tl->line[ch] == ' ') {
850                 int i;
851                 for (i = 0; i < ch; i++) {
852                         if (tl->line[i] != ' ') {
853                                 return 0;
854                         }
855                 }
856
857                 int tabsize = (ch) % TXT_TABSIZE + 1;
858                 for (i = ch + 1; tl->line[i] == ' ' && tabsize < TXT_TABSIZE; i++) {
859                         tabsize++;
860                 }
861
862                 return i - ch;
863         }
864         else {
865                 return 0;
866         }
867 }
868
869 void txt_move_left(Text *text, const bool sel)
870 {
871         TextLine **linep;
872         int *charp;
873         int tabsize = 0;
874
875         if (sel) txt_curs_sel(text, &linep, &charp);
876         else { txt_pop_first(text); txt_curs_cur(text, &linep, &charp); }
877         if (!*linep) return;
878
879         if (*charp == 0) {
880                 if ((*linep)->prev) {
881                         txt_move_up(text, sel);
882                         *charp = (*linep)->len;
883                 }
884         }
885         else {
886                 /* do nice left only if there are only spaces */
887                 // TXT_TABSIZE hardcoded in DNA_text_types.h
888                 if (text->flags & TXT_TABSTOSPACES) {
889                         tabsize = txt_calc_tab_left(*linep, *charp);
890                 }
891
892                 if (tabsize) {
893                         (*charp) -= tabsize;
894                 }
895                 else {
896                         const char *prev = BLI_str_prev_char_utf8((*linep)->line + *charp);
897                         *charp = prev - (*linep)->line;
898                 }
899         }
900
901         if (!sel) txt_pop_sel(text);
902 }
903
904 void txt_move_right(Text *text, const bool sel)
905 {
906         TextLine **linep;
907         int *charp;
908
909         if (sel) txt_curs_sel(text, &linep, &charp);
910         else { txt_pop_last(text); txt_curs_cur(text, &linep, &charp); }
911         if (!*linep) return;
912
913         if (*charp == (*linep)->len) {
914                 if ((*linep)->next) {
915                         txt_move_down(text, sel);
916                         *charp = 0;
917                 }
918         }
919         else {
920                 /* do nice right only if there are only spaces */
921                 /* spaces hardcoded in DNA_text_types.h */
922                 int tabsize = 0;
923
924                 if (text->flags & TXT_TABSTOSPACES) {
925                         tabsize = txt_calc_tab_right(*linep, *charp);
926                 }
927
928                 if (tabsize) {
929                         (*charp) += tabsize;
930                 }
931                 else {
932                         (*charp) += BLI_str_utf8_size((*linep)->line + *charp);
933                 }
934         }
935
936         if (!sel) txt_pop_sel(text);
937 }
938
939 void txt_jump_left(Text *text, const bool sel, const bool use_init_step)
940 {
941         TextLine **linep;
942         int *charp;
943
944         if (sel) txt_curs_sel(text, &linep, &charp);
945         else { txt_pop_first(text); txt_curs_cur(text, &linep, &charp); }
946         if (!*linep) return;
947
948         BLI_str_cursor_step_utf8((*linep)->line, (*linep)->len,
949                                  charp, STRCUR_DIR_PREV,
950                                  STRCUR_JUMP_DELIM, use_init_step);
951
952         if (!sel) txt_pop_sel(text);
953 }
954
955 void txt_jump_right(Text *text, const bool sel, const bool use_init_step)
956 {
957         TextLine **linep;
958         int *charp;
959
960         if (sel) txt_curs_sel(text, &linep, &charp);
961         else { txt_pop_last(text); txt_curs_cur(text, &linep, &charp); }
962         if (!*linep) return;
963
964         BLI_str_cursor_step_utf8((*linep)->line, (*linep)->len,
965                                  charp, STRCUR_DIR_NEXT,
966                                  STRCUR_JUMP_DELIM, use_init_step);
967
968         if (!sel) txt_pop_sel(text);
969 }
970
971 void txt_move_bol(Text *text, const bool sel)
972 {
973         TextLine **linep;
974         int *charp;
975
976         if (sel) txt_curs_sel(text, &linep, &charp);
977         else txt_curs_cur(text, &linep, &charp);
978         if (!*linep) return;
979
980         *charp = 0;
981
982         if (!sel) txt_pop_sel(text);
983 }
984
985 void txt_move_eol(Text *text, const bool sel)
986 {
987         TextLine **linep;
988         int *charp;
989
990         if (sel) txt_curs_sel(text, &linep, &charp);
991         else txt_curs_cur(text, &linep, &charp);
992         if (!*linep) return;
993
994         *charp = (*linep)->len;
995
996         if (!sel) txt_pop_sel(text);
997 }
998
999 void txt_move_bof(Text *text, const bool sel)
1000 {
1001         TextLine **linep;
1002         int *charp;
1003
1004         if (sel) txt_curs_sel(text, &linep, &charp);
1005         else txt_curs_cur(text, &linep, &charp);
1006         if (!*linep) return;
1007
1008         *linep = text->lines.first;
1009         *charp = 0;
1010
1011         if (!sel) txt_pop_sel(text);
1012 }
1013
1014 void txt_move_eof(Text *text, const bool sel)
1015 {
1016         TextLine **linep;
1017         int *charp;
1018
1019         if (sel) txt_curs_sel(text, &linep, &charp);
1020         else txt_curs_cur(text, &linep, &charp);
1021         if (!*linep) return;
1022
1023         *linep = text->lines.last;
1024         *charp = (*linep)->len;
1025
1026         if (!sel) txt_pop_sel(text);
1027 }
1028
1029 void txt_move_toline(Text *text, unsigned int line, const bool sel)
1030 {
1031         txt_move_to(text, line, 0, sel);
1032 }
1033
1034 /* Moves to a certain byte in a line, not a certain utf8-character! */
1035 void txt_move_to(Text *text, unsigned int line, unsigned int ch, const bool sel)
1036 {
1037         TextLine **linep;
1038         int *charp;
1039         unsigned int i;
1040
1041         if (sel) txt_curs_sel(text, &linep, &charp);
1042         else txt_curs_cur(text, &linep, &charp);
1043         if (!*linep) return;
1044
1045         *linep = text->lines.first;
1046         for (i = 0; i < line; i++) {
1047                 if ((*linep)->next) *linep = (*linep)->next;
1048                 else break;
1049         }
1050         if (ch > (unsigned int)((*linep)->len))
1051                 ch = (unsigned int)((*linep)->len);
1052         *charp = ch;
1053
1054         if (!sel) txt_pop_sel(text);
1055 }
1056
1057 /****************************/
1058 /* Text selection functions */
1059 /****************************/
1060
1061 static void txt_curs_swap(Text *text)
1062 {
1063         TextLine *tmpl;
1064         int tmpc;
1065
1066         tmpl = text->curl;
1067         text->curl = text->sell;
1068         text->sell = tmpl;
1069
1070         tmpc = text->curc;
1071         text->curc = text->selc;
1072         text->selc = tmpc;
1073 }
1074
1075 static void txt_pop_first(Text *text)
1076 {
1077         if (txt_get_span(text->curl, text->sell) < 0 ||
1078             (text->curl == text->sell && text->curc > text->selc))
1079         {
1080                 txt_curs_swap(text);
1081         }
1082
1083         txt_pop_sel(text);
1084 }
1085
1086 static void txt_pop_last(Text *text)
1087 {
1088         if (txt_get_span(text->curl, text->sell) > 0 ||
1089             (text->curl == text->sell && text->curc < text->selc))
1090         {
1091                 txt_curs_swap(text);
1092         }
1093
1094         txt_pop_sel(text);
1095 }
1096
1097 void txt_pop_sel(Text *text)
1098 {
1099         text->sell = text->curl;
1100         text->selc = text->curc;
1101 }
1102
1103 void txt_order_cursors(Text *text, const bool reverse)
1104 {
1105         if (!text->curl) return;
1106         if (!text->sell) return;
1107
1108         /* Flip so text->curl is before/after text->sell */
1109         if (reverse == false) {
1110                 if ((txt_get_span(text->curl, text->sell) < 0) ||
1111                     (text->curl == text->sell && text->curc > text->selc))
1112                 {
1113                         txt_curs_swap(text);
1114                 }
1115         }
1116         else {
1117                 if ((txt_get_span(text->curl, text->sell) > 0) ||
1118                     (text->curl == text->sell && text->curc < text->selc))
1119                 {
1120                         txt_curs_swap(text);
1121                 }
1122         }
1123 }
1124
1125 bool txt_has_sel(Text *text)
1126 {
1127         return ((text->curl != text->sell) || (text->curc != text->selc));
1128 }
1129
1130 static void txt_delete_sel(Text *text, TextUndoBuf *utxt)
1131 {
1132         TextLine *tmpl;
1133         char *buf;
1134
1135         if (!text->curl) return;
1136         if (!text->sell) return;
1137
1138         if (!txt_has_sel(text)) return;
1139
1140         txt_order_cursors(text, false);
1141
1142         if (!undoing) {
1143                 buf = txt_sel_to_buf(text);
1144                 txt_undo_add_blockop(text, utxt, UNDO_DBLOCK, buf);
1145                 MEM_freeN(buf);
1146         }
1147
1148         buf = MEM_mallocN(text->curc + (text->sell->len - text->selc) + 1, "textline_string");
1149
1150         strncpy(buf, text->curl->line, text->curc);
1151         strcpy(buf + text->curc, text->sell->line + text->selc);
1152         buf[text->curc + (text->sell->len - text->selc)] = 0;
1153
1154         make_new_line(text->curl, buf);
1155
1156         tmpl = text->sell;
1157         while (tmpl != text->curl) {
1158                 tmpl = tmpl->prev;
1159                 if (!tmpl) break;
1160
1161                 txt_delete_line(text, tmpl->next);
1162         }
1163
1164         text->sell = text->curl;
1165         text->selc = text->curc;
1166 }
1167
1168 void txt_sel_all(Text *text)
1169 {
1170         text->curl = text->lines.first;
1171         text->curc = 0;
1172
1173         text->sell = text->lines.last;
1174         text->selc = text->sell->len;
1175 }
1176
1177 /**
1178  * Reverse of #txt_pop_sel
1179  * Clears the selection and ensures the cursor is located
1180  * at the selection (where the cursor is visually while editing).
1181  */
1182 void txt_sel_clear(Text *text)
1183 {
1184         if (text->sell) {
1185                 text->curl = text->sell;
1186                 text->curc = text->selc;
1187         }
1188 }
1189
1190 void txt_sel_line(Text *text)
1191 {
1192         if (!text->curl) return;
1193
1194         text->curc = 0;
1195         text->sell = text->curl;
1196         text->selc = text->sell->len;
1197 }
1198
1199 /***************************/
1200 /* Cut and paste functions */
1201 /***************************/
1202
1203 char *txt_to_buf(Text *text)
1204 {
1205         int length;
1206         TextLine *tmp, *linef, *linel;
1207         int charf, charl;
1208         char *buf;
1209
1210         if (!text->curl) return NULL;
1211         if (!text->sell) return NULL;
1212         if (!text->lines.first) return NULL;
1213
1214         linef = text->lines.first;
1215         charf = 0;
1216
1217         linel = text->lines.last;
1218         charl = linel->len;
1219
1220         if (linef == text->lines.last) {
1221                 length = charl - charf;
1222
1223                 buf = MEM_mallocN(length + 2, "text buffer");
1224
1225                 BLI_strncpy(buf, linef->line + charf, length + 1);
1226                 buf[length] = 0;
1227         }
1228         else {
1229                 length = linef->len - charf;
1230                 length += charl;
1231                 length += 2; /* For the 2 '\n' */
1232
1233                 tmp = linef->next;
1234                 while (tmp && tmp != linel) {
1235                         length += tmp->len + 1;
1236                         tmp = tmp->next;
1237                 }
1238
1239                 buf = MEM_mallocN(length + 1, "cut buffer");
1240
1241                 strncpy(buf, linef->line + charf, linef->len - charf);
1242                 length = linef->len - charf;
1243
1244                 buf[length++] = '\n';
1245
1246                 tmp = linef->next;
1247                 while (tmp && tmp != linel) {
1248                         strncpy(buf + length, tmp->line, tmp->len);
1249                         length += tmp->len;
1250
1251                         buf[length++] = '\n';
1252
1253                         tmp = tmp->next;
1254                 }
1255                 strncpy(buf + length, linel->line, charl);
1256                 length += charl;
1257
1258                 /* python compiler wants an empty end line */
1259                 buf[length++] = '\n';
1260                 buf[length] = 0;
1261         }
1262
1263         return buf;
1264 }
1265
1266 int txt_find_string(Text *text, const char *findstr, int wrap, int match_case)
1267 {
1268         TextLine *tl, *startl;
1269         const char *s = NULL;
1270
1271         if (!text->curl || !text->sell) return 0;
1272
1273         txt_order_cursors(text, false);
1274
1275         tl = startl = text->sell;
1276
1277         if (match_case) s = strstr(&tl->line[text->selc], findstr);
1278         else s = BLI_strcasestr(&tl->line[text->selc], findstr);
1279         while (!s) {
1280                 tl = tl->next;
1281                 if (!tl) {
1282                         if (wrap)
1283                                 tl = text->lines.first;
1284                         else
1285                                 break;
1286                 }
1287
1288                 if (match_case) s = strstr(tl->line, findstr);
1289                 else s = BLI_strcasestr(tl->line, findstr);
1290                 if (tl == startl)
1291                         break;
1292         }
1293
1294         if (s) {
1295                 int newl = txt_get_span(text->lines.first, tl);
1296                 int newc = (int)(s - tl->line);
1297                 txt_move_to(text, newl, newc, 0);
1298                 txt_move_to(text, newl, newc + strlen(findstr), 1);
1299                 return 1;
1300         }
1301         else
1302                 return 0;
1303 }
1304
1305 char *txt_sel_to_buf(Text *text)
1306 {
1307         char *buf;
1308         int length = 0;
1309         TextLine *tmp, *linef, *linel;
1310         int charf, charl;
1311
1312         if (!text->curl) return NULL;
1313         if (!text->sell) return NULL;
1314
1315         if (text->curl == text->sell) {
1316                 linef = linel = text->curl;
1317
1318                 if (text->curc < text->selc) {
1319                         charf = text->curc;
1320                         charl = text->selc;
1321                 }
1322                 else {
1323                         charf = text->selc;
1324                         charl = text->curc;
1325                 }
1326         }
1327         else if (txt_get_span(text->curl, text->sell) < 0) {
1328                 linef = text->sell;
1329                 linel = text->curl;
1330
1331                 charf = text->selc;
1332                 charl = text->curc;
1333         }
1334         else {
1335                 linef = text->curl;
1336                 linel = text->sell;
1337
1338                 charf = text->curc;
1339                 charl = text->selc;
1340         }
1341
1342         if (linef == linel) {
1343                 length = charl - charf;
1344
1345                 buf = MEM_mallocN(length + 1, "sel buffer");
1346
1347                 BLI_strncpy(buf, linef->line + charf, length + 1);
1348         }
1349         else {
1350                 length += linef->len - charf;
1351                 length += charl;
1352                 length++; /* For the '\n' */
1353
1354                 tmp = linef->next;
1355                 while (tmp && tmp != linel) {
1356                         length += tmp->len + 1;
1357                         tmp = tmp->next;
1358                 }
1359
1360                 buf = MEM_mallocN(length + 1, "sel buffer");
1361
1362                 strncpy(buf, linef->line + charf, linef->len - charf);
1363                 length = linef->len - charf;
1364
1365                 buf[length++] = '\n';
1366
1367                 tmp = linef->next;
1368                 while (tmp && tmp != linel) {
1369                         strncpy(buf + length, tmp->line, tmp->len);
1370                         length += tmp->len;
1371
1372                         buf[length++] = '\n';
1373
1374                         tmp = tmp->next;
1375                 }
1376                 strncpy(buf + length, linel->line, charl);
1377                 length += charl;
1378
1379                 buf[length] = 0;
1380         }
1381
1382         return buf;
1383 }
1384
1385 void txt_insert_buf(Text *text, TextUndoBuf *utxt, const char *in_buffer)
1386 {
1387         const bool undoing_orig = undoing;
1388         int l = 0, len;
1389         size_t i = 0, j;
1390         TextLine *add;
1391         char *buffer;
1392
1393         if (!in_buffer) return;
1394
1395         txt_delete_sel(text, utxt);
1396
1397         len = strlen(in_buffer);
1398         buffer = BLI_strdupn(in_buffer, len);
1399         len += txt_extended_ascii_as_utf8(&buffer);
1400
1401         if (!undoing) {
1402                 txt_undo_add_blockop(text, utxt, UNDO_IBLOCK, buffer);
1403         }
1404         undoing = true;
1405
1406         /* Read the first line (or as close as possible */
1407         while (buffer[i] && buffer[i] != '\n') {
1408                 txt_add_raw_char(text, utxt, BLI_str_utf8_as_unicode_step(buffer, &i));
1409         }
1410
1411         if (buffer[i] == '\n') {
1412                 txt_split_curline(text, utxt);
1413                 i++;
1414
1415                 while (i < len) {
1416                         l = 0;
1417
1418                         while (buffer[i] && buffer[i] != '\n') {
1419                                 i++;
1420                                 l++;
1421                         }
1422
1423                         if (buffer[i] == '\n') {
1424                                 add = txt_new_linen(buffer + (i - l), l);
1425                                 BLI_insertlinkbefore(&text->lines, text->curl, add);
1426                                 i++;
1427                         }
1428                         else {
1429                                 for (j = i - l; j < i && j < len; ) {
1430                                         txt_add_raw_char(text, utxt, BLI_str_utf8_as_unicode_step(buffer, &j));
1431                                 }
1432                                 break;
1433                         }
1434                 }
1435         }
1436
1437         MEM_freeN(buffer);
1438         undoing = undoing_orig;
1439 }
1440
1441 /******************/
1442 /* Undo functions */
1443 /******************/
1444
1445 static bool max_undo_test(TextUndoBuf *utxt, int x)
1446 {
1447         /* Normally over-allocating is preferred,
1448          * however in this case the buffer is small enough and re-allocation
1449          * fast enough for each undo step that it's not a problem to allocate each time.
1450          * This also saves on some memory when we have many text buffers
1451          * that would have an empty undo memory allocated.
1452          */
1453
1454         /* Add one for the null terminator. */
1455         utxt->len = utxt->pos + x + 1;
1456         if (utxt->len > TXT_MAX_UNDO) {
1457                 /* XXX error("Undo limit reached, buffer cleared\n"); */
1458                 MEM_freeN(utxt->buf);
1459                 return false;
1460         }
1461         else {
1462                 /* Small reallocations on each undo step is fine. */
1463                 utxt->buf = MEM_recallocN(utxt->buf, utxt->len);
1464         }
1465         return true;
1466 }
1467
1468 static void txt_undo_end(Text *UNUSED(text), TextUndoBuf *utxt)
1469 {
1470         int undo_pos_end = utxt->pos + 1;
1471         BLI_assert(undo_pos_end + 1 == utxt->len);
1472         utxt->buf[undo_pos_end] = '\0';
1473 }
1474
1475 static void txt_undo_store_uint16(char *undo_buf, int *undo_pos, unsigned short value)
1476 {
1477         undo_buf[*undo_pos] = (value) & 0xff;
1478         (*undo_pos)++;
1479         undo_buf[*undo_pos] = (value >> 8) & 0xff;
1480         (*undo_pos)++;
1481 }
1482
1483 static void txt_undo_store_uint32(char *undo_buf, int *undo_pos, unsigned int value)
1484 {
1485         undo_buf[*undo_pos] = (value) & 0xff;
1486         (*undo_pos)++;
1487         undo_buf[*undo_pos] = (value >> 8) & 0xff;
1488         (*undo_pos)++;
1489         undo_buf[*undo_pos] = (value >> 16) & 0xff;
1490         (*undo_pos)++;
1491         undo_buf[*undo_pos] = (value >> 24) & 0xff;
1492         (*undo_pos)++;
1493 }
1494
1495 /* store the cur cursor to the undo buffer (6 bytes)*/
1496 static void txt_undo_store_cur(Text *text, TextUndoBuf *utxt)
1497 {
1498         txt_undo_store_uint16(utxt->buf, &utxt->pos, text->curc);
1499         txt_undo_store_uint32(utxt->buf, &utxt->pos, txt_get_span(text->lines.first, text->curl));
1500 }
1501
1502 /* store the sel cursor to the undo buffer (6 bytes) */
1503 static void txt_undo_store_sel(Text *text, TextUndoBuf *utxt)
1504 {
1505         txt_undo_store_uint16(utxt->buf, &utxt->pos, text->selc);
1506         txt_undo_store_uint32(utxt->buf, &utxt->pos, txt_get_span(text->lines.first, text->sell));
1507 }
1508
1509 /* store both cursors to the undo buffer (12 bytes) */
1510 static void txt_undo_store_cursors(Text *text, TextUndoBuf *utxt)
1511 {
1512         txt_undo_store_cur(text, utxt);
1513         txt_undo_store_sel(text, utxt);
1514 }
1515
1516 /* store an operator along with a block of data */
1517 static void txt_undo_add_blockop(Text *text, TextUndoBuf *utxt, int op, const char *buf)
1518 {
1519         unsigned int length = strlen(buf);
1520
1521         if (!max_undo_test(utxt, 2 + 12 + 4 + length + 4 + 1)) {
1522                 return;
1523         }
1524         /* 2 bytes */
1525         utxt->pos++;
1526         utxt->buf[utxt->pos] = op;
1527         utxt->pos++;
1528         /* 12 bytes */
1529         txt_undo_store_cursors(text, utxt);
1530         /* 4 bytes */
1531         txt_undo_store_uint32(utxt->buf, &utxt->pos, length);
1532         /* 'length' bytes */
1533         memcpy(utxt->buf + utxt->pos, buf, length);
1534         utxt->pos += length;
1535         /* 4 bytes */
1536         txt_undo_store_uint32(utxt->buf, &utxt->pos, length);
1537         /* 1 byte */
1538         utxt->buf[utxt->pos] = op;
1539
1540         txt_undo_end(text, utxt);
1541 }
1542
1543 /* store a regular operator */
1544 void txt_undo_add_op(Text *text, TextUndoBuf *utxt, int op)
1545 {
1546         if (!max_undo_test(utxt, 2 + 12 + 1)) {
1547                 return;
1548         }
1549
1550         /* 2 bytes */
1551         utxt->pos++;
1552         utxt->buf[utxt->pos] = op;
1553         utxt->pos++;
1554         /* 12 bytes */
1555         txt_undo_store_cursors(text, utxt);
1556         /* 1 byte */
1557         utxt->buf[utxt->pos] = op;
1558
1559         txt_undo_end(text, utxt);
1560 }
1561
1562 /* store an operator for a single character */
1563 static void txt_undo_add_charop(Text *text, TextUndoBuf *utxt, int op_start, unsigned int c)
1564 {
1565         char utf8[BLI_UTF8_MAX];
1566         size_t i, utf8_size = BLI_str_utf8_from_unicode(c, utf8);
1567
1568         if (utf8_size < 4 && 0) {
1569                 if (!max_undo_test(utxt, 2 + 6 + utf8_size + 1)) {
1570                         return;
1571                 }
1572                 /* 2 bytes */
1573                 utxt->pos++;
1574                 utxt->buf[utxt->pos] = op_start + utf8_size - 1;
1575                 utxt->pos++;
1576                 /* 6 bytes */
1577                 txt_undo_store_cur(text, utxt);
1578                 /* 'utf8_size' bytes */
1579                 for (i = 0; i < utf8_size; i++) {
1580                         utxt->buf[utxt->pos] = utf8[i];
1581                         utxt->pos++;
1582                 }
1583                 /* 1 byte */
1584                 utxt->buf[utxt->pos] = op_start + utf8_size - 1;
1585         }
1586         else {
1587                 if (!max_undo_test(utxt, 2 + 6 + 4 + 1)) {
1588                         return;
1589                 }
1590                 /* 2 bytes */
1591                 utxt->pos++;
1592                 utxt->buf[utxt->pos] = op_start + 3;
1593                 utxt->pos++;
1594                 /* 6 bytes */
1595                 txt_undo_store_cur(text, utxt);
1596                 /* 4 bytes */
1597                 txt_undo_store_uint32(utxt->buf, &utxt->pos, c);
1598                 /* 1 byte */
1599                 utxt->buf[utxt->pos] = op_start + 3;
1600         }
1601
1602         txt_undo_end(text, utxt);
1603 }
1604
1605 /* extends Link */
1606 struct LinkInt {
1607         struct LinkInt *next, *prev;
1608         int value;
1609 };
1610
1611 /**
1612  * UnindentLines points to a #ListBase composed of #LinkInt elements, listing the numbers
1613  * of the lines that should not be indented back.
1614  */
1615 static void txt_undo_add_unprefix_op(
1616         Text *text, TextUndoBuf *utxt, char undo_op,
1617         const ListBase *line_index_mask, const int line_index_mask_len)
1618 {
1619         struct LinkInt *idata;
1620
1621         BLI_assert(BLI_listbase_count(line_index_mask) == line_index_mask_len);
1622
1623         /* OP byte + UInt32 count + counted UInt32 line numbers + UInt32 count + 12-bytes selection + OP byte */
1624         if (!max_undo_test(utxt, 2 + 4 + (line_index_mask_len * 4) + 4 + 12 + 1)) {
1625                 return;
1626         }
1627
1628         /* 2 bytes */
1629         utxt->pos++;
1630         utxt->buf[utxt->pos] = undo_op;
1631         utxt->pos++;
1632         /* Adding number of line numbers to read
1633          * 4 bytes */
1634         txt_undo_store_uint32(utxt->buf, &utxt->pos, line_index_mask_len);
1635
1636         /* Adding linenumbers of lines that shall not be indented if undoing.
1637          * 'line_index_mask_len * 4' bytes */
1638         for (idata = line_index_mask->first; idata; idata = idata->next) {
1639                 txt_undo_store_uint32(utxt->buf, &utxt->pos, idata->value);
1640         }
1641
1642         /* Adding number of line numbers to read again.
1643          * 4 bytes */
1644         txt_undo_store_uint32(utxt->buf, &utxt->pos, line_index_mask_len);
1645         /* Adding current selection.
1646          * 12 bytes */
1647         txt_undo_store_cursors(text, utxt);
1648         /* Closing with OP (same as above).
1649          * 1 byte */
1650         utxt->buf[utxt->pos] = undo_op;
1651         /* Marking as last undo operation */
1652         txt_undo_end(text, utxt);
1653 }
1654
1655 static unsigned short txt_undo_read_uint16(const char *undo_buf, int *undo_pos)
1656 {
1657         unsigned short val;
1658         val = undo_buf[*undo_pos]; (*undo_pos)--;
1659         val = (val << 8) + undo_buf[*undo_pos]; (*undo_pos)--;
1660         return val;
1661 }
1662
1663 static unsigned int txt_undo_read_uint32(const char *undo_buf, int *undo_pos)
1664 {
1665         unsigned int val;
1666         val = undo_buf[*undo_pos]; (*undo_pos)--;
1667         val = (val << 8) + undo_buf[*undo_pos]; (*undo_pos)--;
1668         val = (val << 8) + undo_buf[*undo_pos]; (*undo_pos)--;
1669         val = (val << 8) + undo_buf[*undo_pos]; (*undo_pos)--;
1670         return val;
1671 }
1672
1673 /* read the cur cursor from the undo buffer */
1674 static void txt_undo_read_cur(const char *undo_buf, int *undo_pos, unsigned int *curln, unsigned short *curc)
1675 {
1676         *curln = txt_undo_read_uint32(undo_buf, undo_pos);
1677         *curc  = txt_undo_read_uint16(undo_buf, undo_pos);
1678 }
1679
1680 /* read the sel cursor from the undo buffer */
1681 static void txt_undo_read_sel(const char *undo_buf, int *undo_pos, unsigned int *selln, unsigned short *selc)
1682 {
1683         *selln = txt_undo_read_uint32(undo_buf, undo_pos);
1684         *selc  = txt_undo_read_uint16(undo_buf, undo_pos);
1685 }
1686
1687 /* read both cursors from the undo buffer */
1688 static void txt_undo_read_cursors(const char *undo_buf, int *undo_pos,
1689                                   unsigned int *curln, unsigned short *curc,
1690                                   unsigned int *selln, unsigned short *selc)
1691 {
1692         txt_undo_read_sel(undo_buf, undo_pos, selln, selc);
1693         txt_undo_read_cur(undo_buf, undo_pos, curln, curc);
1694 }
1695
1696 static unsigned int txt_undo_read_unicode(const char *undo_buf, int *undo_pos, short bytes)
1697 {
1698         unsigned int unicode;
1699         char utf8[BLI_UTF8_MAX + 1];
1700
1701         switch (bytes) {
1702                 case 1: /* ascii */
1703                         unicode = undo_buf[*undo_pos]; (*undo_pos)--;
1704                         break;
1705                 case 2: /* 2-byte symbol */
1706                         utf8[2] = '\0';
1707                         utf8[1] = undo_buf[*undo_pos]; (*undo_pos)--;
1708                         utf8[0] = undo_buf[*undo_pos]; (*undo_pos)--;
1709                         unicode = BLI_str_utf8_as_unicode(utf8);
1710                         break;
1711                 case 3: /* 3-byte symbol */
1712                         utf8[3] = '\0';
1713                         utf8[2] = undo_buf[*undo_pos]; (*undo_pos)--;
1714                         utf8[1] = undo_buf[*undo_pos]; (*undo_pos)--;
1715                         utf8[0] = undo_buf[*undo_pos]; (*undo_pos)--;
1716                         unicode = BLI_str_utf8_as_unicode(utf8);
1717                         break;
1718                 case 4: /* 32-bit unicode symbol */
1719                         unicode = txt_undo_read_uint32(undo_buf, undo_pos);
1720                         break;
1721                 default:
1722                         /* should never happen */
1723                         BLI_assert(0);
1724                         unicode = 0;
1725                         break;
1726         }
1727
1728         return unicode;
1729 }
1730
1731 static unsigned short txt_redo_read_uint16(const char *undo_buf, int *undo_pos)
1732 {
1733         unsigned short val;
1734         val = undo_buf[*undo_pos]; (*undo_pos)++;
1735         val = val + (undo_buf[*undo_pos] << 8); (*undo_pos)++;
1736         return val;
1737 }
1738
1739 static unsigned int txt_redo_read_uint32(const char *undo_buf, int *undo_pos)
1740 {
1741         unsigned int val;
1742         val = undo_buf[*undo_pos]; (*undo_pos)++;
1743         val = val + (undo_buf[*undo_pos] << 8); (*undo_pos)++;
1744         val = val + (undo_buf[*undo_pos] << 16); (*undo_pos)++;
1745         val = val + (undo_buf[*undo_pos] << 24); (*undo_pos)++;
1746         return val;
1747 }
1748
1749 /* redo read cur cursor from the undo buffer */
1750 static void txt_redo_read_cur(const char *undo_buf, int *undo_pos, unsigned int *curln, unsigned short *curc)
1751 {
1752         *curc  = txt_redo_read_uint16(undo_buf, undo_pos);
1753         *curln = txt_redo_read_uint32(undo_buf, undo_pos);
1754 }
1755
1756 /* redo read sel cursor from the undo buffer */
1757 static void txt_redo_read_sel(const char *undo_buf, int *undo_pos, unsigned int *selln, unsigned short *selc)
1758 {
1759         *selc  = txt_redo_read_uint16(undo_buf, undo_pos);
1760         *selln = txt_redo_read_uint32(undo_buf, undo_pos);
1761 }
1762
1763 /* redo read both cursors from the undo buffer */
1764 static void txt_redo_read_cursors(const char *undo_buf, int *undo_pos,
1765                                   unsigned int *curln, unsigned short *curc,
1766                                   unsigned int *selln, unsigned short *selc)
1767 {
1768         txt_redo_read_cur(undo_buf, undo_pos, curln, curc);
1769         txt_redo_read_sel(undo_buf, undo_pos, selln, selc);
1770 }
1771
1772 static unsigned int txt_redo_read_unicode(const char *undo_buf, int *undo_pos, short bytes)
1773 {
1774         unsigned int unicode;
1775         char utf8[BLI_UTF8_MAX + 1];
1776
1777         switch (bytes) {
1778                 case 1: /* ascii */
1779                         unicode = undo_buf[*undo_pos]; (*undo_pos)++;
1780                         break;
1781                 case 2: /* 2-byte symbol */
1782                         utf8[0] = undo_buf[*undo_pos]; (*undo_pos)++;
1783                         utf8[1] = undo_buf[*undo_pos]; (*undo_pos)++;
1784                         utf8[2] = '\0';
1785                         unicode = BLI_str_utf8_as_unicode(utf8);
1786                         break;
1787                 case 3: /* 3-byte symbol */
1788                         utf8[0] = undo_buf[*undo_pos]; (*undo_pos)++;
1789                         utf8[1] = undo_buf[*undo_pos]; (*undo_pos)++;
1790                         utf8[2] = undo_buf[*undo_pos]; (*undo_pos)++;
1791                         utf8[3] = '\0';
1792                         unicode = BLI_str_utf8_as_unicode(utf8);
1793                         break;
1794                 case 4: /* 32-bit unicode symbol */
1795                         unicode = txt_redo_read_uint32(undo_buf, undo_pos);
1796                         break;
1797                 default:
1798                         /* should never happen */
1799                         BLI_assert(0);
1800                         unicode = 0;
1801                         break;
1802         }
1803
1804         return unicode;
1805 }
1806
1807 void txt_do_undo(Text *text, TextUndoBuf *utxt)
1808 {
1809         int op = utxt->buf[utxt->pos];
1810         int prev_flags;
1811         unsigned int linep;
1812         unsigned int uni_char;
1813         unsigned int curln, selln;
1814         unsigned short curc, selc;
1815         unsigned short charp;
1816         char *buf;
1817
1818         if (utxt->pos < 0) {
1819                 return;
1820         }
1821
1822         utxt->pos--;
1823
1824         undoing = 1;
1825
1826         switch (op) {
1827                 case UNDO_INSERT_1:
1828                 case UNDO_INSERT_2:
1829                 case UNDO_INSERT_3:
1830                 case UNDO_INSERT_4:
1831                         utxt->pos -= op - UNDO_INSERT_1 + 1;
1832
1833                         /* get and restore the cursors */
1834                         txt_undo_read_cur(utxt->buf, &utxt->pos, &curln, &curc);
1835                         txt_move_to(text, curln, curc, 0);
1836                         txt_move_to(text, curln, curc, 1);
1837
1838                         txt_delete_char(text, utxt);
1839
1840                         utxt->pos--;
1841                         break;
1842
1843                 case UNDO_BS_1:
1844                 case UNDO_BS_2:
1845                 case UNDO_BS_3:
1846                 case UNDO_BS_4:
1847                         charp = op - UNDO_BS_1 + 1;
1848                         uni_char = txt_undo_read_unicode(utxt->buf, &utxt->pos, charp);
1849
1850                         /* get and restore the cursors */
1851                         txt_undo_read_cur(utxt->buf, &utxt->pos, &curln, &curc);
1852                         txt_move_to(text, curln, curc, 0);
1853                         txt_move_to(text, curln, curc, 1);
1854
1855                         txt_add_char(text, utxt, uni_char);
1856
1857                         utxt->pos--;
1858                         break;
1859
1860                 case UNDO_DEL_1:
1861                 case UNDO_DEL_2:
1862                 case UNDO_DEL_3:
1863                 case UNDO_DEL_4:
1864                         charp = op - UNDO_DEL_1 + 1;
1865                         uni_char = txt_undo_read_unicode(utxt->buf, &utxt->pos, charp);
1866
1867                         /* get and restore the cursors */
1868                         txt_undo_read_cur(utxt->buf, &utxt->pos, &curln, &curc);
1869                         txt_move_to(text, curln, curc, 0);
1870                         txt_move_to(text, curln, curc, 1);
1871
1872                         txt_add_char(text, utxt, uni_char);
1873
1874                         txt_move_left(text, 0);
1875
1876                         utxt->pos--;
1877                         break;
1878
1879                 case UNDO_DBLOCK:
1880                 {
1881                         int i;
1882                         /* length of the string in the buffer */
1883                         linep = txt_undo_read_uint32(utxt->buf, &utxt->pos);
1884
1885                         buf = MEM_mallocN(linep + 1, "dblock buffer");
1886                         for (i = 0; i < linep; i++) {
1887                                 buf[(linep - 1) - i] = utxt->buf[utxt->pos];
1888                                 utxt->pos--;
1889                         }
1890                         buf[i] = 0;
1891
1892                         /* skip over the length that was stored again */
1893                         utxt->pos -= 4;
1894
1895                         /* Get the cursor positions */
1896                         txt_undo_read_cursors(utxt->buf, &utxt->pos, &curln, &curc, &selln, &selc);
1897
1898                         /* move cur to location that needs buff inserted */
1899                         txt_move_to(text, curln, curc, 0);
1900
1901                         txt_insert_buf(text, utxt, buf);
1902                         MEM_freeN(buf);
1903
1904                         /* restore the cursors */
1905                         txt_move_to(text, curln, curc, 0);
1906                         txt_move_to(text, selln, selc, 1);
1907
1908                         utxt->pos--;
1909
1910                         break;
1911                 }
1912                 case UNDO_IBLOCK:
1913                 {
1914                         int i;
1915                         /* length of the string in the buffer */
1916                         linep = txt_undo_read_uint32(utxt->buf, &utxt->pos);
1917
1918                         /* txt_backspace_char removes utf8-characters, not bytes */
1919                         buf = MEM_mallocN(linep + 1, "iblock buffer");
1920                         for (i = 0; i < linep; i++) {
1921                                 buf[(linep - 1) - i] = utxt->buf[utxt->pos];
1922                                 utxt->pos--;
1923                         }
1924                         buf[i] = 0;
1925                         linep = BLI_strlen_utf8(buf);
1926                         MEM_freeN(buf);
1927
1928                         /* skip over the length that was stored again */
1929                         utxt->pos -= 4;
1930
1931                         /* get and restore the cursors */
1932                         txt_undo_read_cursors(utxt->buf, &utxt->pos, &curln, &curc, &selln, &selc);
1933
1934                         txt_move_to(text, curln, curc, 0);
1935                         txt_move_to(text, selln, selc, 1);
1936
1937                         if ((curln == selln) && (curc == selc)) {
1938                                 /* disable tabs to spaces since moving right may involve skipping multiple spaces */
1939                                 prev_flags = text->flags;
1940                                 text->flags &= ~TXT_TABSTOSPACES;
1941
1942                                 for (i = 0; i < linep; i++)
1943                                         txt_move_right(text, 1);
1944
1945                                 text->flags = prev_flags;
1946                         }
1947
1948                         txt_delete_selected(text, utxt);
1949
1950                         utxt->pos--;
1951                         break;
1952                 }
1953                 case UNDO_INDENT:
1954                 case UNDO_COMMENT:
1955                 case UNDO_DUPLICATE:
1956                 case UNDO_MOVE_LINES_UP:
1957                 case UNDO_MOVE_LINES_DOWN:
1958                         /* get and restore the cursors */
1959                         txt_undo_read_cursors(utxt->buf, &utxt->pos, &curln, &curc, &selln, &selc);
1960                         txt_move_to(text, curln, curc, 0);
1961                         txt_move_to(text, selln, selc, 1);
1962
1963                         if (op == UNDO_INDENT) {
1964                                 txt_unindent(text, utxt);
1965                         }
1966                         else if (op == UNDO_COMMENT) {
1967                                 txt_uncomment(text, utxt);
1968                         }
1969                         else if (op == UNDO_DUPLICATE) {
1970                                 txt_delete_line(text, text->curl->next);
1971                         }
1972                         else if (op == UNDO_MOVE_LINES_UP) {
1973                                 txt_move_lines(text, utxt, TXT_MOVE_LINE_DOWN);
1974                         }
1975                         else if (op == UNDO_MOVE_LINES_DOWN) {
1976                                 txt_move_lines(text, utxt, TXT_MOVE_LINE_UP);
1977                         }
1978
1979                         utxt->pos--;
1980                         break;
1981                 case UNDO_UNINDENT:
1982                 case UNDO_UNCOMMENT:
1983                 {
1984                         void (*txt_prefix_fn)(Text *, TextUndoBuf *);
1985                         void (*txt_unprefix_fn)(Text *, TextUndoBuf *);
1986                         int count;
1987                         int i;
1988                         /* Get and restore the cursors */
1989                         txt_undo_read_cursors(utxt->buf, &utxt->pos, &curln, &curc, &selln, &selc);
1990                         txt_move_to(text, curln, curc, 0);
1991                         txt_move_to(text, selln, selc, 1);
1992
1993                         /* Un-unindent */
1994                         if (op == UNDO_UNINDENT) {
1995                                 txt_prefix_fn = txt_indent;
1996                                 txt_unprefix_fn = txt_unindent;
1997                         }
1998                         else {
1999                                 txt_prefix_fn = txt_comment;
2000                                 txt_unprefix_fn = txt_uncomment;
2001                         }
2002
2003                         txt_prefix_fn(text, utxt);
2004
2005                         /* Get the count */
2006                         count = txt_undo_read_uint32(utxt->buf, &utxt->pos);
2007                         /* Iterate! */
2008                         txt_pop_sel(text);
2009
2010                         for (i = 0; i < count; i++) {
2011                                 txt_move_to(text, txt_undo_read_uint32(utxt->buf, &utxt->pos), 0, 0);
2012                                 /* Un-un-unindent/comment */
2013                                 txt_unprefix_fn(text, utxt);
2014                         }
2015                         /* Restore selection */
2016                         txt_move_to(text, curln, curc, 0);
2017                         txt_move_to(text, selln, selc, 1);
2018                         /* Jumo over count */
2019                         txt_undo_read_uint32(utxt->buf, &utxt->pos);
2020                         /* Jump over closing OP byte */
2021                         utxt->pos--;
2022                         break;
2023                 }
2024                 default:
2025                         //XXX error("Undo buffer error - resetting");
2026                         utxt->pos = -1;
2027
2028                         break;
2029         }
2030
2031         undoing = 0;
2032 }
2033
2034 void txt_do_redo(Text *text, TextUndoBuf *utxt)
2035 {
2036         char op;
2037         char *buf;
2038         unsigned int linep;
2039         unsigned short charp;
2040         unsigned int uni_uchar;
2041         unsigned int curln, selln;
2042         unsigned short curc, selc;
2043
2044         utxt->pos++;
2045         op = utxt->buf[utxt->pos];
2046
2047         if (!op) {
2048                 utxt->pos--;
2049                 return;
2050         }
2051
2052         undoing = 1;
2053
2054         switch (op) {
2055                 case UNDO_INSERT_1:
2056                 case UNDO_INSERT_2:
2057                 case UNDO_INSERT_3:
2058                 case UNDO_INSERT_4:
2059                         utxt->pos++;
2060
2061                         /* get and restore the cursors */
2062                         txt_redo_read_cur(utxt->buf, &utxt->pos, &curln, &curc);
2063                         txt_move_to(text, curln, curc, 0);
2064                         txt_move_to(text, curln, curc, 1);
2065
2066                         charp = op - UNDO_INSERT_1 + 1;
2067                         uni_uchar = txt_redo_read_unicode(utxt->buf, &utxt->pos, charp);
2068
2069                         txt_add_char(text, utxt, uni_uchar);
2070                         break;
2071
2072                 case UNDO_BS_1:
2073                 case UNDO_BS_2:
2074                 case UNDO_BS_3:
2075                 case UNDO_BS_4:
2076                         utxt->pos++;
2077
2078                         /* get and restore the cursors */
2079                         txt_redo_read_cur(utxt->buf, &utxt->pos, &curln, &curc);
2080                         txt_move_to(text, curln, curc, 0);
2081                         txt_move_to(text, curln, curc, 1);
2082
2083                         utxt->pos += op - UNDO_BS_1 + 1;
2084
2085                         /* move right so we backspace the correct char */
2086                         txt_move_right(text, 0);
2087                         txt_backspace_char(text, utxt);
2088
2089                         break;
2090
2091                 case UNDO_DEL_1:
2092                 case UNDO_DEL_2:
2093                 case UNDO_DEL_3:
2094                 case UNDO_DEL_4:
2095                         utxt->pos++;
2096
2097                         /* get and restore the cursors */
2098                         txt_redo_read_cur(utxt->buf, &utxt->pos, &curln, &curc);
2099                         txt_move_to(text, curln, curc, 0);
2100                         txt_move_to(text, curln, curc, 1);
2101
2102                         utxt->pos += op - UNDO_DEL_1 + 1;
2103
2104                         txt_delete_char(text, utxt);
2105
2106                         break;
2107
2108                 case UNDO_DBLOCK:
2109                         utxt->pos++;
2110
2111                         /* get and restore the cursors */
2112                         txt_redo_read_cursors(utxt->buf, &utxt->pos, &curln, &curc, &selln, &selc);
2113                         txt_move_to(text, curln, curc, 0);
2114                         txt_move_to(text, selln, selc, 1);
2115
2116                         /* length of the block */
2117                         linep = txt_redo_read_uint32(utxt->buf, &utxt->pos);
2118
2119                         utxt->pos += linep;
2120
2121                         /* skip over the length that was stored again */
2122                         utxt->pos += 4;
2123
2124                         txt_delete_sel(text, utxt);
2125
2126                         break;
2127
2128                 case UNDO_IBLOCK:
2129                         utxt->pos++;
2130
2131                         /* get and restore the cursors */
2132                         txt_redo_read_cursors(utxt->buf, &utxt->pos, &curln, &curc, &selln, &selc);
2133                         txt_move_to(text, curln, curc, 0);
2134                         txt_move_to(text, curln, curc, 1);
2135
2136                         /* length of the block */
2137                         linep = txt_redo_read_uint32(utxt->buf, &utxt->pos);
2138
2139                         buf = MEM_mallocN(linep + 1, "iblock buffer");
2140                         memcpy(buf, &utxt->buf[utxt->pos], linep);
2141                         utxt->pos += linep;
2142                         buf[linep] = 0;
2143
2144                         txt_insert_buf(text, utxt, buf);
2145                         MEM_freeN(buf);
2146
2147                         /* skip over the length that was stored again */
2148                         utxt->pos += 4;
2149
2150                         break;
2151
2152                 case UNDO_INDENT:
2153                 case UNDO_COMMENT:
2154                 case UNDO_UNCOMMENT:
2155                 case UNDO_DUPLICATE:
2156                 case UNDO_MOVE_LINES_UP:
2157                 case UNDO_MOVE_LINES_DOWN:
2158                         utxt->pos++;
2159
2160                         /* get and restore the cursors */
2161                         txt_redo_read_cursors(utxt->buf, &utxt->pos, &curln, &curc, &selln, &selc);
2162                         txt_move_to(text, curln, curc, 0);
2163                         txt_move_to(text, selln, selc, 1);
2164
2165                         if (op == UNDO_INDENT) {
2166                                 txt_indent(text, utxt);
2167                         }
2168                         else if (op == UNDO_COMMENT) {
2169                                 txt_comment(text, utxt);
2170                         }
2171                         else if (op == UNDO_UNCOMMENT) {
2172                                 txt_uncomment(text, utxt);
2173                         }
2174                         else if (op == UNDO_DUPLICATE) {
2175                                 txt_duplicate_line(text, utxt);
2176                         }
2177                         else if (op == UNDO_MOVE_LINES_UP) {
2178                                 /* offset the cursor by + 1 */
2179                                 txt_move_to(text, curln + 1, curc, 0);
2180                                 txt_move_to(text, selln + 1, selc, 1);
2181
2182                                 txt_move_lines(text, utxt, TXT_MOVE_LINE_UP);
2183                         }
2184                         else if (op == UNDO_MOVE_LINES_DOWN) {
2185                                 /* offset the cursor by - 1 */
2186                                 txt_move_to(text, curln - 1, curc, 0);
2187                                 txt_move_to(text, selln - 1, selc, 1);
2188
2189                                 txt_move_lines(text, utxt, TXT_MOVE_LINE_DOWN);
2190                         }
2191
2192                         /* re-restore the cursors since they got moved when redoing */
2193                         txt_move_to(text, curln, curc, 0);
2194                         txt_move_to(text, selln, selc, 1);
2195
2196                         break;
2197                 case UNDO_UNINDENT:
2198                 {
2199                         int count;
2200                         int i;
2201
2202                         utxt->pos++;
2203                         /* Scan all the stuff described in txt_undo_add_unindent_op */
2204                         count = txt_redo_read_uint32(utxt->buf, &utxt->pos);
2205                         for (i = 0; i < count; i++) {
2206                                 txt_redo_read_uint32(utxt->buf, &utxt->pos);
2207                         }
2208                         /* Count again */
2209                         txt_redo_read_uint32(utxt->buf, &utxt->pos);
2210                         /* Get the selection and re-unindent */
2211                         txt_redo_read_cursors(utxt->buf, &utxt->pos, &curln, &curc, &selln, &selc);
2212                         txt_move_to(text, curln, curc, 0);
2213                         txt_move_to(text, selln, selc, 1);
2214                         txt_unindent(text, utxt);
2215                         break;
2216                 }
2217                 default:
2218                         //XXX error("Undo buffer error - resetting");
2219                         utxt->pos = -1;
2220
2221                         break;
2222         }
2223
2224         undoing = 0;
2225 }
2226
2227 /**************************/
2228 /* Line editing functions */
2229 /**************************/
2230
2231 void txt_split_curline(Text *text, TextUndoBuf *utxt)
2232 {
2233         TextLine *ins;
2234         char *left, *right;
2235
2236         if (!text->curl) return;
2237
2238         txt_delete_sel(text, utxt);
2239
2240         if (!undoing) txt_undo_add_charop(text, utxt, UNDO_INSERT_1, '\n');
2241
2242         /* Make the two half strings */
2243
2244         left = MEM_mallocN(text->curc + 1, "textline_string");
2245         if (text->curc) memcpy(left, text->curl->line, text->curc);
2246         left[text->curc] = 0;
2247
2248         right = MEM_mallocN(text->curl->len - text->curc + 1, "textline_string");
2249         memcpy(right, text->curl->line + text->curc, text->curl->len - text->curc + 1);
2250
2251         MEM_freeN(text->curl->line);
2252         if (text->curl->format) MEM_freeN(text->curl->format);
2253
2254         /* Make the new TextLine */
2255
2256         ins = MEM_mallocN(sizeof(TextLine), "textline");
2257         ins->line = left;
2258         ins->format = NULL;
2259         ins->len = text->curc;
2260
2261         text->curl->line = right;
2262         text->curl->format = NULL;
2263         text->curl->len = text->curl->len - text->curc;
2264
2265         BLI_insertlinkbefore(&text->lines, text->curl, ins);
2266
2267         text->curc = 0;
2268
2269         txt_make_dirty(text);
2270         txt_clean_text(text);
2271
2272         txt_pop_sel(text);
2273 }
2274
2275 static void txt_delete_line(Text *text, TextLine *line)
2276 {
2277         if (!text->curl) return;
2278
2279         BLI_remlink(&text->lines, line);
2280
2281         if (line->line) MEM_freeN(line->line);
2282         if (line->format) MEM_freeN(line->format);
2283
2284         MEM_freeN(line);
2285
2286         txt_make_dirty(text);
2287         txt_clean_text(text);
2288 }
2289
2290 static void txt_combine_lines(Text *text, TextLine *linea, TextLine *lineb)
2291 {
2292         char *tmp, *s;
2293
2294         if (!linea || !lineb) return;
2295
2296
2297         tmp = MEM_mallocN(linea->len + lineb->len + 1, "textline_string");
2298
2299         s = tmp;
2300         s += BLI_strcpy_rlen(s, linea->line);
2301         s += BLI_strcpy_rlen(s, lineb->line);
2302         (void)s;
2303
2304         make_new_line(linea, tmp);
2305
2306         txt_delete_line(text, lineb);
2307
2308         txt_make_dirty(text);
2309         txt_clean_text(text);
2310 }
2311
2312 void txt_duplicate_line(Text *text, TextUndoBuf *utxt)
2313 {
2314         TextLine *textline;
2315
2316         if (!text->curl) return;
2317
2318         if (text->curl == text->sell) {
2319                 textline = txt_new_line(text->curl->line);
2320                 BLI_insertlinkafter(&text->lines, text->curl, textline);
2321
2322                 txt_make_dirty(text);
2323                 txt_clean_text(text);
2324
2325                 if (!undoing) txt_undo_add_op(text, utxt, UNDO_DUPLICATE);
2326         }
2327 }
2328
2329 void txt_delete_char(Text *text, TextUndoBuf *utxt)
2330 {
2331         unsigned int c = '\n';
2332
2333         if (!text->curl) return;
2334
2335         if (txt_has_sel(text)) { /* deleting a selection */
2336                 txt_delete_sel(text, utxt);
2337                 txt_make_dirty(text);
2338                 return;
2339         }
2340         else if (text->curc == text->curl->len) { /* Appending two lines */
2341                 if (text->curl->next) {
2342                         txt_combine_lines(text, text->curl, text->curl->next);
2343                         txt_pop_sel(text);
2344                 }
2345                 else
2346                         return;
2347         }
2348         else { /* Just deleting a char */
2349                 size_t c_len = 0;
2350                 c = BLI_str_utf8_as_unicode_and_size(text->curl->line + text->curc, &c_len);
2351
2352                 memmove(text->curl->line + text->curc, text->curl->line + text->curc + c_len, text->curl->len - text->curc - c_len + 1);
2353
2354                 text->curl->len -= c_len;
2355
2356                 txt_pop_sel(text);
2357         }
2358
2359         txt_make_dirty(text);
2360         txt_clean_text(text);
2361
2362         if (!undoing) txt_undo_add_charop(text, utxt, UNDO_DEL_1, c);
2363 }
2364
2365 void txt_delete_word(Text *text, TextUndoBuf *utxt)
2366 {
2367         txt_jump_right(text, true, true);
2368         txt_delete_sel(text, utxt);
2369         txt_make_dirty(text);
2370 }
2371
2372 void txt_backspace_char(Text *text, TextUndoBuf *utxt)
2373 {
2374         unsigned int c = '\n';
2375
2376         if (!text->curl) return;
2377
2378         if (txt_has_sel(text)) { /* deleting a selection */
2379                 txt_delete_sel(text, utxt);
2380                 txt_make_dirty(text);
2381                 return;
2382         }
2383         else if (text->curc == 0) { /* Appending two lines */
2384                 if (!text->curl->prev) return;
2385
2386                 text->curl = text->curl->prev;
2387                 text->curc = text->curl->len;
2388
2389                 txt_combine_lines(text, text->curl, text->curl->next);
2390                 txt_pop_sel(text);
2391         }
2392         else { /* Just backspacing a char */
2393                 size_t c_len = 0;
2394                 const char *prev = BLI_str_prev_char_utf8(text->curl->line + text->curc);
2395                 c = BLI_str_utf8_as_unicode_and_size(prev, &c_len);
2396
2397                 /* source and destination overlap, don't use memcpy() */
2398                 memmove(text->curl->line + text->curc - c_len,
2399                         text->curl->line + text->curc,
2400                         text->curl->len  - text->curc + 1);
2401
2402                 text->curl->len -= c_len;
2403                 text->curc -= c_len;
2404
2405                 txt_pop_sel(text);
2406         }
2407
2408         txt_make_dirty(text);
2409         txt_clean_text(text);
2410
2411         if (!undoing) txt_undo_add_charop(text, utxt, UNDO_BS_1, c);
2412 }
2413
2414 void txt_backspace_word(Text *text, TextUndoBuf *utxt)
2415 {
2416         txt_jump_left(text, true, true);
2417         txt_delete_sel(text, utxt);
2418         txt_make_dirty(text);
2419 }
2420
2421 /* Max spaces to replace a tab with, currently hardcoded to TXT_TABSIZE = 4.
2422  * Used by txt_convert_tab_to_spaces, indent and unindent.
2423  * Remember to change this string according to max tab size */
2424 static char tab_to_spaces[] = "    ";
2425
2426 static void txt_convert_tab_to_spaces(Text *text, TextUndoBuf *utxt)
2427 {
2428         /* sb aims to pad adjust the tab-width needed so that the right number of spaces
2429          * is added so that the indention of the line is the right width (i.e. aligned
2430          * to multiples of TXT_TABSIZE)
2431          */
2432         const char *sb = &tab_to_spaces[text->curc % TXT_TABSIZE];
2433         txt_insert_buf(text, utxt, sb);
2434 }
2435
2436 static bool txt_add_char_intern(Text *text, TextUndoBuf *utxt, unsigned int add, bool replace_tabs)
2437 {
2438         char *tmp, ch[BLI_UTF8_MAX];
2439         size_t add_len;
2440
2441         if (!text->curl) return 0;
2442
2443         if (add == '\n') {
2444                 txt_split_curline(text, utxt);
2445                 return true;
2446         }
2447
2448         /* insert spaces rather than tabs */
2449         if (add == '\t' && replace_tabs) {
2450                 txt_convert_tab_to_spaces(text, utxt);
2451                 return true;
2452         }
2453
2454         txt_delete_sel(text, utxt);
2455
2456         if (!undoing) txt_undo_add_charop(text, utxt, UNDO_INSERT_1, add);
2457
2458         add_len = BLI_str_utf8_from_unicode(add, ch);
2459
2460         tmp = MEM_mallocN(text->curl->len + add_len + 1, "textline_string");
2461
2462         memcpy(tmp, text->curl->line, text->curc);
2463         memcpy(tmp + text->curc, ch, add_len);
2464         memcpy(tmp + text->curc + add_len, text->curl->line + text->curc, text->curl->len - text->curc + 1);
2465
2466         make_new_line(text->curl, tmp);
2467
2468         text->curc += add_len;
2469
2470         txt_pop_sel(text);
2471
2472         txt_make_dirty(text);
2473         txt_clean_text(text);
2474
2475         return 1;
2476 }
2477
2478 bool txt_add_char(Text *text, TextUndoBuf *utxt, unsigned int add)
2479 {
2480         return txt_add_char_intern(text, utxt, add, (text->flags & TXT_TABSTOSPACES) != 0);
2481 }
2482
2483 bool txt_add_raw_char(Text *text, TextUndoBuf *utxt, unsigned int add)
2484 {
2485         return txt_add_char_intern(text, utxt, add, 0);
2486 }
2487
2488 void txt_delete_selected(Text *text, TextUndoBuf *utxt)
2489 {
2490         txt_delete_sel(text, utxt);
2491         txt_make_dirty(text);
2492 }
2493
2494 bool txt_replace_char(Text *text, TextUndoBuf *utxt, unsigned int add)
2495 {
2496         unsigned int del;
2497         size_t del_size = 0, add_size;
2498         char ch[BLI_UTF8_MAX];
2499
2500         if (!text->curl) return false;
2501
2502         /* If text is selected or we're at the end of the line just use txt_add_char */
2503         if (text->curc == text->curl->len || txt_has_sel(text) || add == '\n') {
2504                 return txt_add_char(text, utxt, add);
2505         }
2506
2507         del = BLI_str_utf8_as_unicode_and_size(text->curl->line + text->curc, &del_size);
2508         add_size = BLI_str_utf8_from_unicode(add, ch);
2509
2510         if (add_size > del_size) {
2511                 char *tmp = MEM_mallocN(text->curl->len + add_size - del_size + 1, "textline_string");
2512                 memcpy(tmp, text->curl->line, text->curc);
2513                 memcpy(tmp + text->curc + add_size, text->curl->line + text->curc + del_size, text->curl->len - text->curc - del_size + 1);
2514                 MEM_freeN(text->curl->line);
2515                 text->curl->line = tmp;
2516         }
2517         else if (add_size < del_size) {
2518                 char *tmp = text->curl->line;
2519                 memmove(tmp + text->curc + add_size, tmp + text->curc + del_size, text->curl->len - text->curc - del_size + 1);
2520         }
2521
2522         memcpy(text->curl->line + text->curc, ch, add_size);
2523         text->curc += add_size;
2524         text->curl->len += add_size - del_size;
2525
2526         txt_pop_sel(text);
2527         txt_make_dirty(text);
2528         txt_clean_text(text);
2529
2530         /* Should probably create a new op for this */
2531         if (!undoing) {
2532                 txt_undo_add_charop(text, utxt, UNDO_INSERT_1, add);
2533                 text->curc -= add_size;
2534                 txt_pop_sel(text);
2535                 txt_undo_add_charop(text, utxt, UNDO_DEL_1, del);
2536                 text->curc += add_size;
2537                 txt_pop_sel(text);
2538         }
2539         return true;
2540 }
2541
2542 /**
2543  * Generic prefix operation, use for comment & indent.
2544  *
2545  * \note caller must handle undo.
2546  */
2547 static void txt_select_prefix(Text *text, const char *add)
2548 {
2549         int len, num, curc_old;
2550         char *tmp;
2551
2552         const int indentlen = strlen(add);
2553
2554         BLI_assert(!ELEM(NULL, text->curl, text->sell));
2555
2556         curc_old = text->curc;
2557
2558         num = 0;
2559         while (true) {
2560
2561                 /* don't indent blank lines */
2562                 if (text->curl->len != 0) {
2563                         tmp = MEM_mallocN(text->curl->len + indentlen + 1, "textline_string");
2564
2565                         text->curc = 0;
2566                         if (text->curc) memcpy(tmp, text->curl->line, text->curc);  /* XXX never true, check prev line */
2567                         memcpy(tmp + text->curc, add, indentlen);
2568
2569                         len = text->curl->len - text->curc;
2570                         if (len > 0) memcpy(tmp + text->curc + indentlen, text->curl->line + text->curc, len);
2571                         tmp[text->curl->len + indentlen] = 0;
2572
2573                         make_new_line(text->curl, tmp);
2574
2575                         text->curc += indentlen;
2576
2577                         txt_make_dirty(text);
2578                         txt_clean_text(text);
2579                 }
2580
2581                 if (text->curl == text->sell) {
2582                         text->selc += indentlen;
2583                         break;
2584                 }
2585                 else {
2586                         text->curl = text->curl->next;
2587                         num++;
2588                 }
2589         }
2590         if (!curc_old) text->curc = 0;
2591         else text->curc = curc_old + indentlen;
2592
2593         while (num > 0) {
2594                 text->curl = text->curl->prev;
2595                 num--;
2596         }
2597
2598         /* caller must handle undo */
2599 }
2600
2601 /**
2602  * Generic un-prefix operation, use for comment & indent.
2603  *
2604  * \param r_line_index_mask: List of lines that are already at indent level 0,
2605  * to store them later into the undo buffer.
2606  *
2607  * \note caller must handle undo.
2608  */
2609 static void txt_select_unprefix(
2610         Text *text, const char *remove,
2611         ListBase *r_line_index_mask, int *r_line_index_mask_len)
2612 {
2613         int num = 0;
2614         const int indentlen = strlen(remove);
2615         bool unindented_first = false;
2616
2617         int curl_span_init = 0;
2618
2619         BLI_assert(!ELEM(NULL, text->curl, text->sell));
2620
2621         BLI_listbase_clear(r_line_index_mask);
2622         *r_line_index_mask_len = 0;
2623
2624         if (!undoing) {
2625                 curl_span_init = txt_get_span(text->lines.first, text->curl);
2626         }
2627
2628         while (true) {
2629                 bool changed = false;
2630                 if (STREQLEN(text->curl->line, remove, indentlen)) {
2631                         if (num == 0)
2632                                 unindented_first = true;
2633                         text->curl->len -= indentlen;
2634                         memmove(text->curl->line, text->curl->line + indentlen, text->curl->len + 1);
2635                         changed = true;
2636                 }
2637                 else {
2638                         if (!undoing) {
2639                                 /* Create list element for 0 indent line */
2640                                 struct LinkInt *idata = MEM_mallocN(sizeof(struct LinkInt), __func__);
2641                                 idata->value = curl_span_init + num;
2642                                 BLI_assert(idata->value == txt_get_span(text->lines.first, text->curl));
2643                                 BLI_addtail(r_line_index_mask, idata);
2644                                 (*r_line_index_mask_len) += 1;
2645                         }
2646                 }
2647
2648                 txt_make_dirty(text);
2649                 txt_clean_text(text);
2650
2651                 if (text->curl == text->sell) {
2652                         if (changed)
2653                                 text->selc = MAX2(text->selc - indentlen, 0);
2654                         break;
2655                 }
2656                 else {
2657                         text->curl = text->curl->next;
2658                         num++;
2659
2660                 }
2661
2662         }
2663
2664         if (unindented_first)
2665                 text->curc = MAX2(text->curc - indentlen, 0);
2666
2667         while (num > 0) {
2668                 text->curl = text->curl->prev;
2669                 num--;
2670         }
2671
2672         /* caller must handle undo */
2673 }
2674
2675 void txt_comment(Text *text, TextUndoBuf *utxt)
2676 {
2677         const char *prefix = "#";
2678
2679         if (ELEM(NULL, text->curl, text->sell)) {
2680                 return;
2681         }
2682
2683         txt_select_prefix(text, prefix);
2684
2685         if (!undoing) {
2686                 txt_undo_add_op(text, utxt, UNDO_COMMENT);
2687         }
2688 }
2689
2690 void txt_uncomment(Text *text, TextUndoBuf *utxt)
2691 {
2692         const char *prefix = "#";
2693         ListBase line_index_mask;
2694         int line_index_mask_len;
2695
2696         if (ELEM(NULL, text->curl, text->sell)) {
2697                 return;
2698         }
2699
2700         txt_select_unprefix(text, prefix, &line_index_mask, &line_index_mask_len);
2701
2702         if (!undoing) {
2703                 txt_undo_add_unprefix_op(text, utxt, UNDO_UNCOMMENT, &line_index_mask, line_index_mask_len);
2704         }
2705
2706         BLI_freelistN(&line_index_mask);
2707 }
2708
2709 void txt_indent(Text *text, TextUndoBuf *utxt)
2710 {
2711         const char *prefix = (text->flags & TXT_TABSTOSPACES) ? tab_to_spaces : "\t";
2712
2713         if (ELEM(NULL, text->curl, text->sell)) {
2714                 return;
2715         }
2716
2717         txt_select_prefix(text, prefix);
2718
2719         if (!undoing) {
2720                 txt_undo_add_op(text, utxt, UNDO_INDENT);
2721         }
2722 }
2723
2724 void txt_unindent(Text *text, TextUndoBuf *utxt)
2725 {
2726         const char *prefix = (text->flags & TXT_TABSTOSPACES) ? tab_to_spaces : "\t";
2727         ListBase line_index_mask;
2728         int line_index_mask_len;
2729
2730         if (ELEM(NULL, text->curl, text->sell)) {
2731                 return;
2732         }
2733
2734         txt_select_unprefix(text, prefix, &line_index_mask, &line_index_mask_len);
2735
2736         if (!undoing) {
2737                 txt_undo_add_unprefix_op(text, utxt, UNDO_UNINDENT, &line_index_mask, line_index_mask_len);
2738         }
2739
2740         BLI_freelistN(&line_index_mask);
2741 }
2742
2743 void txt_move_lines(struct Text *text, TextUndoBuf *utxt, const int direction)
2744 {
2745         TextLine *line_other;
2746
2747         BLI_assert(ELEM(direction, TXT_MOVE_LINE_UP, TXT_MOVE_LINE_DOWN));
2748
2749         if (!text->curl || !text->sell) return;
2750
2751         txt_order_cursors(text, false);
2752
2753         line_other =  (direction == TXT_MOVE_LINE_DOWN) ? text->sell->next : text->curl->prev;
2754
2755         if (!line_other) return;
2756
2757         BLI_remlink(&text->lines, line_other);
2758
2759         if (direction == TXT_MOVE_LINE_DOWN) {
2760                 BLI_insertlinkbefore(&text->lines, text->curl, line_other);
2761         }
2762         else {
2763                 BLI_insertlinkafter(&text->lines, text->sell, line_other);
2764         }
2765
2766         txt_make_dirty(text);
2767         txt_clean_text(text);
2768
2769         if (!undoing) {
2770                 txt_undo_add_op(text, utxt, (direction == TXT_MOVE_LINE_DOWN) ? UNDO_MOVE_LINES_DOWN : UNDO_MOVE_LINES_UP);
2771         }
2772 }
2773
2774 int txt_setcurr_tab_spaces(Text *text, int space)
2775 {
2776         int i = 0;
2777         int test = 0;
2778         const char *word = ":";
2779         const char *comm = "#";
2780         const char indent = (text->flags & TXT_TABSTOSPACES) ? ' ' : '\t';
2781         static const char *back_words[] = {"return", "break", "continue", "pass", "yield", NULL};
2782
2783         if (!text->curl) return 0;
2784
2785         while (text->curl->line[i] == indent) {
2786                 //we only count those tabs/spaces that are before any text or before the curs;
2787                 if (i == text->curc) {
2788                         return i;
2789                 }
2790                 else {
2791                         i++;
2792                 }
2793         }
2794         if (strstr(text->curl->line, word)) {
2795                 /* if we find a ':' on this line, then add a tab but not if it is:
2796                  *  1) in a comment
2797                  *  2) within an identifier
2798                  *      3) after the cursor (text->curc), i.e. when creating space before a function def [#25414]
2799                  */
2800                 int a;
2801                 bool is_indent = false;
2802                 for (a = 0; (a < text->curc) && (text->curl->line[a] != '\0'); a++) {
2803                         char ch = text->curl->line[a];
2804                         if (ch == '#') {
2805                                 break;
2806                         }
2807                         else if (ch == ':') {
2808                                 is_indent = 1;
2809                         }
2810                         else if (ch != ' ' && ch != '\t') {
2811                                 is_indent = 0;
2812                         }
2813                 }
2814                 if (is_indent) {
2815                         i += space;
2816                 }
2817         }
2818
2819         for (test = 0; back_words[test]; test++) {
2820                 /* if there are these key words then remove a tab because we are done with the block */
2821                 if (strstr(text->curl->line, back_words[test]) && i > 0) {
2822                         if (strcspn(text->curl->line, back_words[test]) < strcspn(text->curl->line, comm)) {
2823                                 i -= space;
2824                         }
2825                 }
2826         }
2827         return i;
2828 }
2829
2830 /*******************************/
2831 /* Character utility functions */
2832 /*******************************/
2833
2834 int text_check_bracket(const char ch)
2835 {
2836         int a;
2837         char opens[] = "([{";
2838         char close[] = ")]}";
2839
2840         for (a = 0; a < (sizeof(opens) - 1); a++) {
2841                 if (ch == opens[a])
2842                         return a + 1;
2843                 else if (ch == close[a])
2844                         return -(a + 1);
2845         }
2846         return 0;
2847 }
2848
2849 /* TODO, have a function for operators - http://docs.python.org/py3k/reference/lexical_analysis.html#operators */
2850 bool text_check_delim(const char ch)
2851 {
2852         int a;
2853         char delims[] = "():\"\' ~!%^&*-+=[]{};/<>|.#\t,@";
2854
2855         for (a = 0; a < (sizeof(delims) - 1); a++) {
2856                 if (ch == delims[a])
2857                         return true;
2858         }
2859         return false;
2860 }
2861
2862 bool text_check_digit(const char ch)
2863 {
2864         if (ch < '0') return false;
2865         if (ch <= '9') return true;
2866         return false;
2867 }
2868
2869 bool text_check_identifier(const char ch)
2870 {
2871         if (ch < '0') return false;
2872         if (ch <= '9') return true;
2873         if (ch < 'A') return false;
2874         if (ch <= 'Z' || ch == '_') return true;
2875         if (ch < 'a') return false;
2876         if (ch <= 'z') return true;
2877         return false;
2878 }
2879
2880 bool text_check_identifier_nodigit(const char ch)
2881 {
2882         if (ch <= '9') return false;
2883         if (ch < 'A') return false;
2884         if (ch <= 'Z' || ch == '_') return true;
2885         if (ch < 'a') return false;
2886         if (ch <= 'z') return true;
2887         return false;
2888 }
2889
2890 #ifndef WITH_PYTHON
2891 int text_check_identifier_unicode(const unsigned int ch)
2892 {
2893         return (ch < 255 && text_check_identifier((unsigned int)ch));
2894 }
2895
2896 int text_check_identifier_nodigit_unicode(const unsigned int ch)
2897 {
2898         return (ch < 255 && text_check_identifier_nodigit((char)ch));
2899 }
2900 #endif  /* WITH_PYTHON */
2901
2902 bool text_check_whitespace(const char ch)
2903 {
2904         if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')
2905                 return true;
2906         return false;
2907 }
2908
2909 int text_find_identifier_start(const char *str, int i)
2910 {
2911         if (UNLIKELY(i <= 0)) {
2912                 return 0;
2913         }
2914
2915         while (i--) {
2916                 if (!text_check_identifier(str[i])) {
2917                         break;
2918                 }
2919         }
2920         i++;
2921         return i;
2922 }