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