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