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