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