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