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