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