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