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