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