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