797459984e707cefd8128d39651ec73dc9d90ab9
[blender.git] / source / blender / editors / space_text / text_ops.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 sptext
22  */
23
24 #include <errno.h>
25 #include <string.h>
26
27 #include "MEM_guardedalloc.h"
28
29 #include "DNA_text_types.h"
30
31 #include "BLI_blenlib.h"
32 #include "BLI_math_base.h"
33
34 #include "BLT_translation.h"
35
36 #include "PIL_time.h"
37
38 #include "BKE_context.h"
39 #include "BKE_lib_id.h"
40 #include "BKE_main.h"
41 #include "BKE_report.h"
42 #include "BKE_text.h"
43
44 #include "WM_api.h"
45 #include "WM_types.h"
46
47 #include "ED_curve.h"
48 #include "ED_screen.h"
49 #include "ED_text.h"
50 #include "UI_interface.h"
51 #include "UI_resources.h"
52
53 #include "RNA_access.h"
54 #include "RNA_define.h"
55
56 #ifdef WITH_PYTHON
57 #  include "BPY_extern.h"
58 #endif
59
60 #include "text_format.h"
61 #include "text_intern.h"
62
63 static void txt_screen_clamp(SpaceText *st, ARegion *region);
64
65 /* -------------------------------------------------------------------- */
66 /** \name Util
67  * \{ */
68
69 /**
70  * Tests if the given character represents a start of a new line or the
71  * indentation part of a line.
72  * \param c: The current character.
73  * \param r_last_state: A pointer to a flag representing the last state. The
74  * flag may be modified.
75  */
76 static void test_line_start(char c, bool *r_last_state)
77 {
78   if (c == '\n') {
79     *r_last_state = true;
80   }
81   else if (!ELEM(c, '\t', ' ')) {
82     *r_last_state = false;
83   }
84 }
85
86 /**
87  * This function converts the indentation tabs from a buffer to spaces.
88  * \param buf: A pointer to a cstring.
89  * \param tab_size: The size, in spaces, of the tab character.
90  */
91 static char *buf_tabs_to_spaces(const char *in_buf, const int tab_size)
92 {
93   /* Get the number of tab characters in buffer. */
94   bool line_start = true;
95   int num_tabs = 0;
96
97   for (int in_offset = 0; in_buf[in_offset]; in_offset++) {
98     /* Verify if is an indentation whitespace character. */
99     test_line_start(in_buf[in_offset], &line_start);
100
101     if (in_buf[in_offset] == '\t' && line_start) {
102       num_tabs++;
103     }
104   }
105
106   /* Allocate output before with extra space for expanded tabs. */
107   const int out_size = strlen(in_buf) + num_tabs * (tab_size - 1) + 1;
108   char *out_buf = MEM_mallocN(out_size * sizeof(char), __func__);
109
110   /* Fill output buffer. */
111   int spaces_until_tab = 0;
112   int out_offset = 0;
113   line_start = true;
114
115   for (int in_offset = 0; in_buf[in_offset]; in_offset++) {
116     /* Verify if is an indentation whitespace character. */
117     test_line_start(in_buf[in_offset], &line_start);
118
119     if (in_buf[in_offset] == '\t' && line_start) {
120       /* Calculate tab size so it fills until next indentation. */
121       int num_spaces = tab_size - (spaces_until_tab % tab_size);
122       spaces_until_tab = 0;
123
124       /* Write to buffer. */
125       memset(&out_buf[out_offset], ' ', num_spaces);
126       out_offset += num_spaces;
127     }
128     else {
129       if (in_buf[in_offset] == ' ') {
130         spaces_until_tab++;
131       }
132       else if (in_buf[in_offset] == '\n') {
133         spaces_until_tab = 0;
134       }
135
136       out_buf[out_offset++] = in_buf[in_offset];
137     }
138   }
139
140   out_buf[out_offset] = '\0';
141   return out_buf;
142 }
143
144 BLI_INLINE int text_pixel_x_to_column(SpaceText *st, const int x)
145 {
146   /* Add half the char width so mouse cursor selection is in between letters. */
147   return (x + (st->runtime.cwidth_px / 2)) / st->runtime.cwidth_px;
148 }
149
150 /** \} */
151
152 /* -------------------------------------------------------------------- */
153 /** \name Operator Poll
154  * \{ */
155
156 static bool text_new_poll(bContext *UNUSED(C))
157 {
158   return 1;
159 }
160
161 static bool text_edit_poll(bContext *C)
162 {
163   Text *text = CTX_data_edit_text(C);
164
165   if (!text) {
166     return 0;
167   }
168
169   if (ID_IS_LINKED(text)) {
170     // BKE_report(op->reports, RPT_ERROR, "Cannot edit external library data");
171     return 0;
172   }
173
174   return 1;
175 }
176
177 bool text_space_edit_poll(bContext *C)
178 {
179   SpaceText *st = CTX_wm_space_text(C);
180   Text *text = CTX_data_edit_text(C);
181
182   if (!st || !text) {
183     return 0;
184   }
185
186   if (ID_IS_LINKED(text)) {
187     // BKE_report(op->reports, RPT_ERROR, "Cannot edit external library data");
188     return 0;
189   }
190
191   return 1;
192 }
193
194 static bool text_region_edit_poll(bContext *C)
195 {
196   SpaceText *st = CTX_wm_space_text(C);
197   Text *text = CTX_data_edit_text(C);
198   ARegion *region = CTX_wm_region(C);
199
200   if (!st || !text) {
201     return 0;
202   }
203
204   if (!region || region->regiontype != RGN_TYPE_WINDOW) {
205     return 0;
206   }
207
208   if (ID_IS_LINKED(text)) {
209     // BKE_report(op->reports, RPT_ERROR, "Cannot edit external library data");
210     return 0;
211   }
212
213   return 1;
214 }
215
216 /** \} */
217
218 /* -------------------------------------------------------------------- */
219 /** \name Updates
220  * \{ */
221
222 void text_update_line_edited(TextLine *line)
223 {
224   if (!line) {
225     return;
226   }
227
228   /* we just free format here, and let it rebuild during draw */
229   if (line->format) {
230     MEM_freeN(line->format);
231     line->format = NULL;
232   }
233 }
234
235 void text_update_edited(Text *text)
236 {
237   TextLine *line;
238
239   for (line = text->lines.first; line; line = line->next) {
240     text_update_line_edited(line);
241   }
242 }
243
244 /** \} */
245
246 /* -------------------------------------------------------------------- */
247 /** \name New Operator
248  * \{ */
249
250 static int text_new_exec(bContext *C, wmOperator *UNUSED(op))
251 {
252   SpaceText *st = CTX_wm_space_text(C);
253   Main *bmain = CTX_data_main(C);
254   Text *text;
255   PointerRNA ptr, idptr;
256   PropertyRNA *prop;
257
258   text = BKE_text_add(bmain, "Text");
259   /* Texts have no user by default... Only the 'real' user flag. */
260   id_us_min(&text->id);
261
262   /* hook into UI */
263   UI_context_active_but_prop_get_templateID(C, &ptr, &prop);
264
265   if (prop) {
266     RNA_id_pointer_create(&text->id, &idptr);
267     RNA_property_pointer_set(&ptr, prop, idptr, NULL);
268     RNA_property_update(C, &ptr, prop);
269   }
270   else if (st) {
271     st->text = text;
272     st->left = 0;
273     st->top = 0;
274     st->runtime.scroll_ofs_px[0] = 0;
275     st->runtime.scroll_ofs_px[1] = 0;
276     text_drawcache_tag_update(st, 1);
277   }
278
279   WM_event_add_notifier(C, NC_TEXT | NA_ADDED, text);
280
281   return OPERATOR_FINISHED;
282 }
283
284 void TEXT_OT_new(wmOperatorType *ot)
285 {
286   /* identifiers */
287   ot->name = "New Text";
288   ot->idname = "TEXT_OT_new";
289   ot->description = "Create a new text data-block";
290
291   /* api callbacks */
292   ot->exec = text_new_exec;
293   ot->poll = text_new_poll;
294
295   /* flags */
296   ot->flag = OPTYPE_UNDO;
297 }
298
299 /** \} */
300
301 /* -------------------------------------------------------------------- */
302 /** \name Open Operator
303  * \{ */
304
305 static void text_open_init(bContext *C, wmOperator *op)
306 {
307   PropertyPointerRNA *pprop;
308
309   op->customdata = pprop = MEM_callocN(sizeof(PropertyPointerRNA), "OpenPropertyPointerRNA");
310   UI_context_active_but_prop_get_templateID(C, &pprop->ptr, &pprop->prop);
311 }
312
313 static void text_open_cancel(bContext *UNUSED(C), wmOperator *op)
314 {
315   MEM_freeN(op->customdata);
316 }
317
318 static int text_open_exec(bContext *C, wmOperator *op)
319 {
320   SpaceText *st = CTX_wm_space_text(C);
321   Main *bmain = CTX_data_main(C);
322   Text *text;
323   PropertyPointerRNA *pprop;
324   PointerRNA idptr;
325   char str[FILE_MAX];
326   const bool internal = RNA_boolean_get(op->ptr, "internal");
327
328   RNA_string_get(op->ptr, "filepath", str);
329
330   text = BKE_text_load_ex(bmain, str, BKE_main_blendfile_path(bmain), internal);
331   /* Texts have no user by default... Only the 'real' user flag. */
332   id_us_min(&text->id);
333
334   if (!text) {
335     if (op->customdata) {
336       MEM_freeN(op->customdata);
337     }
338     return OPERATOR_CANCELLED;
339   }
340
341   if (!op->customdata) {
342     text_open_init(C, op);
343   }
344
345   /* hook into UI */
346   pprop = op->customdata;
347
348   if (pprop->prop) {
349     RNA_id_pointer_create(&text->id, &idptr);
350     RNA_property_pointer_set(&pprop->ptr, pprop->prop, idptr, NULL);
351     RNA_property_update(C, &pprop->ptr, pprop->prop);
352   }
353   else if (st) {
354     st->text = text;
355     st->left = 0;
356     st->top = 0;
357     st->runtime.scroll_ofs_px[0] = 0;
358     st->runtime.scroll_ofs_px[1] = 0;
359   }
360
361   text_drawcache_tag_update(st, 1);
362   WM_event_add_notifier(C, NC_TEXT | NA_ADDED, text);
363
364   MEM_freeN(op->customdata);
365
366   return OPERATOR_FINISHED;
367 }
368
369 static int text_open_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
370 {
371   Main *bmain = CTX_data_main(C);
372   Text *text = CTX_data_edit_text(C);
373   const char *path = (text && text->name) ? text->name : BKE_main_blendfile_path(bmain);
374
375   if (RNA_struct_property_is_set(op->ptr, "filepath")) {
376     return text_open_exec(C, op);
377   }
378
379   text_open_init(C, op);
380   RNA_string_set(op->ptr, "filepath", path);
381   WM_event_add_fileselect(C, op);
382
383   return OPERATOR_RUNNING_MODAL;
384 }
385
386 void TEXT_OT_open(wmOperatorType *ot)
387 {
388   /* identifiers */
389   ot->name = "Open Text";
390   ot->idname = "TEXT_OT_open";
391   ot->description = "Open a new text data-block";
392
393   /* api callbacks */
394   ot->exec = text_open_exec;
395   ot->invoke = text_open_invoke;
396   ot->cancel = text_open_cancel;
397   ot->poll = text_new_poll;
398
399   /* flags */
400   ot->flag = OPTYPE_UNDO;
401
402   /* properties */
403   WM_operator_properties_filesel(ot,
404                                  FILE_TYPE_FOLDER | FILE_TYPE_TEXT | FILE_TYPE_PYSCRIPT,
405                                  FILE_SPECIAL,
406                                  FILE_OPENFILE,
407                                  WM_FILESEL_FILEPATH,
408                                  FILE_DEFAULTDISPLAY,
409                                  FILE_SORT_ALPHA);  // XXX TODO, relative_path
410   RNA_def_boolean(
411       ot->srna, "internal", 0, "Make internal", "Make text file internal after loading");
412 }
413
414 /** \} */
415
416 /* -------------------------------------------------------------------- */
417 /** \name Reload Operator
418  * \{ */
419
420 static int text_reload_exec(bContext *C, wmOperator *op)
421 {
422   SpaceText *st = CTX_wm_space_text(C);
423   Text *text = CTX_data_edit_text(C);
424   ARegion *region = CTX_wm_region(C);
425
426   /* store view & cursor state */
427   const int orig_top = st->top;
428   const int orig_curl = BLI_findindex(&text->lines, text->curl);
429   const int orig_curc = text->curc;
430
431   /* Don't make this part of 'poll', since 'Alt-R' will type 'R',
432    * if poll checks for the filename. */
433   if (text->name == NULL) {
434     BKE_report(op->reports, RPT_ERROR, "This text has not been saved");
435     return OPERATOR_CANCELLED;
436   }
437
438   if (!BKE_text_reload(text)) {
439     BKE_report(op->reports, RPT_ERROR, "Could not reopen file");
440     return OPERATOR_CANCELLED;
441   }
442
443 #ifdef WITH_PYTHON
444   if (text->compiled) {
445     BPY_text_free_code(text);
446   }
447 #endif
448
449   text_update_edited(text);
450   text_update_cursor_moved(C);
451   text_drawcache_tag_update(CTX_wm_space_text(C), 1);
452   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
453
454   text->flags &= ~TXT_ISDIRTY;
455
456   /* return to scroll position */
457   st->top = orig_top;
458   txt_screen_clamp(st, region);
459   /* return cursor */
460   txt_move_to(text, orig_curl, orig_curc, false);
461
462   return OPERATOR_FINISHED;
463 }
464
465 void TEXT_OT_reload(wmOperatorType *ot)
466 {
467   /* identifiers */
468   ot->name = "Reload";
469   ot->idname = "TEXT_OT_reload";
470   ot->description = "Reload active text data-block from its file";
471
472   /* api callbacks */
473   ot->exec = text_reload_exec;
474   ot->invoke = WM_operator_confirm;
475   ot->poll = text_edit_poll;
476 }
477
478 /** \} */
479
480 /* -------------------------------------------------------------------- */
481 /** \name Delete Operator
482  * \{ */
483
484 static bool text_unlink_poll(bContext *C)
485 {
486   /* it should be possible to unlink texts if they're lib-linked in... */
487   return CTX_data_edit_text(C) != NULL;
488 }
489
490 static int text_unlink_exec(bContext *C, wmOperator *UNUSED(op))
491 {
492   Main *bmain = CTX_data_main(C);
493   SpaceText *st = CTX_wm_space_text(C);
494   Text *text = CTX_data_edit_text(C);
495
496   /* make the previous text active, if its not there make the next text active */
497   if (st) {
498     if (text->id.prev) {
499       st->text = text->id.prev;
500       text_update_cursor_moved(C);
501       WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
502     }
503     else if (text->id.next) {
504       st->text = text->id.next;
505       text_update_cursor_moved(C);
506       WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
507     }
508   }
509
510   BKE_id_delete(bmain, text);
511
512   text_drawcache_tag_update(st, 1);
513   WM_event_add_notifier(C, NC_TEXT | NA_REMOVED, NULL);
514
515   return OPERATOR_FINISHED;
516 }
517
518 void TEXT_OT_unlink(wmOperatorType *ot)
519 {
520   /* identifiers */
521   ot->name = "Unlink";
522   ot->idname = "TEXT_OT_unlink";
523   ot->description = "Unlink active text data-block";
524
525   /* api callbacks */
526   ot->exec = text_unlink_exec;
527   ot->invoke = WM_operator_confirm;
528   ot->poll = text_unlink_poll;
529
530   /* flags */
531   ot->flag = OPTYPE_UNDO;
532 }
533
534 /** \} */
535
536 /* -------------------------------------------------------------------- */
537 /** \name Make Internal Operator
538  * \{ */
539
540 static int text_make_internal_exec(bContext *C, wmOperator *UNUSED(op))
541 {
542   Text *text = CTX_data_edit_text(C);
543
544   text->flags |= TXT_ISMEM | TXT_ISDIRTY;
545
546   if (text->name) {
547     MEM_freeN(text->name);
548     text->name = NULL;
549   }
550
551   text_update_cursor_moved(C);
552   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
553
554   return OPERATOR_FINISHED;
555 }
556
557 void TEXT_OT_make_internal(wmOperatorType *ot)
558 {
559   /* identifiers */
560   ot->name = "Make Internal";
561   ot->idname = "TEXT_OT_make_internal";
562   ot->description = "Make active text file internal";
563
564   /* api callbacks */
565   ot->exec = text_make_internal_exec;
566   ot->poll = text_edit_poll;
567
568   /* flags */
569   ot->flag = OPTYPE_UNDO;
570 }
571
572 /** \} */
573
574 /* -------------------------------------------------------------------- */
575 /** \name Save Operator
576  * \{ */
577
578 static bool text_save_poll(bContext *C)
579 {
580   Text *text = CTX_data_edit_text(C);
581
582   if (!text_edit_poll(C)) {
583     return 0;
584   }
585
586   return (text->name != NULL && !(text->flags & TXT_ISMEM));
587 }
588
589 static void txt_write_file(Main *bmain, Text *text, ReportList *reports)
590 {
591   FILE *fp;
592   TextLine *tmp;
593   BLI_stat_t st;
594   char filepath[FILE_MAX];
595
596   BLI_strncpy(filepath, text->name, FILE_MAX);
597   BLI_path_abs(filepath, BKE_main_blendfile_path(bmain));
598
599   fp = BLI_fopen(filepath, "w");
600   if (fp == NULL) {
601     BKE_reportf(reports,
602                 RPT_ERROR,
603                 "Unable to save '%s': %s",
604                 filepath,
605                 errno ? strerror(errno) : TIP_("unknown error writing file"));
606     return;
607   }
608
609   for (tmp = text->lines.first; tmp; tmp = tmp->next) {
610     fputs(tmp->line, fp);
611     if (tmp->next) {
612       fputc('\n', fp);
613     }
614   }
615
616   fclose(fp);
617
618   if (BLI_stat(filepath, &st) == 0) {
619     text->mtime = st.st_mtime;
620
621     /* report since this can be called from key-shortcuts */
622     BKE_reportf(reports, RPT_INFO, "Saved Text '%s'", filepath);
623   }
624   else {
625     text->mtime = 0;
626     BKE_reportf(reports,
627                 RPT_WARNING,
628                 "Unable to stat '%s': %s",
629                 filepath,
630                 errno ? strerror(errno) : TIP_("unknown error stating file"));
631   }
632
633   text->flags &= ~TXT_ISDIRTY;
634 }
635
636 static int text_save_exec(bContext *C, wmOperator *op)
637 {
638   Main *bmain = CTX_data_main(C);
639   Text *text = CTX_data_edit_text(C);
640
641   txt_write_file(bmain, text, op->reports);
642
643   text_update_cursor_moved(C);
644   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
645
646   return OPERATOR_FINISHED;
647 }
648
649 void TEXT_OT_save(wmOperatorType *ot)
650 {
651   /* identifiers */
652   ot->name = "Save";
653   ot->idname = "TEXT_OT_save";
654   ot->description = "Save active text data-block";
655
656   /* api callbacks */
657   ot->exec = text_save_exec;
658   ot->poll = text_save_poll;
659 }
660
661 /** \} */
662
663 /* -------------------------------------------------------------------- */
664 /** \name Save As Operator
665  * \{ */
666
667 static int text_save_as_exec(bContext *C, wmOperator *op)
668 {
669   Main *bmain = CTX_data_main(C);
670   Text *text = CTX_data_edit_text(C);
671   char str[FILE_MAX];
672
673   if (!text) {
674     return OPERATOR_CANCELLED;
675   }
676
677   RNA_string_get(op->ptr, "filepath", str);
678
679   if (text->name) {
680     MEM_freeN(text->name);
681   }
682   text->name = BLI_strdup(str);
683   text->flags &= ~TXT_ISMEM;
684
685   txt_write_file(bmain, text, op->reports);
686
687   text_update_cursor_moved(C);
688   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
689
690   return OPERATOR_FINISHED;
691 }
692
693 static int text_save_as_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
694 {
695   Main *bmain = CTX_data_main(C);
696   Text *text = CTX_data_edit_text(C);
697   const char *str;
698
699   if (RNA_struct_property_is_set(op->ptr, "filepath")) {
700     return text_save_as_exec(C, op);
701   }
702
703   if (text->name) {
704     str = text->name;
705   }
706   else if (text->flags & TXT_ISMEM) {
707     str = text->id.name + 2;
708   }
709   else {
710     str = BKE_main_blendfile_path(bmain);
711   }
712
713   RNA_string_set(op->ptr, "filepath", str);
714   WM_event_add_fileselect(C, op);
715
716   return OPERATOR_RUNNING_MODAL;
717 }
718
719 void TEXT_OT_save_as(wmOperatorType *ot)
720 {
721   /* identifiers */
722   ot->name = "Save As";
723   ot->idname = "TEXT_OT_save_as";
724   ot->description = "Save active text file with options";
725
726   /* api callbacks */
727   ot->exec = text_save_as_exec;
728   ot->invoke = text_save_as_invoke;
729   ot->poll = text_edit_poll;
730
731   /* properties */
732   WM_operator_properties_filesel(ot,
733                                  FILE_TYPE_FOLDER | FILE_TYPE_TEXT | FILE_TYPE_PYSCRIPT,
734                                  FILE_SPECIAL,
735                                  FILE_SAVE,
736                                  WM_FILESEL_FILEPATH,
737                                  FILE_DEFAULTDISPLAY,
738                                  FILE_SORT_ALPHA);  // XXX TODO, relative_path
739 }
740
741 /** \} */
742
743 /* -------------------------------------------------------------------- */
744 /** \name Run Script Operator
745  * \{ */
746
747 static bool text_run_script_poll(bContext *C)
748 {
749   return (CTX_data_edit_text(C) != NULL);
750 }
751
752 static int text_run_script(bContext *C, ReportList *reports)
753 {
754 #ifdef WITH_PYTHON
755   Text *text = CTX_data_edit_text(C);
756   const bool is_live = (reports == NULL);
757
758   /* only for comparison */
759   void *curl_prev = text->curl;
760   int curc_prev = text->curc;
761
762   if (BPY_execute_text(C, text, reports, !is_live)) {
763     if (is_live) {
764       /* for nice live updates */
765       WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
766     }
767     return OPERATOR_FINISHED;
768   }
769
770   /* Don't report error messages while live editing */
771   if (!is_live) {
772     /* text may have freed its self */
773     if (CTX_data_edit_text(C) == text) {
774       if (text->curl != curl_prev || curc_prev != text->curc) {
775         text_update_cursor_moved(C);
776         WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
777       }
778     }
779
780     BKE_report(
781         reports, RPT_ERROR, "Python script failed, check the message in the system console");
782
783     return OPERATOR_FINISHED;
784   }
785 #else
786   (void)C;
787   (void)reports;
788 #endif /* !WITH_PYTHON */
789   return OPERATOR_CANCELLED;
790 }
791
792 static int text_run_script_exec(bContext *C, wmOperator *op)
793 {
794 #ifndef WITH_PYTHON
795   (void)C; /* unused */
796
797   BKE_report(op->reports, RPT_ERROR, "Python disabled in this build");
798
799   return OPERATOR_CANCELLED;
800 #else
801   return text_run_script(C, op->reports);
802 #endif
803 }
804
805 void TEXT_OT_run_script(wmOperatorType *ot)
806 {
807   /* identifiers */
808   ot->name = "Run Script";
809   ot->idname = "TEXT_OT_run_script";
810   ot->description = "Run active script";
811
812   /* api callbacks */
813   ot->poll = text_run_script_poll;
814   ot->exec = text_run_script_exec;
815
816   /* flags */
817   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
818 }
819
820 /** \} */
821
822 /* -------------------------------------------------------------------- */
823 /** \name Refresh Pyconstraints Operator
824  * \{ */
825
826 static int text_refresh_pyconstraints_exec(bContext *UNUSED(C), wmOperator *UNUSED(op))
827 {
828 #ifdef WITH_PYTHON
829 #  if 0
830   Main *bmain = CTX_data_main(C);
831   Text *text = CTX_data_edit_text(C);
832   Object *ob;
833   bConstraint *con;
834   short update;
835
836   /* check all pyconstraints */
837   for (ob = bmain->objects.first; ob; ob = ob->id.next) {
838     update = 0;
839     if (ob->type == OB_ARMATURE && ob->pose) {
840       bPoseChannel *pchan;
841       for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
842         for (con = pchan->constraints.first; con; con = con->next) {
843           if (con->type == CONSTRAINT_TYPE_PYTHON) {
844             bPythonConstraint *data = con->data;
845             if (data->text == text) {
846               BPY_pyconstraint_update(ob, con);
847             }
848             update = 1;
849           }
850         }
851       }
852     }
853     for (con = ob->constraints.first; con; con = con->next) {
854       if (con->type == CONSTRAINT_TYPE_PYTHON) {
855         bPythonConstraint *data = con->data;
856         if (data->text == text) {
857           BPY_pyconstraint_update(ob, con);
858         }
859         update = 1;
860       }
861     }
862
863     if (update) {
864       DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
865     }
866   }
867 #  endif
868 #endif
869
870   return OPERATOR_FINISHED;
871 }
872
873 void TEXT_OT_refresh_pyconstraints(wmOperatorType *ot)
874 {
875   /* identifiers */
876   ot->name = "Refresh PyConstraints";
877   ot->idname = "TEXT_OT_refresh_pyconstraints";
878   ot->description = "Refresh all pyconstraints";
879
880   /* api callbacks */
881   ot->exec = text_refresh_pyconstraints_exec;
882   ot->poll = text_edit_poll;
883 }
884
885 /** \} */
886
887 /* -------------------------------------------------------------------- */
888 /** \name Paste Operator
889  * \{ */
890
891 static int text_paste_exec(bContext *C, wmOperator *op)
892 {
893   const bool selection = RNA_boolean_get(op->ptr, "selection");
894   Text *text = CTX_data_edit_text(C);
895   char *buf;
896   int buf_len;
897
898   buf = WM_clipboard_text_get(selection, &buf_len);
899
900   if (!buf) {
901     return OPERATOR_CANCELLED;
902   }
903
904   text_drawcache_tag_update(CTX_wm_space_text(C), 0);
905
906   ED_text_undo_push_init(C);
907
908   /* Convert clipboard content indentation to spaces if specified */
909   if (text->flags & TXT_TABSTOSPACES) {
910     char *new_buf = buf_tabs_to_spaces(buf, TXT_TABSIZE);
911     MEM_freeN(buf);
912     buf = new_buf;
913   }
914
915   txt_insert_buf(text, buf);
916   text_update_edited(text);
917
918   MEM_freeN(buf);
919
920   text_update_cursor_moved(C);
921   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
922
923   /* run the script while editing, evil but useful */
924   if (CTX_wm_space_text(C)->live_edit) {
925     text_run_script(C, NULL);
926   }
927
928   return OPERATOR_FINISHED;
929 }
930
931 void TEXT_OT_paste(wmOperatorType *ot)
932 {
933   /* identifiers */
934   ot->name = "Paste";
935   ot->idname = "TEXT_OT_paste";
936   ot->description = "Paste text from clipboard";
937
938   /* api callbacks */
939   ot->exec = text_paste_exec;
940   ot->poll = text_edit_poll;
941
942   /* flags */
943   ot->flag = OPTYPE_UNDO;
944
945   /* properties */
946   RNA_def_boolean(ot->srna,
947                   "selection",
948                   0,
949                   "Selection",
950                   "Paste text selected elsewhere rather than copied (X11 only)");
951 }
952
953 /** \} */
954
955 /* -------------------------------------------------------------------- */
956 /** \name Duplicate Operator
957  * \{ */
958
959 static int text_duplicate_line_exec(bContext *C, wmOperator *UNUSED(op))
960 {
961   Text *text = CTX_data_edit_text(C);
962
963   ED_text_undo_push_init(C);
964
965   txt_duplicate_line(text);
966
967   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
968
969   /* run the script while editing, evil but useful */
970   if (CTX_wm_space_text(C)->live_edit) {
971     text_run_script(C, NULL);
972   }
973
974   return OPERATOR_FINISHED;
975 }
976
977 void TEXT_OT_duplicate_line(wmOperatorType *ot)
978 {
979   /* identifiers */
980   ot->name = "Duplicate Line";
981   ot->idname = "TEXT_OT_duplicate_line";
982   ot->description = "Duplicate the current line";
983
984   /* api callbacks */
985   ot->exec = text_duplicate_line_exec;
986   ot->poll = text_edit_poll;
987
988   /* flags */
989   ot->flag = OPTYPE_UNDO;
990 }
991
992 /** \} */
993
994 /* -------------------------------------------------------------------- */
995 /** \name Copy Operator
996  * \{ */
997
998 static void txt_copy_clipboard(Text *text)
999 {
1000   char *buf;
1001
1002   if (!txt_has_sel(text)) {
1003     return;
1004   }
1005
1006   buf = txt_sel_to_buf(text, NULL);
1007
1008   if (buf) {
1009     WM_clipboard_text_set(buf, 0);
1010     MEM_freeN(buf);
1011   }
1012 }
1013
1014 static int text_copy_exec(bContext *C, wmOperator *UNUSED(op))
1015 {
1016   Text *text = CTX_data_edit_text(C);
1017
1018   txt_copy_clipboard(text);
1019
1020   return OPERATOR_FINISHED;
1021 }
1022
1023 void TEXT_OT_copy(wmOperatorType *ot)
1024 {
1025   /* identifiers */
1026   ot->name = "Copy";
1027   ot->idname = "TEXT_OT_copy";
1028   ot->description = "Copy selected text to clipboard";
1029
1030   /* api callbacks */
1031   ot->exec = text_copy_exec;
1032   ot->poll = text_edit_poll;
1033 }
1034
1035 /** \} */
1036
1037 /* -------------------------------------------------------------------- */
1038 /** \name Cut Operator
1039  * \{ */
1040
1041 static int text_cut_exec(bContext *C, wmOperator *UNUSED(op))
1042 {
1043   Text *text = CTX_data_edit_text(C);
1044
1045   text_drawcache_tag_update(CTX_wm_space_text(C), 0);
1046
1047   txt_copy_clipboard(text);
1048
1049   ED_text_undo_push_init(C);
1050   txt_delete_selected(text);
1051
1052   text_update_cursor_moved(C);
1053   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1054
1055   /* run the script while editing, evil but useful */
1056   if (CTX_wm_space_text(C)->live_edit) {
1057     text_run_script(C, NULL);
1058   }
1059
1060   return OPERATOR_FINISHED;
1061 }
1062
1063 void TEXT_OT_cut(wmOperatorType *ot)
1064 {
1065   /* identifiers */
1066   ot->name = "Cut";
1067   ot->idname = "TEXT_OT_cut";
1068   ot->description = "Cut selected text to clipboard";
1069
1070   /* api callbacks */
1071   ot->exec = text_cut_exec;
1072   ot->poll = text_edit_poll;
1073
1074   /* flags */
1075   ot->flag = OPTYPE_UNDO;
1076 }
1077
1078 /** \} */
1079
1080 /* -------------------------------------------------------------------- */
1081 /** \name Indent or Autocomplete Operator
1082  * \{ */
1083
1084 static int text_indent_or_autocomplete_exec(bContext *C, wmOperator *UNUSED(op))
1085 {
1086   Text *text = CTX_data_edit_text(C);
1087   TextLine *line = text->curl;
1088   bool text_before_cursor = text->curc != 0 && !ELEM(line->line[text->curc - 1], ' ', '\t');
1089   if (text_before_cursor && (txt_has_sel(text) == false)) {
1090     WM_operator_name_call(C, "TEXT_OT_autocomplete", WM_OP_INVOKE_DEFAULT, NULL);
1091   }
1092   else {
1093     WM_operator_name_call(C, "TEXT_OT_indent", WM_OP_EXEC_DEFAULT, NULL);
1094   }
1095   return OPERATOR_FINISHED;
1096 }
1097
1098 void TEXT_OT_indent_or_autocomplete(wmOperatorType *ot)
1099 {
1100   /* identifiers */
1101   ot->name = "Indent or Autocomplete";
1102   ot->idname = "TEXT_OT_indent_or_autocomplete";
1103   ot->description = "Indent selected text or autocomplete";
1104
1105   /* api callbacks */
1106   ot->exec = text_indent_or_autocomplete_exec;
1107   ot->poll = text_edit_poll;
1108
1109   /* flags */
1110   ot->flag = 0;
1111 }
1112
1113 /** \} */
1114
1115 /* -------------------------------------------------------------------- */
1116 /** \name Indent Operator
1117  * \{ */
1118
1119 static int text_indent_exec(bContext *C, wmOperator *UNUSED(op))
1120 {
1121   Text *text = CTX_data_edit_text(C);
1122
1123   text_drawcache_tag_update(CTX_wm_space_text(C), 0);
1124
1125   ED_text_undo_push_init(C);
1126
1127   if (txt_has_sel(text)) {
1128     txt_order_cursors(text, false);
1129     txt_indent(text);
1130   }
1131   else {
1132     txt_add_char(text, '\t');
1133   }
1134
1135   text_update_edited(text);
1136
1137   text_update_cursor_moved(C);
1138   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1139
1140   return OPERATOR_FINISHED;
1141 }
1142
1143 void TEXT_OT_indent(wmOperatorType *ot)
1144 {
1145   /* identifiers */
1146   ot->name = "Indent";
1147   ot->idname = "TEXT_OT_indent";
1148   ot->description = "Indent selected text";
1149
1150   /* api callbacks */
1151   ot->exec = text_indent_exec;
1152   ot->poll = text_edit_poll;
1153
1154   /* flags */
1155   ot->flag = OPTYPE_UNDO;
1156 }
1157
1158 /** \} */
1159
1160 /* -------------------------------------------------------------------- */
1161 /** \name Unindent Operator
1162  * \{ */
1163
1164 static int text_unindent_exec(bContext *C, wmOperator *UNUSED(op))
1165 {
1166   Text *text = CTX_data_edit_text(C);
1167
1168   text_drawcache_tag_update(CTX_wm_space_text(C), 0);
1169
1170   ED_text_undo_push_init(C);
1171
1172   txt_order_cursors(text, false);
1173   txt_unindent(text);
1174
1175   text_update_edited(text);
1176
1177   text_update_cursor_moved(C);
1178   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1179
1180   return OPERATOR_FINISHED;
1181 }
1182
1183 void TEXT_OT_unindent(wmOperatorType *ot)
1184 {
1185   /* identifiers */
1186   ot->name = "Unindent";
1187   ot->idname = "TEXT_OT_unindent";
1188   ot->description = "Unindent selected text";
1189
1190   /* api callbacks */
1191   ot->exec = text_unindent_exec;
1192   ot->poll = text_edit_poll;
1193
1194   /* flags */
1195   ot->flag = OPTYPE_UNDO;
1196 }
1197
1198 /** \} */
1199
1200 /* -------------------------------------------------------------------- */
1201 /** \name Line Break Operator
1202  * \{ */
1203
1204 static int text_line_break_exec(bContext *C, wmOperator *UNUSED(op))
1205 {
1206   SpaceText *st = CTX_wm_space_text(C);
1207   Text *text = CTX_data_edit_text(C);
1208   int a, curts;
1209   int space = (text->flags & TXT_TABSTOSPACES) ? st->tabnumber : 1;
1210
1211   text_drawcache_tag_update(st, 0);
1212
1213   // double check tabs/spaces before splitting the line
1214   curts = txt_setcurr_tab_spaces(text, space);
1215   ED_text_undo_push_init(C);
1216   txt_split_curline(text);
1217
1218   for (a = 0; a < curts; a++) {
1219     if (text->flags & TXT_TABSTOSPACES) {
1220       txt_add_char(text, ' ');
1221     }
1222     else {
1223       txt_add_char(text, '\t');
1224     }
1225   }
1226
1227   if (text->curl) {
1228     if (text->curl->prev) {
1229       text_update_line_edited(text->curl->prev);
1230     }
1231     text_update_line_edited(text->curl);
1232   }
1233
1234   text_update_cursor_moved(C);
1235   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1236
1237   return OPERATOR_FINISHED;
1238 }
1239
1240 void TEXT_OT_line_break(wmOperatorType *ot)
1241 {
1242   /* identifiers */
1243   ot->name = "Line Break";
1244   ot->idname = "TEXT_OT_line_break";
1245   ot->description = "Insert line break at cursor position";
1246
1247   /* api callbacks */
1248   ot->exec = text_line_break_exec;
1249   ot->poll = text_edit_poll;
1250
1251   /* flags */
1252   ot->flag = OPTYPE_UNDO;
1253 }
1254
1255 /** \} */
1256
1257 /* -------------------------------------------------------------------- */
1258 /** \name Toggle-Comment Operator
1259  * \{ */
1260
1261 static int text_comment_exec(bContext *C, wmOperator *op)
1262 {
1263   Text *text = CTX_data_edit_text(C);
1264   int type = RNA_enum_get(op->ptr, "type");
1265
1266   text_drawcache_tag_update(CTX_wm_space_text(C), 0);
1267
1268   ED_text_undo_push_init(C);
1269
1270   if (txt_has_sel(text)) {
1271     txt_order_cursors(text, false);
1272   }
1273
1274   switch (type) {
1275     case 1:
1276       txt_comment(text);
1277       break;
1278     case -1:
1279       txt_uncomment(text);
1280       break;
1281     default:
1282       if (txt_uncomment(text) == false) {
1283         txt_comment(text);
1284       }
1285       break;
1286   }
1287
1288   text_update_edited(text);
1289
1290   text_update_cursor_moved(C);
1291   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1292
1293   return OPERATOR_FINISHED;
1294 }
1295
1296 void TEXT_OT_comment_toggle(wmOperatorType *ot)
1297 {
1298   static const EnumPropertyItem comment_items[] = {
1299       {0, "TOGGLE", 0, "Toggle Comments", NULL},
1300       {1, "COMMENT", 0, "Comment", NULL},
1301       {-1, "UNCOMMENT", 0, "Un-Comment", NULL},
1302       {0, NULL, 0, NULL, NULL},
1303   };
1304
1305   /* identifiers */
1306   ot->name = "Toggle Comments";
1307   ot->idname = "TEXT_OT_comment_toggle";
1308
1309   /* api callbacks */
1310   ot->exec = text_comment_exec;
1311   ot->poll = text_edit_poll;
1312
1313   /* flags */
1314   ot->flag = OPTYPE_UNDO;
1315
1316   /* properties */
1317   PropertyRNA *prop;
1318   prop = RNA_def_enum(ot->srna, "type", comment_items, 0, "Type", "Add or remove comments");
1319   RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
1320 }
1321
1322 /** \} */
1323
1324 /* -------------------------------------------------------------------- */
1325 /** \name Convert Whitespace Operator
1326  * \{ */
1327
1328 enum { TO_SPACES, TO_TABS };
1329 static const EnumPropertyItem whitespace_type_items[] = {
1330     {TO_SPACES, "SPACES", 0, "To Spaces", NULL},
1331     {TO_TABS, "TABS", 0, "To Tabs", NULL},
1332     {0, NULL, 0, NULL, NULL},
1333 };
1334
1335 static int text_convert_whitespace_exec(bContext *C, wmOperator *op)
1336 {
1337   SpaceText *st = CTX_wm_space_text(C);
1338   Text *text = CTX_data_edit_text(C);
1339   TextLine *tmp;
1340   FlattenString fs;
1341   size_t a, j, max_len = 0;
1342   int type = RNA_enum_get(op->ptr, "type");
1343
1344   /* first convert to all space, this make it a lot easier to convert to tabs
1345    * because there is no mixtures of ' ' && '\t' */
1346   for (tmp = text->lines.first; tmp; tmp = tmp->next) {
1347     char *new_line;
1348
1349     BLI_assert(tmp->line);
1350
1351     flatten_string(st, &fs, tmp->line);
1352     new_line = BLI_strdup(fs.buf);
1353     flatten_string_free(&fs);
1354
1355     MEM_freeN(tmp->line);
1356     if (tmp->format) {
1357       MEM_freeN(tmp->format);
1358     }
1359
1360     /* Put new_line in the tmp->line spot still need to try and set the curc correctly. */
1361     tmp->line = new_line;
1362     tmp->len = strlen(new_line);
1363     tmp->format = NULL;
1364     if (tmp->len > max_len) {
1365       max_len = tmp->len;
1366     }
1367   }
1368
1369   if (type == TO_TABS) {
1370     char *tmp_line = MEM_mallocN(sizeof(*tmp_line) * (max_len + 1), __func__);
1371
1372     for (tmp = text->lines.first; tmp; tmp = tmp->next) {
1373       const char *text_check_line = tmp->line;
1374       const int text_check_line_len = tmp->len;
1375       char *tmp_line_cur = tmp_line;
1376       const size_t tab_len = st->tabnumber;
1377
1378       BLI_assert(text_check_line);
1379
1380       for (a = 0; a < text_check_line_len;) {
1381         /* A tab can only start at a position multiple of tab_len... */
1382         if (!(a % tab_len) && (text_check_line[a] == ' ')) {
1383           /* a + 0 we already know to be ' ' char... */
1384           for (j = 1;
1385                (j < tab_len) && (a + j < text_check_line_len) && (text_check_line[a + j] == ' ');
1386                j++) {
1387             /* pass */
1388           }
1389
1390           if (j == tab_len) {
1391             /* We found a set of spaces that can be replaced by a tab... */
1392             if ((tmp_line_cur == tmp_line) && a != 0) {
1393               /* Copy all 'valid' string already 'parsed'... */
1394               memcpy(tmp_line_cur, text_check_line, a);
1395               tmp_line_cur += a;
1396             }
1397             *tmp_line_cur = '\t';
1398             tmp_line_cur++;
1399             a += j;
1400           }
1401           else {
1402             if (tmp_line_cur != tmp_line) {
1403               memcpy(tmp_line_cur, &text_check_line[a], j);
1404               tmp_line_cur += j;
1405             }
1406             a += j;
1407           }
1408         }
1409         else {
1410           size_t len = BLI_str_utf8_size_safe(&text_check_line[a]);
1411           if (tmp_line_cur != tmp_line) {
1412             memcpy(tmp_line_cur, &text_check_line[a], len);
1413             tmp_line_cur += len;
1414           }
1415           a += len;
1416         }
1417       }
1418
1419       if (tmp_line_cur != tmp_line) {
1420         *tmp_line_cur = '\0';
1421
1422 #ifndef NDEBUG
1423         BLI_assert(tmp_line_cur - tmp_line <= max_len);
1424
1425         flatten_string(st, &fs, tmp_line);
1426         BLI_assert(STREQ(fs.buf, tmp->line));
1427         flatten_string_free(&fs);
1428 #endif
1429
1430         MEM_freeN(tmp->line);
1431         if (tmp->format) {
1432           MEM_freeN(tmp->format);
1433         }
1434
1435         /* Put new_line in the tmp->line spot
1436          * still need to try and set the curc correctly. */
1437         tmp->line = BLI_strdup(tmp_line);
1438         tmp->len = strlen(tmp_line);
1439         tmp->format = NULL;
1440       }
1441     }
1442
1443     MEM_freeN(tmp_line);
1444   }
1445
1446   text_update_edited(text);
1447   text_update_cursor_moved(C);
1448   text_drawcache_tag_update(st, 1);
1449   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1450
1451   return OPERATOR_FINISHED;
1452 }
1453
1454 void TEXT_OT_convert_whitespace(wmOperatorType *ot)
1455 {
1456   /* identifiers */
1457   ot->name = "Convert Whitespace";
1458   ot->idname = "TEXT_OT_convert_whitespace";
1459   ot->description = "Convert whitespaces by type";
1460
1461   /* api callbacks */
1462   ot->exec = text_convert_whitespace_exec;
1463   ot->poll = text_edit_poll;
1464
1465   /* flags */
1466   ot->flag = OPTYPE_UNDO;
1467
1468   /* properties */
1469   RNA_def_enum(ot->srna,
1470                "type",
1471                whitespace_type_items,
1472                TO_SPACES,
1473                "Type",
1474                "Type of whitespace to convert to");
1475 }
1476
1477 /** \} */
1478
1479 /* -------------------------------------------------------------------- */
1480 /** \name Select All Operator
1481  * \{ */
1482
1483 static int text_select_all_exec(bContext *C, wmOperator *UNUSED(op))
1484 {
1485   Text *text = CTX_data_edit_text(C);
1486
1487   txt_sel_all(text);
1488
1489   text_update_cursor_moved(C);
1490   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1491
1492   return OPERATOR_FINISHED;
1493 }
1494
1495 void TEXT_OT_select_all(wmOperatorType *ot)
1496 {
1497   /* identifiers */
1498   ot->name = "Select All";
1499   ot->idname = "TEXT_OT_select_all";
1500   ot->description = "Select all text";
1501
1502   /* api callbacks */
1503   ot->exec = text_select_all_exec;
1504   ot->poll = text_edit_poll;
1505 }
1506
1507 /** \} */
1508
1509 /* -------------------------------------------------------------------- */
1510 /** \name Select Line Operator
1511  * \{ */
1512
1513 static int text_select_line_exec(bContext *C, wmOperator *UNUSED(op))
1514 {
1515   Text *text = CTX_data_edit_text(C);
1516
1517   txt_sel_line(text);
1518
1519   text_update_cursor_moved(C);
1520   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1521
1522   return OPERATOR_FINISHED;
1523 }
1524
1525 void TEXT_OT_select_line(wmOperatorType *ot)
1526 {
1527   /* identifiers */
1528   ot->name = "Select Line";
1529   ot->idname = "TEXT_OT_select_line";
1530   ot->description = "Select text by line";
1531
1532   /* api callbacks */
1533   ot->exec = text_select_line_exec;
1534   ot->poll = text_edit_poll;
1535 }
1536
1537 /** \} */
1538
1539 /* -------------------------------------------------------------------- */
1540 /** \name Select Word Operator
1541  * \{ */
1542
1543 static int text_select_word_exec(bContext *C, wmOperator *UNUSED(op))
1544 {
1545   Text *text = CTX_data_edit_text(C);
1546   /* don't advance cursor before stepping */
1547   const bool use_init_step = false;
1548
1549   txt_jump_left(text, false, use_init_step);
1550   txt_jump_right(text, true, use_init_step);
1551
1552   text_update_cursor_moved(C);
1553   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1554
1555   return OPERATOR_FINISHED;
1556 }
1557
1558 void TEXT_OT_select_word(wmOperatorType *ot)
1559 {
1560   /* identifiers */
1561   ot->name = "Select Word";
1562   ot->idname = "TEXT_OT_select_word";
1563   ot->description = "Select word under cursor";
1564
1565   /* api callbacks */
1566   ot->exec = text_select_word_exec;
1567   ot->poll = text_edit_poll;
1568 }
1569
1570 /** \} */
1571
1572 /* -------------------------------------------------------------------- */
1573 /** \name Move Lines Operators
1574  * \{ */
1575
1576 static int move_lines_exec(bContext *C, wmOperator *op)
1577 {
1578   Text *text = CTX_data_edit_text(C);
1579   const int direction = RNA_enum_get(op->ptr, "direction");
1580
1581   ED_text_undo_push_init(C);
1582
1583   txt_move_lines(text, direction);
1584
1585   text_update_cursor_moved(C);
1586   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
1587
1588   /* run the script while editing, evil but useful */
1589   if (CTX_wm_space_text(C)->live_edit) {
1590     text_run_script(C, NULL);
1591   }
1592
1593   return OPERATOR_FINISHED;
1594 }
1595
1596 void TEXT_OT_move_lines(wmOperatorType *ot)
1597 {
1598   static const EnumPropertyItem direction_items[] = {
1599       {TXT_MOVE_LINE_UP, "UP", 0, "Up", ""},
1600       {TXT_MOVE_LINE_DOWN, "DOWN", 0, "Down", ""},
1601       {0, NULL, 0, NULL, NULL},
1602   };
1603
1604   /* identifiers */
1605   ot->name = "Move Lines";
1606   ot->idname = "TEXT_OT_move_lines";
1607   ot->description = "Move the currently selected line(s) up/down";
1608
1609   /* api callbacks */
1610   ot->exec = move_lines_exec;
1611   ot->poll = text_edit_poll;
1612
1613   /* flags */
1614   ot->flag = OPTYPE_UNDO;
1615
1616   /* properties */
1617   RNA_def_enum(ot->srna, "direction", direction_items, 1, "Direction", "");
1618 }
1619
1620 /** \} */
1621
1622 /* -------------------------------------------------------------------- */
1623 /** \name Move Operator
1624  * \{ */
1625
1626 static const EnumPropertyItem move_type_items[] = {
1627     {LINE_BEGIN, "LINE_BEGIN", 0, "Line Begin", ""},
1628     {LINE_END, "LINE_END", 0, "Line End", ""},
1629     {FILE_TOP, "FILE_TOP", 0, "File Top", ""},
1630     {FILE_BOTTOM, "FILE_BOTTOM", 0, "File Bottom", ""},
1631     {PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
1632     {NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
1633     {PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
1634     {NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
1635     {PREV_LINE, "PREVIOUS_LINE", 0, "Previous Line", ""},
1636     {NEXT_LINE, "NEXT_LINE", 0, "Next Line", ""},
1637     {PREV_PAGE, "PREVIOUS_PAGE", 0, "Previous Page", ""},
1638     {NEXT_PAGE, "NEXT_PAGE", 0, "Next Page", ""},
1639     {0, NULL, 0, NULL, NULL},
1640 };
1641
1642 /* get cursor position in line by relative wrapped line and column positions */
1643 static int text_get_cursor_rel(
1644     SpaceText *st, ARegion *region, TextLine *linein, int rell, int relc)
1645 {
1646   int i, j, start, end, max, chop, curs, loop, endj, found, selc;
1647   char ch;
1648
1649   max = wrap_width(st, region);
1650
1651   selc = start = endj = curs = found = 0;
1652   end = max;
1653   chop = loop = 1;
1654
1655   for (i = 0, j = 0; loop; j += BLI_str_utf8_size_safe(linein->line + j)) {
1656     int chars;
1657     int columns = BLI_str_utf8_char_width_safe(linein->line + j); /* = 1 for tab */
1658
1659     /* Mimic replacement of tabs */
1660     ch = linein->line[j];
1661     if (ch == '\t') {
1662       chars = st->tabnumber - i % st->tabnumber;
1663       ch = ' ';
1664     }
1665     else {
1666       chars = 1;
1667     }
1668
1669     while (chars--) {
1670       if (rell == 0 && i - start <= relc && i + columns - start > relc) {
1671         /* current position could be wrapped to next line */
1672         /* this should be checked when end of current line would be reached */
1673         selc = j;
1674         found = 1;
1675       }
1676       else if (i - end <= relc && i + columns - end > relc) {
1677         curs = j;
1678       }
1679       if (i + columns - start > max) {
1680         end = MIN2(end, i);
1681
1682         if (found) {
1683           /* exact cursor position was found, check if it's */
1684           /* still on needed line (hasn't been wrapped) */
1685           if (selc > endj && !chop) {
1686             selc = endj;
1687           }
1688           loop = 0;
1689           break;
1690         }
1691
1692         if (chop) {
1693           endj = j;
1694         }
1695
1696         start = end;
1697         end += max;
1698         chop = 1;
1699         rell--;
1700
1701         if (rell == 0 && i + columns - start > relc) {
1702           selc = curs;
1703           loop = 0;
1704           break;
1705         }
1706       }
1707       else if (ch == '\0') {
1708         if (!found) {
1709           selc = linein->len;
1710         }
1711         loop = 0;
1712         break;
1713       }
1714       else if (ch == ' ' || ch == '-') {
1715         if (found) {
1716           loop = 0;
1717           break;
1718         }
1719
1720         if (rell == 0 && i + columns - start > relc) {
1721           selc = curs;
1722           loop = 0;
1723           break;
1724         }
1725         end = i + 1;
1726         endj = j;
1727         chop = 0;
1728       }
1729       i += columns;
1730     }
1731   }
1732
1733   return selc;
1734 }
1735
1736 static int cursor_skip_find_line(
1737     SpaceText *st, ARegion *region, int lines, TextLine **linep, int *charp, int *rell, int *relc)
1738 {
1739   int offl, offc, visible_lines;
1740
1741   wrap_offset_in_line(st, region, *linep, *charp, &offl, &offc);
1742   *relc = text_get_char_pos(st, (*linep)->line, *charp) + offc;
1743   *rell = lines;
1744
1745   /* handle current line */
1746   if (lines > 0) {
1747     visible_lines = text_get_visible_lines(st, region, (*linep)->line);
1748
1749     if (*rell - visible_lines + offl >= 0) {
1750       if (!(*linep)->next) {
1751         if (offl < visible_lines - 1) {
1752           *rell = visible_lines - 1;
1753           return 1;
1754         }
1755
1756         *charp = (*linep)->len;
1757         return 0;
1758       }
1759
1760       *rell -= visible_lines - offl;
1761       *linep = (*linep)->next;
1762     }
1763     else {
1764       *rell += offl;
1765       return 1;
1766     }
1767   }
1768   else {
1769     if (*rell + offl <= 0) {
1770       if (!(*linep)->prev) {
1771         if (offl) {
1772           *rell = 0;
1773           return 1;
1774         }
1775
1776         *charp = 0;
1777         return 0;
1778       }
1779
1780       *rell += offl;
1781       *linep = (*linep)->prev;
1782     }
1783     else {
1784       *rell += offl;
1785       return 1;
1786     }
1787   }
1788
1789   /* skip lines and find destination line and offsets */
1790   while (*linep) {
1791     visible_lines = text_get_visible_lines(st, region, (*linep)->line);
1792
1793     if (lines < 0) { /* moving top */
1794       if (*rell + visible_lines >= 0) {
1795         *rell += visible_lines;
1796         break;
1797       }
1798
1799       if (!(*linep)->prev) {
1800         *rell = 0;
1801         break;
1802       }
1803
1804       *rell += visible_lines;
1805       *linep = (*linep)->prev;
1806     }
1807     else { /* moving bottom */
1808       if (*rell - visible_lines < 0) {
1809         break;
1810       }
1811
1812       if (!(*linep)->next) {
1813         *rell = visible_lines - 1;
1814         break;
1815       }
1816
1817       *rell -= visible_lines;
1818       *linep = (*linep)->next;
1819     }
1820   }
1821
1822   return 1;
1823 }
1824
1825 static void txt_wrap_move_bol(SpaceText *st, ARegion *region, const bool sel)
1826 {
1827   Text *text = st->text;
1828   TextLine **linep;
1829   int *charp;
1830   int oldc, i, j, max, start, end, endj, chop, loop;
1831   char ch;
1832
1833   text_update_character_width(st);
1834
1835   if (sel) {
1836     linep = &text->sell;
1837     charp = &text->selc;
1838   }
1839   else {
1840     linep = &text->curl;
1841     charp = &text->curc;
1842   }
1843
1844   oldc = *charp;
1845
1846   max = wrap_width(st, region);
1847
1848   start = endj = 0;
1849   end = max;
1850   chop = loop = 1;
1851   *charp = 0;
1852
1853   for (i = 0, j = 0; loop; j += BLI_str_utf8_size_safe((*linep)->line + j)) {
1854     int chars;
1855     int columns = BLI_str_utf8_char_width_safe((*linep)->line + j); /* = 1 for tab */
1856
1857     /* Mimic replacement of tabs */
1858     ch = (*linep)->line[j];
1859     if (ch == '\t') {
1860       chars = st->tabnumber - i % st->tabnumber;
1861       ch = ' ';
1862     }
1863     else {
1864       chars = 1;
1865     }
1866
1867     while (chars--) {
1868       if (i + columns - start > max) {
1869         end = MIN2(end, i);
1870
1871         *charp = endj;
1872
1873         if (j >= oldc) {
1874           if (ch == '\0') {
1875             *charp = BLI_str_utf8_offset_from_column((*linep)->line, start);
1876           }
1877           loop = 0;
1878           break;
1879         }
1880
1881         if (chop) {
1882           endj = j;
1883         }
1884
1885         start = end;
1886         end += max;
1887         chop = 1;
1888       }
1889       else if (ch == ' ' || ch == '-' || ch == '\0') {
1890         if (j >= oldc) {
1891           *charp = BLI_str_utf8_offset_from_column((*linep)->line, start);
1892           loop = 0;
1893           break;
1894         }
1895
1896         end = i + 1;
1897         endj = j + 1;
1898         chop = 0;
1899       }
1900       i += columns;
1901     }
1902   }
1903
1904   if (!sel) {
1905     txt_pop_sel(text);
1906   }
1907 }
1908
1909 static void txt_wrap_move_eol(SpaceText *st, ARegion *region, const bool sel)
1910 {
1911   Text *text = st->text;
1912   TextLine **linep;
1913   int *charp;
1914   int oldc, i, j, max, start, end, endj, chop, loop;
1915   char ch;
1916
1917   text_update_character_width(st);
1918
1919   if (sel) {
1920     linep = &text->sell;
1921     charp = &text->selc;
1922   }
1923   else {
1924     linep = &text->curl;
1925     charp = &text->curc;
1926   }
1927
1928   oldc = *charp;
1929
1930   max = wrap_width(st, region);
1931
1932   start = endj = 0;
1933   end = max;
1934   chop = loop = 1;
1935   *charp = 0;
1936
1937   for (i = 0, j = 0; loop; j += BLI_str_utf8_size_safe((*linep)->line + j)) {
1938     int chars;
1939     int columns = BLI_str_utf8_char_width_safe((*linep)->line + j); /* = 1 for tab */
1940
1941     /* Mimic replacement of tabs */
1942     ch = (*linep)->line[j];
1943     if (ch == '\t') {
1944       chars = st->tabnumber - i % st->tabnumber;
1945       ch = ' ';
1946     }
1947     else {
1948       chars = 1;
1949     }
1950
1951     while (chars--) {
1952       if (i + columns - start > max) {
1953         end = MIN2(end, i);
1954
1955         if (chop) {
1956           endj = BLI_str_prev_char_utf8((*linep)->line + j) - (*linep)->line;
1957         }
1958
1959         if (endj >= oldc) {
1960           if (ch == '\0') {
1961             *charp = (*linep)->len;
1962           }
1963           else {
1964             *charp = endj;
1965           }
1966           loop = 0;
1967           break;
1968         }
1969
1970         start = end;
1971         end += max;
1972         chop = 1;
1973       }
1974       else if (ch == '\0') {
1975         *charp = (*linep)->len;
1976         loop = 0;
1977         break;
1978       }
1979       else if (ch == ' ' || ch == '-') {
1980         end = i + 1;
1981         endj = j;
1982         chop = 0;
1983       }
1984       i += columns;
1985     }
1986   }
1987
1988   if (!sel) {
1989     txt_pop_sel(text);
1990   }
1991 }
1992
1993 static void txt_wrap_move_up(SpaceText *st, ARegion *region, const bool sel)
1994 {
1995   Text *text = st->text;
1996   TextLine **linep;
1997   int *charp;
1998   int offl, offc, col;
1999
2000   text_update_character_width(st);
2001
2002   if (sel) {
2003     linep = &text->sell;
2004     charp = &text->selc;
2005   }
2006   else {
2007     linep = &text->curl;
2008     charp = &text->curc;
2009   }
2010
2011   wrap_offset_in_line(st, region, *linep, *charp, &offl, &offc);
2012   col = text_get_char_pos(st, (*linep)->line, *charp) + offc;
2013   if (offl) {
2014     *charp = text_get_cursor_rel(st, region, *linep, offl - 1, col);
2015   }
2016   else {
2017     if ((*linep)->prev) {
2018       int visible_lines;
2019
2020       *linep = (*linep)->prev;
2021       visible_lines = text_get_visible_lines(st, region, (*linep)->line);
2022       *charp = text_get_cursor_rel(st, region, *linep, visible_lines - 1, col);
2023     }
2024     else {
2025       *charp = 0;
2026     }
2027   }
2028
2029   if (!sel) {
2030     txt_pop_sel(text);
2031   }
2032 }
2033
2034 static void txt_wrap_move_down(SpaceText *st, ARegion *region, const bool sel)
2035 {
2036   Text *text = st->text;
2037   TextLine **linep;
2038   int *charp;
2039   int offl, offc, col, visible_lines;
2040
2041   text_update_character_width(st);
2042
2043   if (sel) {
2044     linep = &text->sell;
2045     charp = &text->selc;
2046   }
2047   else {
2048     linep = &text->curl;
2049     charp = &text->curc;
2050   }
2051
2052   wrap_offset_in_line(st, region, *linep, *charp, &offl, &offc);
2053   col = text_get_char_pos(st, (*linep)->line, *charp) + offc;
2054   visible_lines = text_get_visible_lines(st, region, (*linep)->line);
2055   if (offl < visible_lines - 1) {
2056     *charp = text_get_cursor_rel(st, region, *linep, offl + 1, col);
2057   }
2058   else {
2059     if ((*linep)->next) {
2060       *linep = (*linep)->next;
2061       *charp = text_get_cursor_rel(st, region, *linep, 0, col);
2062     }
2063     else {
2064       *charp = (*linep)->len;
2065     }
2066   }
2067
2068   if (!sel) {
2069     txt_pop_sel(text);
2070   }
2071 }
2072
2073 /* Moves the cursor vertically by the specified number of lines.
2074  * If the destination line is shorter than the current cursor position, the
2075  * cursor will be positioned at the end of this line.
2076  *
2077  * This is to replace screen_skip for PageUp/Down operations.
2078  */
2079 static void cursor_skip(SpaceText *st, ARegion *region, Text *text, int lines, const bool sel)
2080 {
2081   TextLine **linep;
2082   int *charp;
2083
2084   if (sel) {
2085     linep = &text->sell;
2086     charp = &text->selc;
2087   }
2088   else {
2089     linep = &text->curl;
2090     charp = &text->curc;
2091   }
2092
2093   if (st && region && st->wordwrap) {
2094     int rell, relc;
2095
2096     /* find line and offsets inside it needed to set cursor position */
2097     if (cursor_skip_find_line(st, region, lines, linep, charp, &rell, &relc)) {
2098       *charp = text_get_cursor_rel(st, region, *linep, rell, relc);
2099     }
2100   }
2101   else {
2102     while (lines > 0 && (*linep)->next) {
2103       *linep = (*linep)->next;
2104       lines--;
2105     }
2106     while (lines < 0 && (*linep)->prev) {
2107       *linep = (*linep)->prev;
2108       lines++;
2109     }
2110   }
2111
2112   if (*charp > (*linep)->len) {
2113     *charp = (*linep)->len;
2114   }
2115
2116   if (!sel) {
2117     txt_pop_sel(text);
2118   }
2119 }
2120
2121 static int text_move_cursor(bContext *C, int type, bool select)
2122 {
2123   SpaceText *st = CTX_wm_space_text(C);
2124   Text *text = CTX_data_edit_text(C);
2125   ARegion *region = CTX_wm_region(C);
2126
2127   /* ensure we have the right region, it's optional */
2128   if (region && region->regiontype != RGN_TYPE_WINDOW) {
2129     region = NULL;
2130   }
2131
2132   switch (type) {
2133     case LINE_BEGIN:
2134       if (!select) {
2135         txt_sel_clear(text);
2136       }
2137       if (st && st->wordwrap && region) {
2138         txt_wrap_move_bol(st, region, select);
2139       }
2140       else {
2141         txt_move_bol(text, select);
2142       }
2143       break;
2144
2145     case LINE_END:
2146       if (!select) {
2147         txt_sel_clear(text);
2148       }
2149       if (st && st->wordwrap && region) {
2150         txt_wrap_move_eol(st, region, select);
2151       }
2152       else {
2153         txt_move_eol(text, select);
2154       }
2155       break;
2156
2157     case FILE_TOP:
2158       txt_move_bof(text, select);
2159       break;
2160
2161     case FILE_BOTTOM:
2162       txt_move_eof(text, select);
2163       break;
2164
2165     case PREV_WORD:
2166       if (txt_cursor_is_line_start(text)) {
2167         txt_move_left(text, select);
2168       }
2169       txt_jump_left(text, select, true);
2170       break;
2171
2172     case NEXT_WORD:
2173       if (txt_cursor_is_line_end(text)) {
2174         txt_move_right(text, select);
2175       }
2176       txt_jump_right(text, select, true);
2177       break;
2178
2179     case PREV_CHAR:
2180       if (txt_has_sel(text) && !select) {
2181         txt_order_cursors(text, false);
2182         txt_pop_sel(text);
2183       }
2184       else {
2185         txt_move_left(text, select);
2186       }
2187       break;
2188
2189     case NEXT_CHAR:
2190       if (txt_has_sel(text) && !select) {
2191         txt_order_cursors(text, true);
2192         txt_pop_sel(text);
2193       }
2194       else {
2195         txt_move_right(text, select);
2196       }
2197       break;
2198
2199     case PREV_LINE:
2200       if (st && st->wordwrap && region) {
2201         txt_wrap_move_up(st, region, select);
2202       }
2203       else {
2204         txt_move_up(text, select);
2205       }
2206       break;
2207
2208     case NEXT_LINE:
2209       if (st && st->wordwrap && region) {
2210         txt_wrap_move_down(st, region, select);
2211       }
2212       else {
2213         txt_move_down(text, select);
2214       }
2215       break;
2216
2217     case PREV_PAGE:
2218       if (st) {
2219         cursor_skip(st, region, st->text, -st->runtime.viewlines, select);
2220       }
2221       else {
2222         cursor_skip(NULL, NULL, text, -10, select);
2223       }
2224       break;
2225
2226     case NEXT_PAGE:
2227       if (st) {
2228         cursor_skip(st, region, st->text, st->runtime.viewlines, select);
2229       }
2230       else {
2231         cursor_skip(NULL, NULL, text, 10, select);
2232       }
2233       break;
2234   }
2235
2236   text_update_cursor_moved(C);
2237   WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
2238
2239   return OPERATOR_FINISHED;
2240 }
2241
2242 static int text_move_exec(bContext *C, wmOperator *op)
2243 {
2244   int type = RNA_enum_get(op->ptr, "type");
2245
2246   return text_move_cursor(C, type, 0);
2247 }
2248
2249 void TEXT_OT_move(wmOperatorType *ot)
2250 {
2251   /* identifiers */
2252   ot->name = "Move Cursor";
2253   ot->idname = "TEXT_OT_move";
2254   ot->description = "Move cursor to position type";
2255
2256   /* api callbacks */
2257   ot->exec = text_move_exec;
2258   ot->poll = text_edit_poll;
2259
2260   /* properties */
2261   RNA_def_enum(ot->srna, "type", move_type_items, LINE_BEGIN, "Type", "Where to move cursor to");
2262 }
2263
2264 /** \} */
2265
2266 /* -------------------------------------------------------------------- */
2267 /** \name Move Select Operator
2268  * \{ */
2269
2270 static int text_move_select_exec(bContext *C, wmOperator *op)
2271 {
2272   int type = RNA_enum_get(op->ptr, "type");
2273
2274   return text_move_cursor(C, type, 1);
2275 }
2276
2277 void TEXT_OT_move_select(wmOperatorType *ot)
2278 {
2279   /* identifiers */
2280   ot->name = "Move Select";
2281   ot->idname = "TEXT_OT_move_select";
2282   ot->description = "Move the cursor while selecting";
2283
2284   /* api callbacks */
2285   ot->exec = text_move_select_exec;
2286   ot->poll = text_space_edit_poll;
2287
2288   /* properties */
2289   RNA_def_enum(ot->srna,
2290                "type",
2291                move_type_items,
2292                LINE_BEGIN,
2293                "Type",
2294                "Where to move cursor to, to make a selection");
2295 }
2296
2297 /** \} */
2298
2299 /* -------------------------------------------------------------------- */
2300 /** \name Jump Operator
2301  * \{ */
2302
2303 static int text_jump_exec(bContext *C, wmOperator *op)
2304 {
2305   Text *text = CTX_data_edit_text(C);
2306   int line = RNA_int_get(op->ptr, "line");
2307   short nlines = txt_get_span(text->lines.first, text->lines.last) + 1;
2308
2309   if (line < 1) {
2310     txt_move_toline(text, 1, 0);
2311   }
2312   else if (line > nlines) {
2313     txt_move_toline(text, nlines - 1, 0);
2314   }
2315   else {
2316     txt_move_toline(text, line - 1, 0);
2317   }
2318
2319   text_update_cursor_moved(C);
2320   WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
2321
2322   return OPERATOR_FINISHED;
2323 }
2324
2325 static int text_jump_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
2326 {
2327   return WM_operator_props_dialog_popup(C, op, 200);
2328 }
2329
2330 void TEXT_OT_jump(wmOperatorType *ot)
2331 {
2332   PropertyRNA *prop;
2333
2334   /* identifiers */
2335   ot->name = "Jump";
2336   ot->idname = "TEXT_OT_jump";
2337   ot->description = "Jump cursor to line";
2338
2339   /* api callbacks */
2340   ot->invoke = text_jump_invoke;
2341   ot->exec = text_jump_exec;
2342   ot->poll = text_edit_poll;
2343
2344   /* properties */
2345   prop = RNA_def_int(ot->srna, "line", 1, 1, INT_MAX, "Line", "Line number to jump to", 1, 10000);
2346   RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_TEXT);
2347 }
2348
2349 /** \} */
2350
2351 /* -------------------------------------------------------------------- */
2352 /** \name Delete Operator
2353  * \{ */
2354
2355 static const EnumPropertyItem delete_type_items[] = {
2356     {DEL_NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
2357     {DEL_PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
2358     {DEL_NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
2359     {DEL_PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
2360     {0, NULL, 0, NULL, NULL},
2361 };
2362
2363 static int text_delete_exec(bContext *C, wmOperator *op)
2364 {
2365   SpaceText *st = CTX_wm_space_text(C);
2366   Text *text = CTX_data_edit_text(C);
2367   int type = RNA_enum_get(op->ptr, "type");
2368
2369   text_drawcache_tag_update(st, 0);
2370
2371   /* behavior could be changed here,
2372    * but for now just don't jump words when we have a selection */
2373   if (txt_has_sel(text)) {
2374     if (type == DEL_PREV_WORD) {
2375       type = DEL_PREV_CHAR;
2376     }
2377     else if (type == DEL_NEXT_WORD) {
2378       type = DEL_NEXT_CHAR;
2379     }
2380   }
2381
2382   ED_text_undo_push_init(C);
2383
2384   if (type == DEL_PREV_WORD) {
2385     if (txt_cursor_is_line_start(text)) {
2386       txt_backspace_char(text);
2387     }
2388     txt_backspace_word(text);
2389   }
2390   else if (type == DEL_PREV_CHAR) {
2391
2392     if (text->flags & TXT_TABSTOSPACES) {
2393       if (!txt_has_sel(text) && !txt_cursor_is_line_start(text)) {
2394         int tabsize = 0;
2395         tabsize = txt_calc_tab_left(text->curl, text->curc);
2396         if (tabsize) {
2397           text->sell = text->curl;
2398           text->selc = text->curc - tabsize;
2399           txt_order_cursors(text, false);
2400         }
2401       }
2402     }
2403
2404     txt_backspace_char(text);
2405   }
2406   else if (type == DEL_NEXT_WORD) {
2407     if (txt_cursor_is_line_end(text)) {
2408       txt_delete_char(text);
2409     }
2410     txt_delete_word(text);
2411   }
2412   else if (type == DEL_NEXT_CHAR) {
2413
2414     if (text->flags & TXT_TABSTOSPACES) {
2415       if (!txt_has_sel(text) && !txt_cursor_is_line_end(text)) {
2416         int tabsize = 0;
2417         tabsize = txt_calc_tab_right(text->curl, text->curc);
2418         if (tabsize) {
2419           text->sell = text->curl;
2420           text->selc = text->curc + tabsize;
2421           txt_order_cursors(text, true);
2422         }
2423       }
2424     }
2425
2426     txt_delete_char(text);
2427   }
2428
2429   text_update_line_edited(text->curl);
2430
2431   text_update_cursor_moved(C);
2432   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
2433
2434   /* run the script while editing, evil but useful */
2435   if (st->live_edit) {
2436     text_run_script(C, NULL);
2437   }
2438
2439   return OPERATOR_FINISHED;
2440 }
2441
2442 void TEXT_OT_delete(wmOperatorType *ot)
2443 {
2444   /* identifiers */
2445   ot->name = "Delete";
2446   ot->idname = "TEXT_OT_delete";
2447   ot->description = "Delete text by cursor position";
2448
2449   /* api callbacks */
2450   ot->exec = text_delete_exec;
2451   ot->poll = text_edit_poll;
2452
2453   /* flags */
2454   ot->flag = OPTYPE_UNDO;
2455
2456   /* properties */
2457   PropertyRNA *prop;
2458   prop = RNA_def_enum(ot->srna,
2459                       "type",
2460                       delete_type_items,
2461                       DEL_NEXT_CHAR,
2462                       "Type",
2463                       "Which part of the text to delete");
2464   RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
2465 }
2466
2467 /** \} */
2468
2469 /* -------------------------------------------------------------------- */
2470 /** \name Toggle Overwrite Operator
2471  * \{ */
2472
2473 static int text_toggle_overwrite_exec(bContext *C, wmOperator *UNUSED(op))
2474 {
2475   SpaceText *st = CTX_wm_space_text(C);
2476
2477   st->overwrite = !st->overwrite;
2478
2479   WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
2480
2481   return OPERATOR_FINISHED;
2482 }
2483
2484 void TEXT_OT_overwrite_toggle(wmOperatorType *ot)
2485 {
2486   /* identifiers */
2487   ot->name = "Toggle Overwrite";
2488   ot->idname = "TEXT_OT_overwrite_toggle";
2489   ot->description = "Toggle overwrite while typing";
2490
2491   /* api callbacks */
2492   ot->exec = text_toggle_overwrite_exec;
2493   ot->poll = text_space_edit_poll;
2494 }
2495
2496 /** \} */
2497
2498 /* -------------------------------------------------------------------- */
2499 /** \name Scroll Operator
2500  * \{ */
2501
2502 static void txt_screen_clamp(SpaceText *st, ARegion *region)
2503 {
2504   if (st->top <= 0) {
2505     st->top = 0;
2506   }
2507   else {
2508     int last;
2509     last = text_get_total_lines(st, region);
2510     last = last - (st->runtime.viewlines / 2);
2511     if (last > 0 && st->top > last) {
2512       st->top = last;
2513     }
2514   }
2515 }
2516
2517 /* Moves the view vertically by the specified number of lines */
2518 static void txt_screen_skip(SpaceText *st, ARegion *region, int lines)
2519 {
2520   st->top += lines;
2521   txt_screen_clamp(st, region);
2522 }
2523
2524 /* quick enum for tsc->zone (scroller handles) */
2525 enum eScrollZone {
2526   SCROLLHANDLE_INVALID_OUTSIDE = -1,
2527   SCROLLHANDLE_BAR,
2528   SCROLLHANDLE_MIN_OUTSIDE,
2529   SCROLLHANDLE_MAX_OUTSIDE,
2530 };
2531
2532 typedef struct TextScroll {
2533   int mval_prev[2];
2534   int mval_delta[2];
2535
2536   bool is_first;
2537   bool is_scrollbar;
2538
2539   enum eScrollZone zone;
2540
2541   /* Store the state of the display, cache some constant vars. */
2542   struct {
2543     int ofs_init[2];
2544     int ofs_max[2];
2545     int size_px[2];
2546   } state;
2547   int ofs_delta[2];
2548   int ofs_delta_px[2];
2549 } TextScroll;
2550
2551 static void text_scroll_state_init(TextScroll *tsc, SpaceText *st, ARegion *region)
2552 {
2553   tsc->state.ofs_init[0] = st->left;
2554   tsc->state.ofs_init[1] = st->top;
2555
2556   tsc->state.ofs_max[0] = INT_MAX;
2557   tsc->state.ofs_max[1] = max_ii(0,
2558                                  text_get_total_lines(st, region) - (st->runtime.viewlines / 2));
2559
2560   tsc->state.size_px[0] = st->runtime.cwidth_px;
2561   tsc->state.size_px[1] = TXT_LINE_HEIGHT(st);
2562 }
2563
2564 static bool text_scroll_poll(bContext *C)
2565 {
2566   /* it should be possible to still scroll linked texts to read them,
2567    * even if they can't be edited... */
2568   return CTX_data_edit_text(C) != NULL;
2569 }
2570
2571 static int text_scroll_exec(bContext *C, wmOperator *op)
2572 {
2573   SpaceText *st = CTX_wm_space_text(C);
2574   ARegion *region = CTX_wm_region(C);
2575
2576   int lines = RNA_int_get(op->ptr, "lines");
2577
2578   if (lines == 0) {
2579     return OPERATOR_CANCELLED;
2580   }
2581
2582   txt_screen_skip(st, region, lines * U.wheellinescroll);
2583
2584   ED_area_tag_redraw(CTX_wm_area(C));
2585
2586   return OPERATOR_FINISHED;
2587 }
2588
2589 static void text_scroll_apply(bContext *C, wmOperator *op, const wmEvent *event)
2590 {
2591   SpaceText *st = CTX_wm_space_text(C);
2592   TextScroll *tsc = op->customdata;
2593   int mval[2] = {event->x, event->y};
2594
2595   text_update_character_width(st);
2596
2597   /* compute mouse move distance */
2598   if (tsc->is_first) {
2599     tsc->mval_prev[0] = mval[0];
2600     tsc->mval_prev[1] = mval[1];
2601     tsc->is_first = false;
2602   }
2603
2604   if (event->type != MOUSEPAN) {
2605     tsc->mval_delta[0] = mval[0] - tsc->mval_prev[0];
2606     tsc->mval_delta[1] = mval[1] - tsc->mval_prev[1];
2607   }
2608
2609   /* accumulate scroll, in float values for events that give less than one
2610    * line offset but taken together should still scroll */
2611   if (!tsc->is_scrollbar) {
2612     tsc->ofs_delta_px[0] -= tsc->mval_delta[0];
2613     tsc->ofs_delta_px[1] += tsc->mval_delta[1];
2614   }
2615   else {
2616     tsc->ofs_delta_px[1] -= (tsc->mval_delta[1] * st->runtime.scroll_px_per_line) *
2617                             tsc->state.size_px[1];
2618   }
2619
2620   for (int i = 0; i < 2; i += 1) {
2621     int lines_from_pixels = tsc->ofs_delta_px[i] / tsc->state.size_px[i];
2622     tsc->ofs_delta[i] += lines_from_pixels;
2623     tsc->ofs_delta_px[i] -= lines_from_pixels * tsc->state.size_px[i];
2624   }
2625
2626   /* The final values need to be calculated from the inputs,
2627    * so clamping and ensuring an unsigned pixel offset doesn't conflict with
2628    * updating the cursor mval_delta. */
2629   int scroll_ofs_new[2] = {
2630       tsc->state.ofs_init[0] + tsc->ofs_delta[0],
2631       tsc->state.ofs_init[1] + tsc->ofs_delta[1],
2632   };
2633   int scroll_ofs_px_new[2] = {
2634       tsc->ofs_delta_px[0],
2635       tsc->ofs_delta_px[1],
2636   };
2637
2638   for (int i = 0; i < 2; i += 1) {
2639     /* Ensure always unsigned (adjusting line/column accordingly). */
2640     while (scroll_ofs_px_new[i] < 0) {
2641       scroll_ofs_px_new[i] += tsc->state.size_px[i];
2642       scroll_ofs_new[i] -= 1;
2643     }
2644
2645     /* Clamp within usable region. */
2646     if (scroll_ofs_new[i] < 0) {
2647       scroll_ofs_new[i] = 0;
2648       scroll_ofs_px_new[i] = 0;
2649     }
2650     else if (scroll_ofs_new[i] >= tsc->state.ofs_max[i]) {
2651       scroll_ofs_new[i] = tsc->state.ofs_max[i];
2652       scroll_ofs_px_new[i] = 0;
2653     }
2654   }
2655
2656   /* Override for word-wrap. */
2657   if (st->wordwrap) {
2658     scroll_ofs_new[0] = 0;
2659     scroll_ofs_px_new[0] = 0;
2660   }
2661
2662   /* Apply to the screen. */
2663   if (scroll_ofs_new[0] != st->left || scroll_ofs_new[1] != st->top ||
2664       /* Horizontal sub-pixel offset currently isn't used. */
2665       /* scroll_ofs_px_new[0] != st->scroll_ofs_px[0] || */
2666       scroll_ofs_px_new[1] != st->runtime.scroll_ofs_px[1]) {
2667
2668     st->left = scroll_ofs_new[0];
2669     st->top = scroll_ofs_new[1];
2670     st->runtime.scroll_ofs_px[0] = scroll_ofs_px_new[0];
2671     st->runtime.scroll_ofs_px[1] = scroll_ofs_px_new[1];
2672     ED_area_tag_redraw(CTX_wm_area(C));
2673   }
2674
2675   tsc->mval_prev[0] = mval[0];
2676   tsc->mval_prev[1] = mval[1];
2677 }
2678
2679 static void scroll_exit(bContext *C, wmOperator *op)
2680 {
2681   SpaceText *st = CTX_wm_space_text(C);
2682   TextScroll *tsc = op->customdata;
2683
2684   st->flags &= ~ST_SCROLL_SELECT;
2685
2686   if (st->runtime.scroll_ofs_px[1] > tsc->state.size_px[1] / 2) {
2687     st->top += 1;
2688   }
2689
2690   st->runtime.scroll_ofs_px[0] = 0;
2691   st->runtime.scroll_ofs_px[1] = 0;
2692   ED_area_tag_redraw(CTX_wm_area(C));
2693
2694   MEM_freeN(op->customdata);
2695 }
2696
2697 static int text_scroll_modal(bContext *C, wmOperator *op, const wmEvent *event)
2698 {
2699   TextScroll *tsc = op->customdata;
2700   SpaceText *st = CTX_wm_space_text(C);
2701   ARegion *region = CTX_wm_region(C);
2702
2703   switch (event->type) {
2704     case MOUSEMOVE:
2705       if (tsc->zone == SCROLLHANDLE_BAR) {
2706         text_scroll_apply(C, op, event);
2707       }
2708       break;
2709     case LEFTMOUSE:
2710     case RIGHTMOUSE:
2711     case MIDDLEMOUSE:
2712       if (event->val == KM_RELEASE) {
2713         if (ELEM(tsc->zone, SCROLLHANDLE_MIN_OUTSIDE, SCROLLHANDLE_MAX_OUTSIDE)) {
2714           txt_screen_skip(st,
2715                           region,
2716                           st->runtime.viewlines *
2717                               (tsc->zone == SCROLLHANDLE_MIN_OUTSIDE ? 1 : -1));
2718
2719           ED_area_tag_redraw(CTX_wm_area(C));
2720         }
2721         scroll_exit(C, op);
2722         return OPERATOR_FINISHED;
2723       }
2724   }
2725
2726   return OPERATOR_RUNNING_MODAL;
2727 }
2728
2729 static void text_scroll_cancel(bContext *C, wmOperator *op)
2730 {
2731   scroll_exit(C, op);
2732 }
2733
2734 static int text_scroll_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2735 {
2736   SpaceText *st = CTX_wm_space_text(C);
2737   ARegion *region = CTX_wm_region(C);
2738
2739   TextScroll *tsc;
2740
2741   if (RNA_struct_property_is_set(op->ptr, "lines")) {
2742     return text_scroll_exec(C, op);
2743   }
2744
2745   tsc = MEM_callocN(sizeof(TextScroll), "TextScroll");
2746   tsc->is_first = true;
2747   tsc->zone = SCROLLHANDLE_BAR;
2748
2749   text_scroll_state_init(tsc, st, region);
2750
2751   op->customdata = tsc;
2752
2753   st->flags |= ST_SCROLL_SELECT;
2754
2755   if (event->type == MOUSEPAN) {
2756     text_update_character_width(st);
2757
2758     tsc->mval_prev[0] = event->x;
2759     tsc->mval_prev[1] = event->y;
2760     /* Sensitivity of scroll set to 4pix per line/char */
2761     tsc->mval_delta[0] = (event->x - event->prevx) * st->runtime.cwidth_px / 4;
2762     tsc->mval_delta[1] = (event->y - event->prevy) * st->runtime.lheight_px / 4;
2763     tsc->is_first = false;
2764     tsc->is_scrollbar = false;
2765     text_scroll_apply(C, op, event);
2766     scroll_exit(C, op);
2767     return OPERATOR_FINISHED;
2768   }
2769
2770   WM_event_add_modal_handler(C, op);
2771
2772   return OPERATOR_RUNNING_MODAL;
2773 }
2774
2775 void TEXT_OT_scroll(wmOperatorType *ot)
2776 {
2777   /* identifiers */
2778   ot->name = "Scroll";
2779   /* don't really see the difference between this and
2780    * scroll_bar. Both do basically the same thing (aside
2781    * from keymaps).*/
2782   ot->idname = "TEXT_OT_scroll";
2783
2784   /* api callbacks */
2785   ot->exec = text_scroll_exec;
2786   ot->invoke = text_scroll_invoke;
2787   ot->modal = text_scroll_modal;
2788   ot->cancel = text_scroll_cancel;
2789   ot->poll = text_scroll_poll;
2790
2791   /* flags */
2792   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY | OPTYPE_INTERNAL;
2793
2794   /* properties */
2795   RNA_def_int(
2796       ot->srna, "lines", 1, INT_MIN, INT_MAX, "Lines", "Number of lines to scroll", -100, 100);
2797 }
2798
2799 /** \} */
2800
2801 /* -------------------------------------------------------------------- */
2802 /** \name Scroll Bar Operator
2803  * \{ */
2804
2805 static bool text_region_scroll_poll(bContext *C)
2806 {
2807   /* same as text_region_edit_poll except it works on libdata too */
2808   SpaceText *st = CTX_wm_space_text(C);
2809   Text *text = CTX_data_edit_text(C);
2810   ARegion *region = CTX_wm_region(C);
2811
2812   if (!st || !text) {
2813     return 0;
2814   }
2815
2816   if (!region || region->regiontype != RGN_TYPE_WINDOW) {
2817     return 0;
2818   }
2819
2820   return 1;
2821 }
2822
2823 static int text_scroll_bar_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2824 {
2825   SpaceText *st = CTX_wm_space_text(C);
2826   ARegion *region = CTX_wm_region(C);
2827   TextScroll *tsc;
2828   const int *mval = event->mval;
2829   enum eScrollZone zone = SCROLLHANDLE_INVALID_OUTSIDE;
2830
2831   if (RNA_struct_property_is_set(op->ptr, "lines")) {
2832     return text_scroll_exec(C, op);
2833   }
2834
2835   /* verify we are in the right zone */
2836   if (mval[0] > st->runtime.scroll_region_handle.xmin &&
2837       mval[0] < st->runtime.scroll_region_handle.xmax) {
2838     if (mval[1] >= st->runtime.scroll_region_handle.ymin &&
2839         mval[1] <= st->runtime.scroll_region_handle.ymax) {
2840       /* mouse inside scroll handle */
2841       zone = SCROLLHANDLE_BAR;
2842     }
2843     else if (mval[1] > TXT_SCROLL_SPACE && mval[1] < region->winy - TXT_SCROLL_SPACE) {
2844       if (mval[1] < st->runtime.scroll_region_handle.ymin) {
2845         zone = SCROLLHANDLE_MIN_OUTSIDE;
2846       }
2847       else {
2848         zone = SCROLLHANDLE_MAX_OUTSIDE;
2849       }
2850     }
2851   }
2852
2853   if (zone == SCROLLHANDLE_INVALID_OUTSIDE) {
2854     /* we are outside slider - nothing to do */
2855     return OPERATOR_PASS_THROUGH;
2856   }
2857
2858   tsc = MEM_callocN(sizeof(TextScroll), "TextScroll");
2859   tsc->is_first = true;
2860   tsc->is_scrollbar = true;
2861   tsc->zone = zone;
2862   op->customdata = tsc;
2863   st->flags |= ST_SCROLL_SELECT;
2864
2865   text_scroll_state_init(tsc, st, region);
2866
2867   /* jump scroll, works in v2d but needs to be added here too :S */
2868   if (event->type == MIDDLEMOUSE) {
2869     tsc->mval_prev[0] = region->winrct.xmin + BLI_rcti_cent_x(&st->runtime.scroll_region_handle);
2870     tsc->mval_prev[1] = region->winrct.ymin + BLI_rcti_cent_y(&st->runtime.scroll_region_handle);
2871
2872     tsc->is_first = false;
2873     tsc->zone = SCROLLHANDLE_BAR;
2874     text_scroll_apply(C, op, event);
2875   }
2876
2877   WM_event_add_modal_handler(C, op);
2878
2879   return OPERATOR_RUNNING_MODAL;
2880 }
2881
2882 void TEXT_OT_scroll_bar(wmOperatorType *ot)
2883 {
2884   /* identifiers */
2885   ot->name = "Scrollbar";
2886   /* don't really see the difference between this and
2887    * scroll. Both do basically the same thing (aside
2888    * from keymaps).*/
2889   ot->idname = "TEXT_OT_scroll_bar";
2890
2891   /* api callbacks */
2892   ot->invoke = text_scroll_bar_invoke;
2893   ot->modal = text_scroll_modal;
2894   ot->cancel = text_scroll_cancel;
2895   ot->poll = text_region_scroll_poll;
2896
2897   /* flags */
2898   ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
2899
2900   /* properties */
2901   RNA_def_int(
2902       ot->srna, "lines", 1, INT_MIN, INT_MAX, "Lines", "Number of lines to scroll", -100, 100);
2903 }
2904
2905 /** \} */
2906
2907 /* -------------------------------------------------------------------- */
2908 /** \name Set Selection Operator
2909  * \{ */
2910
2911 typedef struct SetSelection {
2912   int selc, sell;
2913   short mval_prev[2];
2914   wmTimer *timer; /* needed for scrolling when mouse at region bounds */
2915 } SetSelection;
2916
2917 static int flatten_width(SpaceText *st, const char *str)
2918 {
2919   int i, total = 0;
2920
2921   for (i = 0; str[i]; i += BLI_str_utf8_size_safe(str + i)) {
2922     if (str[i] == '\t') {
2923       total += st->tabnumber - total % st->tabnumber;
2924     }
2925     else {
2926       total += BLI_str_utf8_char_width_safe(str + i);
2927     }
2928   }
2929
2930   return total;
2931 }
2932
2933 static int flatten_column_to_offset(SpaceText *st, const char *str, int index)
2934 {
2935   int i = 0, j = 0, col;
2936
2937   while (*(str + j)) {
2938     if (str[j] == '\t') {
2939       col = st->tabnumber - i % st->tabnumber;
2940     }
2941     else {
2942       col = BLI_str_utf8_char_width_safe(str + j);
2943     }
2944
2945     if (i + col > index) {
2946       break;
2947     }
2948
2949     i += col;
2950     j += BLI_str_utf8_size_safe(str + j);
2951   }
2952
2953   return j;
2954 }
2955
2956 static TextLine *get_line_pos_wrapped(SpaceText *st, ARegion *region, int *y)
2957 {
2958   TextLine *linep = st->text->lines.first;
2959   int i, lines;
2960
2961   if (*y < -st->top) {
2962     return NULL; /* We are beyond the first line... */
2963   }
2964
2965   for (i = -st->top; i <= *y && linep; linep = linep->next, i += lines) {
2966     lines = text_get_visible_lines(st, region, linep->line);
2967
2968     if (i + lines > *y) {
2969       /* We found the line matching given vertical 'coordinate',
2970        * now set y relative to this line's start. */
2971       *y -= i;
2972       break;
2973     }
2974   }
2975   return linep;
2976 }
2977
2978 static void text_cursor_set_to_pos_wrapped(
2979     SpaceText *st, ARegion *region, int x, int y, const bool sel)
2980 {
2981   Text *text = st->text;
2982   int max = wrap_width(st, region); /* column */
2983   int charp = -1;                   /* mem */
2984   bool found = false;               /* flags */
2985
2986   /* Point to line matching given y position, if any. */
2987   TextLine *linep = get_line_pos_wrapped(st, region, &y);
2988
2989   if (linep) {
2990     int i = 0, start = 0, end = max; /* column */
2991     int j, curs = 0, endj = 0;       /* mem */
2992     bool chop = true;                /* flags */
2993     char ch;
2994
2995     for (j = 0; !found && ((ch = linep->line[j]) != '\0');
2996          j += BLI_str_utf8_size_safe(linep->line + j)) {
2997       int chars;
2998       int columns = BLI_str_utf8_char_width_safe(linep->line + j); /* = 1 for tab */
2999
3000       /* Mimic replacement of tabs */
3001       if (ch == '\t') {
3002         chars = st->tabnumber - i % st->tabnumber;
3003         ch = ' ';
3004       }
3005       else {
3006         chars = 1;
3007       }
3008
3009       while (chars--) {
3010         /* Gone too far, go back to last wrap point */
3011         if (y < 0) {
3012           charp = endj;
3013           y = 0;
3014           found = true;
3015           break;
3016           /* Exactly at the cursor */
3017         }
3018         else if (y == 0 && i - start <= x && i + columns - start > x) {
3019           /* current position could be wrapped to next line */
3020           /* this should be checked when end of current line would be reached */
3021           charp = curs = j;
3022           found = true;
3023           /* Prepare curs for next wrap */
3024         }
3025         else if (i - end <= x && i + columns - end > x) {
3026           curs = j;
3027         }
3028         if (i + columns - start > max) {
3029           end = MIN2(end, i);
3030
3031           if (found) {
3032             /* exact cursor position was found, check if it's still on needed line
3033              * (hasn't been wrapped) */
3034             if (charp > endj && !chop && ch != '\0') {
3035               charp = endj;
3036             }
3037             break;
3038           }
3039
3040           if (chop) {
3041             endj = j;
3042           }
3043           start = end;
3044           end += max;
3045
3046           if (j < linep->len) {
3047             y--;
3048           }
3049
3050           chop = true;
3051           if (y == 0 && i + columns - start > x) {
3052             charp = curs;
3053             found = true;
3054             break;
3055           }
3056         }
3057         else if (ch == ' ' || ch == '-' || ch == '\0') {
3058           if (found) {
3059             break;
3060           }
3061
3062           if (y == 0 && i + columns - start > x) {
3063             charp = curs;
3064             found = true;
3065             break;
3066           }
3067           end = i + 1;
3068           endj = j;
3069           chop = false;
3070         }
3071         i += columns;
3072       }
3073     }
3074
3075     BLI_assert(y == 0);
3076
3077     if (!found) {
3078       /* On correct line but didn't meet cursor, must be at end */
3079       charp = linep->len;
3080     }
3081   }
3082   else if (y < 0) { /* Before start of text. */
3083     linep = st->text->lines.first;
3084     charp = 0;
3085   }
3086   else { /* Beyond end of text */
3087     linep = st->text->lines.last;
3088     charp = linep->len;
3089   }
3090
3091   BLI_assert(linep && charp != -1);
3092
3093   if (sel) {
3094     text->sell = linep;
3095     text->selc = charp;
3096   }
3097   else {
3098     text->curl = linep;
3099     text->curc = charp;
3100   }
3101 }
3102
3103 static void text_cursor_set_to_pos(SpaceText *st, ARegion *region, int x, int y, const bool sel)
3104 {
3105   Text *text = st->text;
3106   text_update_character_width(st);
3107   y = (region->winy - 2 - y) / TXT_LINE_HEIGHT(st);
3108
3109   x -= TXT_BODY_LEFT(st);
3110   if (x < 0) {
3111     x = 0;
3112   }
3113   x = text_pixel_x_to_column(st, x) + st->left;
3114
3115   if (st->wordwrap) {
3116     text_cursor_set_to_pos_wrapped(st, region, x, y, sel);
3117   }
3118   else {
3119     TextLine **linep;
3120     int *charp;
3121     int w;
3122
3123     if (sel) {
3124       linep = &text->sell;
3125       charp = &text->selc;
3126     }
3127     else {
3128       linep = &text->curl;
3129       charp = &text->curc;
3130     }
3131
3132     y -= txt_get_span(text->lines.first, *linep) - st->top;
3133
3134     if (y > 0) {
3135       while (y-- != 0) {
3136         if ((*linep)->next) {
3137           *linep = (*linep)->next;
3138         }
3139       }
3140     }
3141     else if (y < 0) {
3142       while (y++ != 0) {
3143         if ((*linep)->prev) {
3144           *linep = (*linep)->prev;
3145         }
3146       }
3147     }
3148
3149     w = flatten_width(st, (*linep)->line);
3150     if (x < w) {
3151       *charp = flatten_column_to_offset(st, (*linep)->line, x);
3152     }
3153     else {
3154       *charp = (*linep)->len;
3155     }
3156   }
3157   if (!sel) {
3158     txt_pop_sel(text);
3159   }
3160 }
3161
3162 static void text_cursor_timer_ensure(bContext *C, SetSelection *ssel)
3163 {
3164   if (ssel->timer == NULL) {
3165     wmWindowManager *wm = CTX_wm_manager(C);
3166     wmWindow *win = CTX_wm_window(C);
3167
3168     ssel->timer = WM_event_add_timer(wm, win, TIMER, 0.02f);
3169   }
3170 }
3171
3172 static void text_cursor_timer_remove(bContext *C, SetSelection *ssel)
3173 {
3174   if (ssel->timer) {
3175     wmWindowManager *wm = CTX_wm_manager(C);
3176     wmWindow *win = CTX_wm_window(C);
3177
3178     WM_event_remove_timer(wm, win, ssel->timer);
3179   }
3180   ssel->timer = NULL;
3181 }
3182
3183 static void text_cursor_set_apply(bContext *C, wmOperator *op, const wmEvent *event)
3184 {
3185   SpaceText *st = CTX_wm_space_text(C);
3186   ARegion *region = CTX_wm_region(C);
3187   SetSelection *ssel = op->customdata;
3188
3189   if (event->mval[1] < 0 || event->mval[1] > region->winy) {
3190     text_cursor_timer_ensure(C, ssel);
3191
3192     if (event->type == TIMER) {
3193       text_cursor_set_to_pos(st, region, event->mval[0], event->mval[1], 1);
3194       text_scroll_to_cursor(st, region, false);
3195       WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
3196     }
3197   }
3198   else if (!st->wordwrap && (event->mval[0] < 0 || event->mval[0] > region->winx)) {
3199     text_cursor_timer_ensure(C, ssel);
3200
3201     if (event->type == TIMER) {
3202       text_cursor_set_to_pos(
3203           st, region, CLAMPIS(event->mval[0], 0, region->winx), event->mval[1], 1);
3204       text_scroll_to_cursor(st, region, false);
3205       WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
3206     }
3207   }
3208   else {
3209     text_cursor_timer_remove(C, ssel);
3210
3211     if (event->type != TIMER) {
3212       text_cursor_set_to_pos(st, region, event->mval[0], event->mval[1], 1);
3213       text_scroll_to_cursor(st, region, false);
3214       WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
3215
3216       ssel->mval_prev[0] = event->mval[0];
3217       ssel->mval_prev[1] = event->mval[1];
3218     }
3219   }
3220 }
3221
3222 static void text_cursor_set_exit(bContext *C, wmOperator *op)
3223 {
3224   SpaceText *st = CTX_wm_space_text(C);
3225   Text *text = st->text;
3226   SetSelection *ssel = op->customdata;
3227   char *buffer;
3228
3229   if (txt_has_sel(text)) {
3230     buffer = txt_sel_to_buf(text, NULL);
3231     WM_clipboard_text_set(buffer, 1);
3232     MEM_freeN(buffer);
3233   }
3234
3235   text_update_cursor_moved(C);
3236   WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
3237
3238   text_cursor_timer_remove(C, ssel);
3239   MEM_freeN(ssel);
3240 }
3241
3242 static int text_selection_set_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3243 {
3244   SpaceText *st = CTX_wm_space_text(C);
3245   SetSelection *ssel;
3246
3247   if (event->mval[0] >= st->runtime.scroll_region_handle.xmin) {
3248     return OPERATOR_PASS_THROUGH;
3249   }
3250
3251   op->customdata = MEM_callocN(sizeof(SetSelection), "SetCursor");
3252   ssel = op->customdata;
3253
3254   ssel->mval_prev[0] = event->mval[0];
3255   ssel->mval_prev[1] = event->mval[1];
3256
3257   ssel->sell = txt_get_span(st->text->lines.first, st->text->sell);
3258   ssel->selc = st->text->selc;
3259
3260   WM_event_add_modal_handler(C, op);
3261
3262   text_cursor_set_apply(C, op, event);
3263
3264   return OPERATOR_RUNNING_MODAL;
3265 }
3266
3267 static int text_selection_set_modal(bContext *C, wmOperator *op, const wmEvent *event)
3268 {
3269   switch (event->type) {
3270     case LEFTMOUSE:
3271     case MIDDLEMOUSE:
3272     case RIGHTMOUSE:
3273       text_cursor_set_exit(C, op);
3274       return OPERATOR_FINISHED;
3275     case TIMER:
3276     case MOUSEMOVE:
3277       text_cursor_set_apply(C, op, event);
3278       break;
3279   }
3280
3281   return OPERATOR_RUNNING_MODAL;
3282 }
3283
3284 static void text_selection_set_cancel(bContext *C, wmOperator *op)
3285 {
3286   text_cursor_set_exit(C, op);
3287 }
3288
3289 void TEXT_OT_selection_set(wmOperatorType *ot)
3290 {
3291   /* identifiers */
3292   ot->name = "Set Selection";
3293   ot->idname = "TEXT_OT_selection_set";
3294   ot->description = "Set cursor selection";
3295
3296   /* api callbacks */
3297   ot->invoke = text_selection_set_invoke;
3298   ot->modal = text_selection_set_modal;
3299   ot->cancel = text_selection_set_cancel;
3300   ot->poll = text_region_edit_poll;
3301 }
3302
3303 /** \} */
3304
3305 /* -------------------------------------------------------------------- */
3306 /** \name Set Cursor Operator
3307  * \{ */
3308
3309 static int text_cursor_set_exec(bContext *C, wmOperator *op)
3310 {
3311   SpaceText *st = CTX_wm_space_text(C);
3312   ARegion *region = CTX_wm_region(C);
3313   int x = RNA_int_get(op->ptr, "x");
3314   int y = RNA_int_get(op->ptr, "y");
3315
3316   text_cursor_set_to_pos(st, region, x, y, 0);
3317
3318   text_update_cursor_moved(C);
3319   WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
3320
3321   return OPERATOR_PASS_THROUGH;
3322 }
3323
3324 static int text_cursor_set_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3325 {
3326   SpaceText *st = CTX_wm_space_text(C);
3327
3328   if (event->mval[0] >= st->runtime.scroll_region_handle.xmin) {
3329     return OPERATOR_PASS_THROUGH;
3330   }
3331
3332   RNA_int_set(op->ptr, "x", event->mval[0]);
3333   RNA_int_set(op->ptr, "y", event->mval[1]);
3334
3335   return text_cursor_set_exec(C, op);
3336 }
3337
3338 void TEXT_OT_cursor_set(wmOperatorType *ot)
3339 {
3340   /* identifiers */
3341   ot->name = "Set Cursor";
3342   ot->idname = "TEXT_OT_cursor_set";
3343   ot->description = "Set cursor position";
3344
3345   /* api callbacks */
3346   ot->invoke = text_cursor_set_invoke;
3347   ot->exec = text_cursor_set_exec;
3348   ot->poll = text_region_edit_poll;
3349
3350   /* properties */
3351   RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
3352   RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
3353 }
3354
3355 /** \} */
3356
3357 /* -------------------------------------------------------------------- */
3358 /** \name Line Number Operator
3359  * \{ */
3360
3361 static int text_line_number_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
3362 {
3363   SpaceText *st = CTX_wm_space_text(C);
3364   Text *text = CTX_data_edit_text(C);
3365   ARegion *region = CTX_wm_region(C);
3366   const int *mval = event->mval;
3367   double time;
3368   static int jump_to = 0;
3369   static double last_jump = 0;
3370
3371   text_update_character_width(st);
3372
3373   if (!st->showlinenrs) {
3374     return OPERATOR_PASS_THROUGH;
3375   }
3376
3377   if (!(mval[0] > 2 &&
3378         mval[0] < (TXT_NUMCOL_WIDTH(st) + (TXT_BODY_LPAD * st->runtime.cwidth_px)) &&
3379         mval[1] > 2 && mval[1] < region->winy - 2)) {
3380     return OPERATOR_PASS_THROUGH;
3381   }
3382
3383   if (!(event->ascii >= '0' && event->ascii <= '9')) {
3384     return OPERATOR_PASS_THROUGH;
3385   }
3386
3387   time = PIL_check_seconds_timer();
3388   if (last_jump < time - 1) {
3389     jump_to = 0;
3390   }
3391
3392   jump_to *= 10;
3393   jump_to += (int)(event->ascii - '0');
3394
3395   txt_move_toline(text, jump_to - 1, 0);
3396   last_jump = time;
3397
3398   text_update_cursor_moved(C);
3399   WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
3400
3401   return OPERATOR_FINISHED;
3402 }
3403
3404 void TEXT_OT_line_number(wmOperatorType *ot)
3405 {
3406   /* identifiers */
3407   ot->name = "Line Number";
3408   ot->idname = "TEXT_OT_line_number";
3409   ot->description = "The current line number";
3410
3411   /* api callbacks */
3412   ot->invoke = text_line_number_invoke;
3413   ot->poll = text_region_edit_poll;
3414 }
3415
3416 /** \} */
3417
3418 /* -------------------------------------------------------------------- */
3419 /** \name Insert Operator
3420  * \{ */
3421
3422 static int text_insert_exec(bContext *C, wmOperator *op)
3423 {
3424   SpaceText *st = CTX_wm_space_text(C);
3425   Text *text = CTX_data_edit_text(C);
3426   char *str;
3427   bool done = false;
3428   size_t i = 0;
3429   uint code;
3430
3431   text_drawcache_tag_update(st, 0);
3432
3433   str = RNA_string_get_alloc(op->ptr, "text", NULL, 0);
3434
3435   ED_text_undo_push_init(C);
3436
3437   if (st && st->overwrite) {
3438     while (str[i]) {
3439       code = BLI_str_utf8_as_unicode_step(str, &i);
3440       done |= txt_replace_char(text, code);
3441     }
3442   }
3443   else {
3444     while (str[i]) {
3445       code = BLI_str_utf8_as_unicode_step(str, &i);
3446       done |= txt_add_char(text, code);
3447     }
3448   }
3449
3450   MEM_freeN(str);
3451
3452   if (!done) {
3453     return OPERATOR_CANCELLED;
3454   }
3455
3456   text_update_line_edited(text->curl);
3457
3458   text_update_cursor_moved(C);
3459   WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
3460
3461   return OPERATOR_FINISHED;
3462 }
3463
3464 static int text_insert_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3465 {
3466   int ret;
3467
3468   // if (!RNA_struct_property_is_set(op->ptr, "text")) { /* always set from keymap XXX */
3469   if (!RNA_string_length(op->ptr, "text")) {
3470     /* if alt/ctrl/super are pressed pass through except for utf8 character event
3471      * (when input method are used for utf8 inputs, the user may assign key event
3472      * including alt/ctrl/super like ctrl+m to commit utf8 string.  in such case,
3473      * the modifiers in the utf8 character event make no sense.) */
3474     if ((event->ctrl || event->oskey) && !event->utf8_buf[0]) {
3475       return OPERATOR_PASS_THROUGH;
3476     }
3477     else {
3478       char str[BLI_UTF8_MAX + 1];
3479       size_t len;
3480
3481       if (event->utf8_buf[0]) {
3482         len = BLI_str_utf8_size_safe(event->utf8_buf);
3483         memcpy(str, event->utf8_buf, len);
3484       }
3485       else {
3486         /* in theory, ghost can set value to extended ascii here */
3487         len = BLI_str_utf8_from_unicode(event->ascii, str);
3488       }
3489       str[len] = '\0';
3490       RNA_string_set(op->ptr, "text", str);
3491     }
3492   }
3493
3494   ret = text_insert_exec(C, op);
3495
3496   /* run the script while editing, evil but useful */
3497   if (ret == OPERATOR_FINISHED && CTX_wm_space_text(C)->live_edit) {
3498     text_run_script(C, NULL);
3499   }
3500
3501   return ret;
3502 }
3503
3504 void TEXT_OT_insert(wmOperatorType *ot)
3505 {
3506   PropertyRNA *prop;
3507
3508   /* identifiers */
3509   ot->name = "Insert";
3510   ot->idname = "TEXT_OT_insert";
3511   ot->description = "Insert text at cursor position";
3512
3513   /* api callbacks */
3514   ot->exec = text_insert_exec;
3515   ot->invoke = text_insert_invoke;
3516   ot->poll = text_edit_poll;
3517
3518   /* flags */
3519   ot->flag = OPTYPE_UNDO;
3520
3521   /* properties */
3522   prop = RNA_def_string(
3523       ot->srna, "text", NULL, 0, "Text", "Text to insert at the cursor position");
3524   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
3525 }
3526
3527 /** \} */
3528
3529 /* -------------------------------------------------------------------- */
3530 /** \name Find Operator
3531  * \{ */
3532
3533 /* mode */
3534 #define TEXT_FIND 0
3535 #define TEXT_REPLACE 1
3536
3537 static int text_find_and_replace(bContext *C, wmOperator *op, short mode)
3538 {
3539   Main *bmain = CTX_data_main(C);
3540   SpaceText *st = CTX_wm_space_text(C);
3541   Text *text = st->text;
3542   int flags;
3543   int found = 0;
3544   char *tmp;
3545
3546   if (!st->findstr[0]) {
3547     return OPERATOR_CANCELLED;
3548   }
3549
3550   flags = st->flags;
3551   if (flags & ST_FIND_ALL) {
3552     flags &= ~ST_FIND_WRAP;
3553   }
3554
3555   /* Replace current */
3556   if (mode != TEXT_FIND && txt_has_sel(text)) {
3557     tmp = txt_sel_to_buf(text, NULL);
3558
3559     if (flags & ST_MATCH_CASE) {
3560       found = STREQ(st->findstr, tmp);
3561     }
3562     else {
3563       found = BLI_strcasecmp(st->findstr, tmp) == 0;
3564     }
3565
3566     if (found) {
3567       if (mode == TEXT_REPLACE) {
3568         ED_text_undo_push_init(C);
3569         txt_insert_buf(text, st->replacestr);
3570         if (text->curl && text->curl->format) {
3571           MEM_freeN(text->curl->format);
3572           text->curl->format = NULL;
3573         }
3574         text_update_cursor_moved(C);
3575         WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
3576         text_drawcache_tag_update(CTX_wm_space_text(C), 1);
3577       }
3578     }
3579     MEM_freeN(tmp);
3580     tmp = NULL;
3581   }
3582
3583   /* Find next */
3584   if (txt_find_string(text, st->findstr, flags & ST_FIND_WRAP, flags & ST_MATCH_CASE)) {
3585     text_update_cursor_moved(C);
3586     WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
3587   }
3588   else if (flags & ST_FIND_ALL) {
3589     if (text->id.next) {
3590       text = st->text = text->id.next;
3591     }
3592     else {
3593       text = st->text = bmain->texts.first;
3594     }
3595     txt_move_toline(text, 0, 0);
3596     text_update_cursor_moved(C);
3597     WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
3598   }
3599   else {
3600     if (!found) {
3601       BKE_reportf(op->reports, RPT_WARNING, "Text not found: %s", st->findstr);
3602     }
3603   }
3604
3605   return OPERATOR_FINISHED;
3606 }
3607
3608 static int text_find_exec(bContext *C, wmOperator *op)
3609 {
3610   return text_find_and_replace(C, op, TEXT_FIND);
3611 }
3612
3613 void TEXT_OT_find(wmOperatorType *ot)
3614 {
3615   /* identifiers */
3616   ot->name = "Find Next";
3617   ot->idname = "TEXT_OT_find";
3618   ot->description = "Find specified text";
3619
3620   /* api callbacks */
3621   ot->exec = text_find_exec;
3622   ot->poll = text_space_edit_poll;
3623 }
3624
3625 /** \} */
3626
3627 /* -------------------------------------------------------------------- */
3628 /** \name Replace Operator
3629  * \{ */
3630
3631 static int text_replace_exec(bContext *C, wmOperator *op)
3632 {
3633   return text_find_and_replace(C, op, TEXT_REPLACE);
3634 }
3635
3636 void TEXT_OT_replace(wmOperatorType *ot)
3637 {
3638   /* identifiers */
3639   ot->name = "Replace";
3640   ot->idname = "TEXT_OT_replace";
3641   ot->description = "Replace text with the specified text";
3642
3643   /* api callbacks */
3644   ot->exec = text_replace_exec;
3645   ot->poll = text_space_edit_poll;
3646
3647   /* flags */
3648   ot->flag = OPTYPE_UNDO;
3649 }
3650
3651 /** \} */
3652
3653 /* -------------------------------------------------------------------- */
3654 /** \name Find Set Selected
3655  * \{ */
3656
3657 static int text_find_set_selected_exec(bContext *C, wmOperator *op)
3658 {
3659   SpaceText *st = CTX_wm_space_text(C);
3660   Text *text = CTX_data_edit_text(C);
3661   char *tmp;
3662
3663   tmp = txt_sel_to_buf(text, NULL);
3664   BLI_strncpy(st->findstr, tmp, ST_MAX_FIND_STR);
3665   MEM_freeN(tmp);
3666
3667   if (!st->findstr[0]) {
3668     return OPERATOR_FINISHED;
3669   }
3670
3671   return text_find_and_replace(C, op, TEXT_FIND);
3672 }
3673
3674 void TEXT_OT_find_set_selected(wmOperatorType *ot)
3675 {
3676   /* identifiers */
3677   ot->name = "Find & Set Selection";
3678   ot->idname = "TEXT_OT_find_set_selected";
3679   ot->description = "Find specified text and set as selected";
3680
3681   /* api callbacks */
3682   ot->exec = text_find_set_selected_exec;
3683   ot->poll = text_space_edit_poll;
3684 }
3685
3686 /** \} */
3687
3688 /* -------------------------------------------------------------------- */
3689 /** \name Replace Set Selected
3690  * \{ */
3691
3692 static int text_replace_set_selected_exec(bContext *C, wmOperator *UNUSED(op))
3693 {
3694   SpaceText *st = CTX_wm_space_text(C);
3695   Text *text = CTX_data_edit_text(C);
3696   char *tmp;
3697
3698   tmp = txt_sel_to_buf(text, NULL);
3699   BLI_strncpy(st->replacestr, tmp, ST_MAX_FIND_STR);
3700   MEM_freeN(tmp);
3701
3702   return OPERATOR_FINISHED;
3703 }
3704
3705 void TEXT_OT_replace_set_selected(wmOperatorType *ot)
3706 {
3707   /* identifiers */
3708   ot->name = "Replace & Set Selection";
3709   ot->idname = "TEXT_OT_replace_set_selected";
3710   ot->description = "Replace text with specified text and set as selected";
3711
3712   /* api callbacks */
3713   ot->exec = text_replace_set_selected_exec;
3714   ot->poll = text_space_edit_poll;
3715
3716   /* flags */
3717   ot->flag = OPTYPE_UNDO;
3718 }
3719
3720 /** \} */
3721
3722 /* -------------------------------------------------------------------- */
3723 /** \name Resolve Conflict Operator
3724  * \{ */
3725
3726 enum { RESOLVE_IGNORE, RESOLVE_RELOAD, RESOLVE_SAVE, RESOLVE_MAKE_INTERNAL };
3727 static const EnumPropertyItem resolution_items[] = {
3728     {RESOLVE_IGNORE, "IGNORE", 0, "Ignore", ""},
3729     {RESOLVE_RELOAD, "RELOAD", 0, "Reload", ""},
3730     {RESOLVE_SAVE, "SAVE", 0, "Save", ""},
3731     {RESOLVE_MAKE_INTERNAL, "MAKE_INTERNAL", 0, "Make Internal", ""},
3732     {0, NULL, 0, NULL, NULL},
3733 };
3734
3735 static int text_resolve_conflict_exec(bContext *C, wmOperator *op)
3736 {
3737   Text *text = CTX_data_edit_text(C);
3738   int resolution = RNA_enum_get(op->ptr, "resolution");
3739
3740   switch (resolution) {
3741     case RESOLVE_RELOAD:
3742       return text_reload_exec(C, op);
3743     case RESOLVE_SAVE:
3744       return text_save_exec(C, op);
3745     case RESOLVE_MAKE_INTERNAL:
3746       return text_make_internal_exec(C, op);
3747     case RESOLVE_IGNORE:
3748       BKE_text_file_modified_ignore(text);
3749       return OPERATOR_FINISHED;
3750   }
3751
3752   return OPERATOR_CANCELLED;
3753 }
3754
3755 static int text_resolve_conflict_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
3756 {
3757   Text *text = CTX_data_edit_text(C);
3758   uiPopupMenu *pup;
3759   uiLayout *layout;
3760
3761   switch (BKE_text_file_modified_check(text)) {
3762     case 1:
3763       if (text->flags & TXT_ISDIRTY) {
3764         /* modified locally and externally, ahhh. offer more possibilities. */
3765         pup = UI_popup_menu_begin(
3766             C, IFACE_("File Modified Outside and Inside Blender"), ICON_NONE);
3767         layout = UI_popup_menu_layout(pup);
3768         uiItemEnumO_ptr(layout,
3769                         op->type,
3770                         IFACE_("Reload from disk (ignore local changes)"),
3771                         0,
3772                         "resolution",
3773                         RESOLVE_RELOAD);
3774         uiItemEnumO_ptr(layout,
3775                         op->type,
3776                         IFACE_("Save to disk (ignore outside changes)"),
3777                         0,
3778                         "resolution",
3779                         RESOLVE_SAVE);
3780         uiItemEnumO_ptr(layout,
3781                         op->type,
3782                         IFACE_("Make text internal (separate copy)"),
3783                         0,
3784                         "resolution",
3785                         RESOLVE_MAKE_INTERNAL);
3786         UI_popup_menu_end(C, pup);
3787       }
3788       else {
3789         pup = UI_popup_menu_begin(C, IFACE_("File Modified Outside Blender"), ICON_NONE);
3790         layout = UI_popup_menu_layout(pup);
3791         uiItemEnumO_ptr(
3792             layout, op->type, IFACE_("Reload from disk"), 0, "resolution", RESOLVE_RELOAD);
3793         uiItemEnumO_ptr(layout,
3794                         op->type,
3795                         IFACE_("Make text internal (separate copy)"),
3796                         0,
3797                         "resolution",
3798                         RESOLVE_MAKE_INTERNAL);
3799         uiItemEnumO_ptr(layout, op->type, IFACE_("Ignore"), 0, "resolution", RESOLVE_IGNORE);
3800         UI_popup_menu_end(C, pup);
3801       }
3802       break;
3803     case 2:
3804       pup = UI_popup_menu_begin(C, IFACE_("File Deleted Outside Blender"), ICON_NONE);
3805       layout = UI_popup_menu_layout(pup);
3806       uiItemEnumO_ptr(
3807           layout, op->type, IFACE_("Make text internal"), 0, "resolution", RESOLVE_MAKE_INTERNAL);
3808       uiItemEnumO_ptr(layout, op->type, IFACE_("Recreate file"), 0, "resolution", RESOLVE_SAVE);
3809       UI_popup_menu_end(C, pup);
3810       break;
3811   }
3812
3813   return OPERATOR_INTERFACE;
3814 }
3815
3816 void TEXT_OT_resolve_conflict(wmOperatorType *ot)
3817 {
3818   /* identifiers */
3819   ot->name = "Resolve Conflict";
3820   ot->idname = "TEXT_OT_resolve_conflict";
3821   ot->description = "When external text is out of sync, resolve the conflict";
3822
3823   /* api callbacks */
3824   ot->exec = text_resolve_conflict_exec;
3825   ot->invoke = text_resolve_conflict_invoke;
3826   ot->poll = text_save_poll;
3827
3828   /* properties */
3829   RNA_def_enum(ot->srna,
3830                "resolution",
3831                resolution_items,
3832                RESOLVE_IGNORE,
3833                "Resolution",
3834                "How to solve conflict due to differences in internal and external text");
3835 }
3836
3837 /** \} */
3838
3839 /* -------------------------------------------------------------------- */
3840 /** \name To 3D Object Operator
3841  * \{ */
3842
3843 static int text_to_3d_object_exec(bContext *C, wmOperator *op)
3844 {
3845   Text *text = CTX_data_edit_text(C);
3846   const bool split_lines = RNA_boolean_get(op->ptr, "split_lines");
3847
3848   ED_text_to_object(C, text, split_lines);
3849
3850   return OPERATOR_FINISHED;
3851 }
3852
3853 void TEXT_OT_to_3d_object(wmOperatorType *ot)
3854 {
3855   /* identifiers */
3856   ot->name = "To 3D Object";
3857   ot->idname = "TEXT_OT_to_3d_object";
3858   ot->description = "Create 3D text object from active text data-block";
3859
3860   /* api callbacks */
3861   ot->exec = text_to_3d_object_exec;
3862   ot->poll = text_edit_poll;
3863
3864   /* flags */