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