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