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