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