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