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