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