566659e0391559b4119f28abd02e01a883c72162
[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_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_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_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_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         
2318         st->flags|= ST_SCROLL_SELECT;
2319
2320         WM_event_add_modal_handler(C, op);
2321
2322         return OPERATOR_RUNNING_MODAL;
2323 }
2324
2325 void TEXT_OT_scroll_bar(wmOperatorType *ot)
2326 {
2327         /* identifiers */
2328         ot->name= "Scrollbar";
2329         /*don't really see the difference between this and
2330           scroll. Both do basically the same thing (aside 
2331           from keymaps).*/
2332         ot->idname= "TEXT_OT_scroll_bar";
2333         ot->description= "Scroll text screen";
2334         
2335         /* api callbacks */
2336         ot->invoke= text_scroll_bar_invoke;
2337         ot->modal= text_scroll_modal;
2338         ot->cancel= text_scroll_cancel;
2339         ot->poll= text_region_scroll_poll;
2340
2341         /* flags */
2342         ot->flag= OPTYPE_BLOCKING;
2343
2344         /* properties */
2345         RNA_def_int(ot->srna, "lines", 1, INT_MIN, INT_MAX, "Lines", "Number of lines to scroll", -100, 100);
2346 }
2347
2348 /******************* set selection operator **********************/
2349
2350 typedef struct SetSelection {
2351         int selecting;
2352         int selc, sell;
2353         short old[2];
2354 } SetSelection;
2355
2356 static void text_cursor_set_to_pos(SpaceText *st, ARegion *ar, int x, int y, int sel)
2357 {
2358         FlattenString fs;
2359         Text *text= st->text;
2360         TextLine **linep;
2361         int *charp;
2362         int w;
2363
2364         text_update_character_width(st);
2365
2366         if(sel) { linep= &text->sell; charp= &text->selc; } 
2367         else { linep= &text->curl; charp= &text->curc; }
2368         
2369         y= (ar->winy - 2 - y)/st->lheight;
2370
2371         if(st->showlinenrs)
2372                 x-= TXT_OFFSET+TEXTXLOC;
2373         else
2374                 x-= TXT_OFFSET;
2375
2376         if(x<0) x= 0;
2377         x = (x/st->cwidth) + st->left;
2378         
2379         if(st->wordwrap) {
2380                 int i, j, endj, curs, max, chop, start, end, loop, found;
2381                 char ch;
2382
2383                 /* Point to first visible line */
2384                 *linep= text->lines.first;
2385                 i= st->top;
2386                 while(i>0 && *linep) {
2387                         int lines= text_get_visible_lines(st, ar, (*linep)->line);
2388
2389                         if (i-lines<0) {
2390                                 y+= i;
2391                                 break;
2392                         } else {
2393                                 *linep= (*linep)->next;
2394                                 i-= lines;
2395                         }
2396                 }
2397
2398                 max= wrap_width(st, ar);
2399
2400                 loop= 1;
2401                 found= 0;
2402                 while(loop && *linep) {
2403                         start= 0;
2404                         end= max;
2405                         chop= 1;
2406                         curs= 0;
2407                         endj= 0;
2408                         for(i=0, j=0; loop; j++) {
2409                                 int chars;
2410
2411                                 /* Mimic replacement of tabs */
2412                                 ch= (*linep)->line[j];
2413                                 if(ch=='\t') {
2414                                         chars= st->tabnumber-i%st->tabnumber;
2415                                         ch= ' ';
2416                                 }
2417                                 else
2418                                         chars= 1;
2419
2420                                 while(chars--) {
2421                                         /* Gone too far, go back to last wrap point */
2422                                         if(y<0) {
2423                                                 *charp= endj;
2424                                                 loop= 0;
2425                                                 break;
2426                                         /* Exactly at the cursor */
2427                                         }
2428                                         else if(y==0 && i-start==x) {
2429                                                 /* current position could be wrapped to next line */
2430                                                 /* this should be checked when end of current line would be reached */
2431                                                 *charp= curs= j;
2432                                                 found= 1;
2433                                         /* Prepare curs for next wrap */
2434                                         }
2435                                         else if(i-end==x) {
2436                                                 curs= j;
2437                                         }
2438                                         if(i-start>=max) {
2439                                                 if(found) {
2440                                                         /* exact cursor position was found, check if it's */
2441                                                         /* still on needed line (hasn't been wrapped) */
2442                                                         if(*charp>endj && !chop && ch!='\0') (*charp)= endj;
2443                                                         loop= 0;
2444                                                         break;
2445                                                 }
2446
2447                                                 if(chop) endj= j;
2448                                                 start= end;
2449                                                 end += max;
2450
2451                                                 if(j<(*linep)->len)
2452                                                         y--;
2453
2454                                                 chop= 1;
2455                                                 if(y==0 && i-start>=x) {
2456                                                         *charp= curs;
2457                                                         loop= 0;
2458                                                         break;
2459                                                 }
2460                                         }
2461                                         else if(ch==' ' || ch=='-' || ch=='\0') {
2462                                                 if(found) {
2463                                                         loop= 0;
2464                                                         break;
2465                                                 }
2466
2467                                                 if(y==0 && i-start>=x) {
2468                                                         *charp= curs;
2469                                                         loop= 0;
2470                                                         break;
2471                                                 }
2472                                                 end = i+1;
2473                                                 endj = j;
2474                                                 chop= 0;
2475                                         }
2476                                         i++;
2477                                 }
2478                                 if(ch=='\0') break;
2479                         }
2480                         if(!loop || found) break;
2481
2482                         if(!(*linep)->next) {
2483                                 *charp= (*linep)->len;
2484                                 break;
2485                         }
2486
2487                         /* On correct line but didn't meet cursor, must be at end */
2488                         if(y==0) {
2489                                 *charp= (*linep)->len;
2490                                 break;
2491                         }
2492                         *linep= (*linep)->next;
2493                         y--;
2494                 }
2495
2496         }
2497         else {
2498                 y-= txt_get_span(text->lines.first, *linep) - st->top;
2499                 
2500                 if(y>0) {
2501                         while(y-- != 0) if((*linep)->next) *linep= (*linep)->next;
2502                 }
2503                 else if(y<0) {
2504                         while(y++ != 0) if((*linep)->prev) *linep= (*linep)->prev;
2505                 }
2506
2507                 
2508                 w= flatten_string(st, &fs, (*linep)->line);
2509                 if(x<w) *charp= fs.accum[x];
2510                 else *charp= (*linep)->len;
2511                 flatten_string_free(&fs);
2512         }
2513         if(!sel) txt_pop_sel(text);
2514 }
2515
2516 static void text_cursor_set_apply(bContext *C, wmOperator *op, wmEvent *event)
2517 {
2518         SpaceText *st= CTX_wm_space_text(C);
2519         ARegion *ar= CTX_wm_region(C);
2520         SetSelection *ssel= op->customdata;
2521
2522         if(event->mval[1]<0 || event->mval[1]>ar->winy) {
2523                 int d= (ssel->old[1]-event->mval[1])*st->pix_per_line;
2524                 if(d) txt_screen_skip(st, ar, d);
2525
2526                 text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1]<0?0:ar->winy, 1);
2527
2528                 text_update_cursor_moved(C);
2529                 WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2530         } 
2531         else if(!st->wordwrap && (event->mval[0]<0 || event->mval[0]>ar->winx)) {
2532                 if(event->mval[0]>ar->winx) st->left++;
2533                 else if(event->mval[0]<0 && st->left>0) st->left--;
2534                 
2535                 text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1], 1);
2536                 
2537                 text_update_cursor_moved(C);
2538                 WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2539                 // XXX PIL_sleep_ms(10);
2540         } 
2541         else {
2542                 text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1], 1);
2543
2544                 text_update_cursor_moved(C);
2545                 WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2546
2547                 ssel->old[0]= event->mval[0];
2548                 ssel->old[1]= event->mval[1];
2549         } 
2550 }
2551
2552 static void text_cursor_set_exit(bContext *C, wmOperator *op)
2553 {
2554         SpaceText *st= CTX_wm_space_text(C);
2555         Text *text= st->text;
2556         SetSelection *ssel= op->customdata;
2557         int linep2, charp2;
2558         char *buffer;
2559
2560         if(txt_has_sel(text)) {
2561                 buffer = txt_sel_to_buf(text);
2562                 WM_clipboard_text_set(buffer, 1);
2563                 MEM_freeN(buffer);
2564         }
2565
2566         linep2= txt_get_span(st->text->lines.first, st->text->sell);
2567         charp2= st->text->selc;
2568                 
2569         if(ssel->sell!=linep2 || ssel->selc!=charp2)
2570                 txt_undo_add_toop(st->text, UNDO_STO, ssel->sell, ssel->selc, linep2, charp2);
2571
2572         text_update_cursor_moved(C);
2573         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2574
2575         MEM_freeN(ssel);
2576 }
2577
2578 static int text_set_selection_invoke(bContext *C, wmOperator *op, wmEvent *event)
2579 {
2580         SpaceText *st= CTX_wm_space_text(C);
2581         SetSelection *ssel;
2582
2583         if(event->mval[0]>=st->txtbar.xmin)
2584                 return OPERATOR_PASS_THROUGH;
2585
2586         op->customdata= MEM_callocN(sizeof(SetSelection), "SetCursor");
2587         ssel= op->customdata;
2588         ssel->selecting= RNA_boolean_get(op->ptr, "select");
2589
2590         ssel->old[0]= event->mval[0];
2591         ssel->old[1]= event->mval[1];
2592
2593         ssel->sell= txt_get_span(st->text->lines.first, st->text->sell);
2594         ssel->selc= st->text->selc;
2595
2596         WM_event_add_modal_handler(C, op);
2597
2598         text_cursor_set_apply(C, op, event);
2599
2600         return OPERATOR_RUNNING_MODAL;
2601 }
2602
2603 static int text_set_selection_modal(bContext *C, wmOperator *op, wmEvent *event)
2604 {
2605         switch(event->type) {
2606                 case LEFTMOUSE:
2607                 case MIDDLEMOUSE:
2608                 case RIGHTMOUSE:
2609                         text_cursor_set_exit(C, op);
2610                         return OPERATOR_FINISHED;
2611                 case MOUSEMOVE:
2612                         text_cursor_set_apply(C, op, event);
2613                         break;
2614         }
2615
2616         return OPERATOR_RUNNING_MODAL;
2617 }
2618
2619 static int text_set_selection_cancel(bContext *C, wmOperator *op)
2620 {
2621         text_cursor_set_exit(C, op);
2622         return OPERATOR_FINISHED;
2623 }
2624
2625 void TEXT_OT_selection_set(wmOperatorType *ot)
2626 {
2627         /* identifiers */
2628         ot->name= "Set Selection";
2629         ot->idname= "TEXT_OT_selection_set";
2630         ot->description= "Set cursor selection";
2631
2632         /* api callbacks */
2633         ot->invoke= text_set_selection_invoke;
2634         ot->modal= text_set_selection_modal;
2635         ot->cancel= text_set_selection_cancel;
2636         ot->poll= text_region_edit_poll;
2637
2638         /* properties */
2639         RNA_def_boolean(ot->srna, "select", 0, "Select", "Set selection end rather than cursor");
2640 }
2641
2642 /******************* set cursor operator **********************/
2643
2644 static int text_cursor_set_exec(bContext *C, wmOperator *op)
2645 {
2646         SpaceText *st= CTX_wm_space_text(C);
2647         Text *text= st->text;
2648         ARegion *ar= CTX_wm_region(C);
2649         int x= RNA_int_get(op->ptr, "x");
2650         int y= RNA_int_get(op->ptr, "y");
2651         int oldl, oldc;
2652
2653         oldl= txt_get_span(text->lines.first, text->curl);
2654         oldc= text->curc;
2655
2656         text_cursor_set_to_pos(st, ar, x, y, 0);
2657
2658         txt_undo_add_toop(text, UNDO_CTO, oldl, oldc, txt_get_span(text->lines.first, text->curl), text->curc);
2659
2660         text_update_cursor_moved(C);
2661         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, st->text);
2662
2663         return OPERATOR_PASS_THROUGH;
2664 }
2665
2666 static int text_cursor_set_invoke(bContext *C, wmOperator *op, wmEvent *event)
2667 {
2668         SpaceText *st= CTX_wm_space_text(C);
2669
2670         if(event->mval[0]>=st->txtbar.xmin)
2671                 return OPERATOR_PASS_THROUGH;
2672
2673         RNA_int_set(op->ptr, "x", event->mval[0]);
2674         RNA_int_set(op->ptr, "y", event->mval[1]);
2675
2676         return text_cursor_set_exec(C, op);
2677 }
2678
2679 void TEXT_OT_cursor_set(wmOperatorType *ot)
2680 {
2681         /* identifiers */
2682         ot->name= "Set Cursor";
2683         ot->idname= "TEXT_OT_cursor_set";
2684         ot->description= "Set cursor position";
2685
2686         /* api callbacks */
2687         ot->invoke= text_cursor_set_invoke;
2688         ot->exec= text_cursor_set_exec;
2689         ot->poll= text_region_edit_poll;
2690
2691         /* properties */
2692         RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
2693         RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
2694 }
2695
2696 /******************* line number operator **********************/
2697
2698 static int text_line_number_invoke(bContext *C, wmOperator *UNUSED(op), wmEvent *event)
2699 {
2700         SpaceText *st= CTX_wm_space_text(C);
2701         Text *text= CTX_data_edit_text(C);
2702         ARegion *ar= CTX_wm_region(C);
2703         const int *mval= event->mval;
2704         double time;
2705         static int jump_to= 0;
2706         static double last_jump= 0;
2707
2708         text_update_character_width(st);
2709
2710         if(!st->showlinenrs)
2711                 return OPERATOR_PASS_THROUGH;
2712
2713         if(!(mval[0]>2 && mval[0]<(TXT_OFFSET + TEXTXLOC) && mval[1]>2 && mval[1]<ar->winy-2))
2714                 return OPERATOR_PASS_THROUGH;
2715
2716         if(!(event->ascii>='0' && event->ascii<='9'))
2717                 return OPERATOR_PASS_THROUGH;
2718
2719         time = PIL_check_seconds_timer();
2720         if(last_jump < time-1)
2721                 jump_to= 0;
2722
2723         jump_to *= 10;
2724         jump_to += (int)(event->ascii-'0');
2725
2726         txt_move_toline(text, jump_to-1, 0);
2727         last_jump= time;
2728
2729         text_update_cursor_moved(C);
2730         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, text);
2731
2732         return OPERATOR_FINISHED;
2733 }
2734
2735 void TEXT_OT_line_number(wmOperatorType *ot)
2736 {
2737         /* identifiers */
2738         ot->name= "Line Number";
2739         ot->idname= "TEXT_OT_line_number";
2740         ot->description= "The current line number";
2741         
2742         /* api callbacks */
2743         ot->invoke= text_line_number_invoke;
2744         ot->poll= text_region_edit_poll;
2745 }
2746
2747 /******************* insert operator **********************/
2748
2749 static int text_insert_exec(bContext *C, wmOperator *op)
2750 {
2751         SpaceText *st= CTX_wm_space_text(C);
2752         Text *text= CTX_data_edit_text(C);
2753         char *str;
2754         int done = 0, i;
2755
2756         text_drawcache_tag_update(st, 0);
2757
2758         str= RNA_string_get_alloc(op->ptr, "text", NULL, 0);
2759
2760         if(st && st->overwrite) {
2761                 for(i=0; str[i]; i++) {
2762                         done |= txt_replace_char(text, str[i]);
2763                 }
2764         } else {
2765                 for(i=0; str[i]; i++) {
2766                         done |= txt_add_char(text, str[i]);
2767                 }
2768         }
2769
2770         MEM_freeN(str);
2771         
2772         if(!done)
2773                 return OPERATOR_CANCELLED;
2774
2775         text_update_line_edited(text->curl);
2776
2777         text_update_cursor_moved(C);
2778         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2779
2780         return OPERATOR_FINISHED;
2781 }
2782
2783 static int text_insert_invoke(bContext *C, wmOperator *op, wmEvent *event)
2784 {
2785         int ret;
2786
2787         // if(!RNA_property_is_set(op->ptr, "text")) { /* always set from keymap XXX */
2788         if(!RNA_string_length(op->ptr, "text")) {
2789                 /* if alt/ctrl/super are pressed pass through */
2790                 if(event->ctrl || event->oskey) {
2791                         return OPERATOR_PASS_THROUGH;
2792                 }
2793                 else {
2794                         char str[2];
2795                         str[0]= event->ascii;
2796                         str[1]= '\0';
2797                         RNA_string_set(op->ptr, "text", str);
2798                 }
2799         }
2800
2801         ret = text_insert_exec(C, op);
2802         
2803         /* run the script while editing, evil but useful */
2804         if(ret==OPERATOR_FINISHED && CTX_wm_space_text(C)->live_edit)
2805                 text_run_script(C, NULL);
2806
2807         return ret;
2808 }
2809
2810 void TEXT_OT_insert(wmOperatorType *ot)
2811 {
2812         /* identifiers */
2813         ot->name= "Insert";
2814         ot->idname= "TEXT_OT_insert";
2815         ot->description= "Insert text at cursor position";
2816         
2817         /* api callbacks */
2818         ot->exec= text_insert_exec;
2819         ot->invoke= text_insert_invoke;
2820         ot->poll= text_edit_poll;
2821
2822         /* properties */
2823         RNA_def_string(ot->srna, "text", "", 0, "Text", "Text to insert at the cursor position");
2824 }
2825
2826 /******************* find operator *********************/
2827
2828 /* mode */
2829 #define TEXT_FIND               0
2830 #define TEXT_REPLACE    1
2831 #define TEXT_MARK_ALL   2
2832
2833 static int text_find_and_replace(bContext *C, wmOperator *op, short mode)
2834 {
2835         Main *bmain= CTX_data_main(C);
2836         SpaceText *st= CTX_wm_space_text(C);
2837         Text *start= NULL, *text= st->text;
2838         int flags, first= 1;
2839         int found = 0;
2840         char *tmp;
2841
2842         if(!st->findstr[0] || (mode == TEXT_REPLACE && !st->replacestr[0]))
2843                 return OPERATOR_CANCELLED;
2844
2845         flags= st->flags;
2846         if(flags & ST_FIND_ALL)
2847                 flags ^= ST_FIND_WRAP;
2848
2849         do {
2850                 int proceed= 0;
2851
2852                 if(first) {
2853                         if(text->markers.first)
2854                                 WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2855
2856                         txt_clear_markers(text, TMARK_GRP_FINDALL, 0);
2857                 }
2858
2859                 first= 0;
2860                 
2861                 /* Replace current */
2862                 if(mode!=TEXT_FIND && txt_has_sel(text)) {
2863                         tmp= txt_sel_to_buf(text);
2864
2865                         if(flags & ST_MATCH_CASE) proceed= strcmp(st->findstr, tmp)==0;
2866                         else proceed= BLI_strcasecmp(st->findstr, tmp)==0;
2867
2868                         if(proceed) {
2869                                 if(mode==TEXT_REPLACE) {
2870                                         txt_insert_buf(text, st->replacestr);
2871                                         if(text->curl && text->curl->format) {
2872                                                 MEM_freeN(text->curl->format);
2873                                                 text->curl->format= NULL;
2874                                         }
2875                                         text_update_cursor_moved(C);
2876                                         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2877                                         text_drawcache_tag_update(CTX_wm_space_text(C), 1);
2878                                 }
2879                                 else if(mode==TEXT_MARK_ALL) {
2880                                         unsigned char color[4];
2881                                         UI_GetThemeColor4ubv(TH_SHADE2, color);
2882
2883                                         if(txt_find_marker(text, text->curl, text->selc, TMARK_GRP_FINDALL, 0)) {
2884                                                 if(tmp) MEM_freeN(tmp), tmp=NULL;
2885                                                 break;
2886                                         }
2887
2888                                         txt_add_marker(text, text->curl, text->curc, text->selc, color, TMARK_GRP_FINDALL, TMARK_EDITALL);
2889                                         text_update_cursor_moved(C);
2890                                         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
2891                                 }
2892                         }
2893                         MEM_freeN(tmp);
2894                         tmp= NULL;
2895                 }
2896
2897                 /* Find next */
2898                 if(txt_find_string(text, st->findstr, flags & ST_FIND_WRAP, flags & ST_MATCH_CASE)) {
2899                         text_update_cursor_moved(C);
2900                         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, text);
2901                 }
2902                 else if(flags & ST_FIND_ALL) {
2903                         if(text==start) break;
2904                         if(!start) start= text;
2905                         if(text->id.next)
2906                                 text= st->text= text->id.next;
2907                         else
2908                                 text= st->text= bmain->text.first;
2909                         txt_move_toline(text, 0, 0);
2910                         text_update_cursor_moved(C);
2911                         WM_event_add_notifier(C, NC_TEXT|ND_CURSOR, text);
2912                         first= 1;
2913                 }
2914                 else {
2915                         if(!found && !proceed) BKE_reportf(op->reports, RPT_ERROR, "Text not found: %s", st->findstr);
2916                         break;
2917                 }
2918                 found = 1;
2919         } while(mode==TEXT_MARK_ALL);
2920
2921         return OPERATOR_FINISHED;
2922 }
2923
2924 static int text_find_exec(bContext *C, wmOperator *op)
2925 {
2926         return text_find_and_replace(C, op, TEXT_FIND);
2927 }
2928
2929 void TEXT_OT_find(wmOperatorType *ot)
2930 {
2931         /* identifiers */
2932         ot->name= "Find";
2933         ot->idname= "TEXT_OT_find";
2934         ot->description= "Find specified text";
2935         
2936         /* api callbacks */
2937         ot->exec= text_find_exec;
2938         ot->poll= text_space_edit_poll;
2939 }
2940
2941 /******************* replace operator *********************/
2942
2943 static int text_replace_exec(bContext *C, wmOperator *op)
2944 {
2945         return text_find_and_replace(C, op, TEXT_REPLACE);
2946 }
2947
2948 void TEXT_OT_replace(wmOperatorType *ot)
2949 {
2950         /* identifiers */
2951         ot->name= "Replace";
2952         ot->idname= "TEXT_OT_replace";
2953         ot->description= "Replace text with the specified text";
2954
2955         /* api callbacks */
2956         ot->exec= text_replace_exec;
2957         ot->poll= text_space_edit_poll;
2958 }
2959
2960 /******************* mark all operator *********************/
2961
2962 static int text_mark_all_exec(bContext *C, wmOperator *op)
2963 {
2964         return text_find_and_replace(C, op, TEXT_MARK_ALL);
2965 }
2966
2967 void TEXT_OT_mark_all(wmOperatorType *ot)
2968 {
2969         /* identifiers */
2970         ot->name= "Mark All";
2971         ot->idname= "TEXT_OT_mark_all";
2972         ot->description= "Mark all specified text";
2973         
2974         /* api callbacks */
2975         ot->exec= text_mark_all_exec;
2976         ot->poll= text_space_edit_poll;
2977 }
2978
2979 /******************* find set selected *********************/
2980
2981 static int text_find_set_selected_exec(bContext *C, wmOperator *op)
2982 {
2983         SpaceText *st= CTX_wm_space_text(C);
2984         Text *text= CTX_data_edit_text(C);
2985         char *tmp;
2986
2987         tmp= txt_sel_to_buf(text);
2988         BLI_strncpy(st->findstr, tmp, ST_MAX_FIND_STR);
2989         MEM_freeN(tmp);
2990
2991         if(!st->findstr[0])
2992                 return OPERATOR_FINISHED;
2993
2994         return text_find_and_replace(C, op, TEXT_FIND);
2995 }
2996
2997 void TEXT_OT_find_set_selected(wmOperatorType *ot)
2998 {
2999         /* identifiers */
3000         ot->name= "Find Set Selected";
3001         ot->idname= "TEXT_OT_find_set_selected";
3002         ot->description= "Find specified text and set as selected";
3003         
3004         /* api callbacks */
3005         ot->exec= text_find_set_selected_exec;
3006         ot->poll= text_space_edit_poll;
3007 }
3008
3009 /******************* replace set selected *********************/
3010
3011 static int text_replace_set_selected_exec(bContext *C, wmOperator *UNUSED(op))
3012 {
3013         SpaceText *st= CTX_wm_space_text(C);
3014         Text *text= CTX_data_edit_text(C);
3015         char *tmp;
3016
3017         tmp= txt_sel_to_buf(text);
3018         BLI_strncpy(st->replacestr, tmp, ST_MAX_FIND_STR);
3019         MEM_freeN(tmp);
3020
3021         return OPERATOR_FINISHED;
3022 }
3023
3024 void TEXT_OT_replace_set_selected(wmOperatorType *ot)
3025 {
3026         /* identifiers */
3027         ot->name= "Replace Set Selected";
3028         ot->idname= "TEXT_OT_replace_set_selected";
3029         ot->description= "Replace text with specified text and set as selected";
3030         
3031         /* api callbacks */
3032         ot->exec= text_replace_set_selected_exec;
3033         ot->poll= text_space_edit_poll;
3034 }
3035
3036 /****************** resolve conflict operator ******************/
3037
3038 enum { RESOLVE_IGNORE, RESOLVE_RELOAD, RESOLVE_SAVE, RESOLVE_MAKE_INTERNAL };
3039 static EnumPropertyItem resolution_items[]= {
3040         {RESOLVE_IGNORE, "IGNORE", 0, "Ignore", ""},
3041         {RESOLVE_RELOAD, "RELOAD", 0, "Reload", ""},
3042         {RESOLVE_SAVE, "SAVE", 0, "Save", ""},
3043         {RESOLVE_MAKE_INTERNAL, "MAKE_INTERNAL", 0, "Make Internal", ""},
3044         {0, NULL, 0, NULL, NULL}};
3045
3046 /* returns 0 if file on disk is the same or Text is in memory only
3047    returns 1 if file has been modified on disk since last local edit
3048    returns 2 if file on disk has been deleted
3049    -1 is returned if an error occurs */
3050
3051 int text_file_modified(Text *text)
3052 {
3053         struct stat st;
3054         int result;
3055         char file[FILE_MAX];
3056
3057         if(!text || !text->name)
3058                 return 0;
3059
3060         BLI_strncpy(file, text->name, FILE_MAX);
3061         BLI_path_abs(file, G.main->name);
3062
3063         if(!BLI_exists(file))
3064                 return 2;
3065
3066         result = stat(file, &st);
3067         
3068         if(result == -1)
3069                 return -1;
3070
3071         if((st.st_mode & S_IFMT) != S_IFREG)
3072                 return -1;
3073
3074         if(st.st_mtime > text->mtime)
3075                 return 1;
3076
3077         return 0;
3078 }
3079
3080 static void text_ignore_modified(Text *text)
3081 {
3082         struct stat st;
3083         int result;
3084         char file[FILE_MAX];
3085
3086         if(!text || !text->name) return;
3087
3088         BLI_strncpy(file, text->name, FILE_MAX);
3089         BLI_path_abs(file, G.main->name);
3090
3091         if(!BLI_exists(file)) return;
3092
3093         result = stat(file, &st);
3094         
3095         if(result == -1 || (st.st_mode & S_IFMT) != S_IFREG)
3096                 return;
3097
3098         text->mtime= st.st_mtime;
3099 }
3100
3101 static int text_resolve_conflict_exec(bContext *C, wmOperator *op)
3102 {
3103         Text *text= CTX_data_edit_text(C);
3104         int resolution= RNA_enum_get(op->ptr, "resolution");
3105
3106         switch(resolution) {
3107                 case RESOLVE_RELOAD:
3108                         return text_reload_exec(C, op);
3109                 case RESOLVE_SAVE:
3110                         return text_save_exec(C, op);
3111                 case RESOLVE_MAKE_INTERNAL:
3112                         return text_make_internal_exec(C, op);
3113                 case RESOLVE_IGNORE:
3114                         text_ignore_modified(text);
3115                         return OPERATOR_FINISHED;
3116         }
3117
3118         return OPERATOR_CANCELLED;
3119 }
3120
3121 static int text_resolve_conflict_invoke(bContext *C, wmOperator *op, wmEvent *UNUSED(event))
3122 {
3123         Text *text= CTX_data_edit_text(C);
3124         uiPopupMenu *pup;
3125         uiLayout *layout;
3126
3127         switch(text_file_modified(text)) {
3128                 case 1:
3129                         if(text->flags & TXT_ISDIRTY) {
3130                                 /* modified locally and externally, ahhh. offer more possibilites. */
3131                                 pup= uiPupMenuBegin(C, "File Modified Outside and Inside Blender", ICON_NONE);
3132                                 layout= uiPupMenuLayout(pup);
3133                                 uiItemEnumO(layout, op->type->idname, "Reload from disk (ignore local changes)", 0, "resolution", RESOLVE_RELOAD);
3134                                 uiItemEnumO(layout, op->type->idname, "Save to disk (ignore outside changes)", 0, "resolution", RESOLVE_SAVE);
3135                                 uiItemEnumO(layout, op->type->idname, "Make text internal (separate copy)", 0, "resolution", RESOLVE_MAKE_INTERNAL);
3136                                 uiPupMenuEnd(C, pup);
3137                         }
3138                         else {
3139                                 pup= uiPupMenuBegin(C, "File Modified Outside Blender", ICON_NONE);
3140                                 layout= uiPupMenuLayout(pup);
3141                                 uiItemEnumO(layout, op->type->idname, "Reload from disk", 0, "resolution", RESOLVE_RELOAD);
3142                                 uiItemEnumO(layout, op->type->idname, "Make text internal (separate copy)", 0, "resolution", RESOLVE_MAKE_INTERNAL);
3143                                 uiItemEnumO(layout, op->type->idname, "Ignore", 0, "resolution", RESOLVE_IGNORE);
3144                                 uiPupMenuEnd(C, pup);
3145                         }
3146                         break;
3147                 case 2:
3148                         pup= uiPupMenuBegin(C, "File Deleted Outside Blender", ICON_NONE);
3149                         layout= uiPupMenuLayout(pup);
3150                         uiItemEnumO(layout, op->type->idname, "Make text internal", 0, "resolution", RESOLVE_MAKE_INTERNAL);
3151                         uiItemEnumO(layout, op->type->idname, "Recreate file", 0, "resolution", RESOLVE_SAVE);
3152                         uiPupMenuEnd(C, pup);
3153                         break;
3154         }
3155
3156         return OPERATOR_CANCELLED;
3157 }
3158
3159 void TEXT_OT_resolve_conflict(wmOperatorType *ot)
3160 {
3161         /* identifiers */
3162         ot->name= "Resolve Conflict";
3163         ot->idname= "TEXT_OT_resolve_conflict";
3164         ot->description= "When external text is out of sync, resolve the conflict";
3165
3166         /* api callbacks */
3167         ot->exec= text_resolve_conflict_exec;
3168         ot->invoke= text_resolve_conflict_invoke;
3169         ot->poll= text_save_poll;
3170
3171         /* properties */
3172         RNA_def_enum(ot->srna, "resolution", resolution_items, RESOLVE_IGNORE, "Resolution", "How to solve conflict due to differences in internal and external text");
3173 }
3174
3175 /********************** to 3d object operator *****************/
3176
3177 static int text_to_3d_object_exec(bContext *C, wmOperator *op)
3178 {
3179         Text *text= CTX_data_edit_text(C);
3180         int split_lines= RNA_boolean_get(op->ptr, "split_lines");
3181
3182         ED_text_to_object(C, text, split_lines);
3183
3184         return OPERATOR_FINISHED;
3185 }
3186
3187 void TEXT_OT_to_3d_object(wmOperatorType *ot)
3188 {
3189         /* identifiers */
3190         ot->name= "To 3D Object";
3191         ot->idname= "TEXT_OT_to_3d_object";
3192         ot->description= "Create 3d text object from active text data block";
3193         
3194         /* api callbacks */
3195         ot->exec= text_to_3d_object_exec;
3196         ot->poll= text_edit_poll;
3197         
3198         /* flags */
3199         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
3200
3201         /* properties */
3202         RNA_def_boolean(ot->srna, "split_lines", 0, "Split Lines", "Create one object per line in the text");
3203 }
3204
3205
3206 /************************ undo ******************************/
3207
3208 void ED_text_undo_step(bContext *C, int step)
3209 {
3210         Text *text= CTX_data_edit_text(C);
3211
3212         if(!text)
3213                 return;
3214
3215         if(step==1)
3216                 txt_do_undo(text);
3217         else if(step==-1)
3218                 txt_do_redo(text);
3219
3220         text_update_edited(text);
3221
3222         text_update_cursor_moved(C);
3223         text_drawcache_tag_update(CTX_wm_space_text(C), 1);
3224         WM_event_add_notifier(C, NC_TEXT|NA_EDITED, text);
3225 }
3226