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