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