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