Fix PyConsole: Drag events finished early in modal text selection
[blender.git] / source / blender / editors / space_console / console_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  * Contributor(s): Campbell Barton
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/editors/space_console/console_ops.c
24  *  \ingroup spconsole
25  */
26
27
28 #include <stdlib.h>
29 #include <string.h>
30 #include <ctype.h> /* ispunct */
31 #include <sys/stat.h>
32
33 #include "MEM_guardedalloc.h"
34
35 #include "DNA_userdef_types.h"
36
37 #include "BLI_utildefines.h"
38 #include "BLI_listbase.h"
39 #include "BLI_string_cursor_utf8.h"
40 #include "BLI_string_utf8.h"
41 #include "BLI_string.h"
42 #include "BLI_dynstr.h"
43 #include "BLI_math.h"
44
45 #include "BKE_context.h"
46
47 #include "WM_api.h"
48 #include "WM_types.h"
49
50 #include "UI_view2d.h"
51 #include "ED_screen.h"
52
53 #include "RNA_access.h"
54 #include "RNA_define.h"
55
56 #include "console_intern.h"
57
58 /* so when we type - the view scrolls to the bottom */
59 static void console_scroll_bottom(ARegion *ar)
60 {
61         View2D *v2d = &ar->v2d;
62         v2d->cur.ymin = 0.0;
63         v2d->cur.ymax = (float)v2d->winy;
64 }
65
66 void console_textview_update_rect(SpaceConsole *sc, ARegion *ar)
67 {
68         View2D *v2d = &ar->v2d;
69
70         UI_view2d_totRect_set(v2d, ar->winx - 1, console_textview_height(sc, ar));
71 }
72
73 static void console_select_offset(SpaceConsole *sc, const int offset)
74 {
75         sc->sel_start += offset;
76         sc->sel_end += offset;
77 }
78
79 void console_history_free(SpaceConsole *sc, ConsoleLine *cl)
80 {
81         BLI_remlink(&sc->history, cl);
82         MEM_freeN(cl->line);
83         MEM_freeN(cl);
84 }
85 void console_scrollback_free(SpaceConsole *sc, ConsoleLine *cl)
86 {
87         BLI_remlink(&sc->scrollback, cl);
88         MEM_freeN(cl->line);
89         MEM_freeN(cl);
90 }
91
92 static void console_scrollback_limit(SpaceConsole *sc)
93 {
94         int tot;
95
96         if (U.scrollback < 32) U.scrollback = 256;  // XXX - save in user defaults
97
98         for (tot = BLI_listbase_count(&sc->scrollback); tot > U.scrollback; tot--)
99                 console_scrollback_free(sc, sc->scrollback.first);
100 }
101
102 static ConsoleLine *console_history_find(SpaceConsole *sc, const char *str, ConsoleLine *cl_ignore)
103 {
104         ConsoleLine *cl;
105
106         for (cl = sc->history.last; cl; cl = cl->prev) {
107                 if (cl == cl_ignore)
108                         continue;
109
110                 if (STREQ(str, cl->line))
111                         return cl;
112         }
113
114         return NULL;
115 }
116
117 /* return 0 if no change made, clamps the range */
118 static bool console_line_cursor_set(ConsoleLine *cl, int cursor)
119 {
120         int cursor_new;
121
122         if      (cursor < 0) cursor_new = 0;
123         else if (cursor > cl->len) cursor_new = cl->len;
124         else cursor_new = cursor;
125
126         if (cursor_new == cl->cursor) {
127                 return false;
128         }
129
130         cl->cursor = cursor_new;
131         return true;
132 }
133
134 #if 0 // XXX unused
135 static void console_lb_debug__internal(ListBase *lb)
136 {
137         ConsoleLine *cl;
138
139         printf("%d: ", BLI_listbase_count(lb));
140         for (cl = lb->first; cl; cl = cl->next)
141                 printf("<%s> ", cl->line);
142         printf("\n");
143
144 }
145
146 static void console_history_debug(const bContext *C)
147 {
148         SpaceConsole *sc = CTX_wm_space_console(C);
149
150
151         console_lb_debug__internal(&sc->history);
152 }
153 #endif
154
155 static ConsoleLine *console_lb_add__internal(ListBase *lb, ConsoleLine *from)
156 {
157         ConsoleLine *ci = MEM_callocN(sizeof(ConsoleLine), "ConsoleLine Add");
158
159         if (from) {
160                 BLI_assert(strlen(from->line) == from->len);
161                 ci->line = BLI_strdupn(from->line, from->len);
162                 ci->len = ci->len_alloc = from->len;
163                 ci->cursor = from->cursor;
164                 ci->type = from->type;
165         }
166         else {
167                 ci->line = MEM_callocN(64, "console-in-line");
168                 ci->len_alloc = 64;
169                 ci->len = 0;
170         }
171
172         BLI_addtail(lb, ci);
173         return ci;
174 }
175
176 static ConsoleLine *console_history_add(SpaceConsole *sc, ConsoleLine *from)
177 {
178         return console_lb_add__internal(&sc->history, from);
179 }
180
181 #if 0 /* may use later ? */
182 static ConsoleLine *console_scrollback_add(const bContext *C, ConsoleLine *from)
183 {
184         SpaceConsole *sc = CTX_wm_space_console(C);
185
186         return console_lb_add__internal(&sc->scrollback, from);
187 }
188 #endif
189
190 static ConsoleLine *console_lb_add_str__internal(ListBase *lb, char *str, bool own)
191 {
192         ConsoleLine *ci = MEM_callocN(sizeof(ConsoleLine), "ConsoleLine Add");
193         if (own) ci->line = str;
194         else ci->line = BLI_strdup(str);
195
196         ci->len = ci->len_alloc = strlen(str);
197
198         BLI_addtail(lb, ci);
199         return ci;
200 }
201 ConsoleLine *console_history_add_str(SpaceConsole *sc, char *str, bool own)
202 {
203         return console_lb_add_str__internal(&sc->history, str, own);
204 }
205 ConsoleLine *console_scrollback_add_str(SpaceConsole *sc, char *str, bool own)
206 {
207         ConsoleLine *ci = console_lb_add_str__internal(&sc->scrollback, str, own);
208         console_select_offset(sc, ci->len + 1);
209         return ci;
210 }
211
212 ConsoleLine *console_history_verify(const bContext *C)
213 {
214         SpaceConsole *sc = CTX_wm_space_console(C);
215         ConsoleLine *ci = sc->history.last;
216         if (ci == NULL)
217                 ci = console_history_add(sc, NULL);
218
219         return ci;
220 }
221
222
223 static void console_line_verify_length(ConsoleLine *ci, int len)
224 {
225         /* resize the buffer if needed */
226         if (len >= ci->len_alloc) {
227                 /* new length */
228 #ifndef NDEBUG
229                 int new_len = len + 1;
230 #else
231                 int new_len = (len + 1) * 2;
232 #endif
233                 char *new_line = MEM_callocN(new_len, "console line");
234                 memcpy(new_line, ci->line, ci->len);
235                 MEM_freeN(ci->line);
236
237                 ci->line = new_line;
238                 ci->len_alloc = new_len;
239         }
240 }
241
242 static int console_line_insert(ConsoleLine *ci, char *str)
243 {
244         int len = strlen(str);
245
246         if (len > 0 && str[len - 1] == '\n') { /* stop new lines being pasted at the end of lines */
247                 str[len - 1] = '\0';
248                 len--;
249         }
250
251         if (len == 0)
252                 return 0;
253
254         console_line_verify_length(ci, len + ci->len);
255
256         memmove(ci->line + ci->cursor + len, ci->line + ci->cursor, (ci->len - ci->cursor) + 1);
257         memcpy(ci->line + ci->cursor, str, len);
258
259         ci->len += len;
260         ci->cursor += len;
261
262         return len;
263 }
264
265 /**
266  * Take an absolute index and give the line/column info.
267  *
268  * \note be sure to call console_scrollback_prompt_begin first
269  */
270 static bool console_line_column_from_index(
271         SpaceConsole *sc, const int pos,
272         ConsoleLine **r_cl, int *r_cl_offset, int *r_col)
273 {
274         ConsoleLine *cl;
275         int offset = 0;
276
277         for (cl = sc->scrollback.last; cl; cl = cl->prev) {
278                 offset += cl->len + 1;
279                 if (offset >= pos) {
280                         break;
281                 }
282         }
283
284         if (cl) {
285                 offset -= 1;
286                 *r_cl = cl;
287                 *r_cl_offset = offset;
288                 *r_col = offset - pos;
289                 return true;
290         }
291         else {
292                 *r_cl = NULL;
293                 *r_cl_offset = -1;
294                 *r_col = -1;
295                 return false;
296         }
297 }
298
299 /* static funcs for text editing */
300
301 /* similar to the text editor, with some not used. keep compatible */
302 static const EnumPropertyItem console_move_type_items[] = {
303         {LINE_BEGIN, "LINE_BEGIN", 0, "Line Begin", ""},
304         {LINE_END, "LINE_END", 0, "Line End", ""},
305         {PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
306         {NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
307         {PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
308         {NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
309         {0, NULL, 0, NULL, NULL}
310 };
311
312 static int console_move_exec(bContext *C, wmOperator *op)
313 {
314         ConsoleLine *ci = console_history_verify(C);
315
316         int type = RNA_enum_get(op->ptr, "type");
317         bool done = false;
318         int pos;
319
320         switch (type) {
321                 case LINE_BEGIN:
322                         pos = ci->cursor;
323                         BLI_str_cursor_step_utf8(ci->line, ci->len,
324                                                  &pos, STRCUR_DIR_PREV,
325                                                  STRCUR_JUMP_ALL, true);
326                         done = console_line_cursor_set(ci, pos);
327                         break;
328                 case LINE_END:
329                         pos = ci->cursor;
330                         BLI_str_cursor_step_utf8(ci->line, ci->len,
331                                                  &pos, STRCUR_DIR_NEXT,
332                                                  STRCUR_JUMP_ALL, true);
333                         done = console_line_cursor_set(ci, pos);
334                         break;
335                 case PREV_CHAR:
336                         pos = ci->cursor;
337                         BLI_str_cursor_step_utf8(ci->line, ci->len,
338                                                  &pos, STRCUR_DIR_PREV,
339                                                  STRCUR_JUMP_NONE, true);
340                         done = console_line_cursor_set(ci, pos);
341                         break;
342                 case NEXT_CHAR:
343                         pos = ci->cursor;
344                         BLI_str_cursor_step_utf8(ci->line, ci->len,
345                                                  &pos, STRCUR_DIR_NEXT,
346                                                  STRCUR_JUMP_NONE, true);
347                         done = console_line_cursor_set(ci, pos);
348                         break;
349
350                 /* - if the character is a delimiter then skip delimiters (including white space)
351                  * - when jump over the word */
352                 case PREV_WORD:
353                         pos = ci->cursor;
354                         BLI_str_cursor_step_utf8(ci->line, ci->len,
355                                                  &pos, STRCUR_DIR_PREV,
356                                                  STRCUR_JUMP_DELIM, true);
357                         done = console_line_cursor_set(ci, pos);
358                         break;
359                 case NEXT_WORD:
360                         pos = ci->cursor;
361                         BLI_str_cursor_step_utf8(ci->line, ci->len,
362                                                  &pos, STRCUR_DIR_NEXT,
363                                                  STRCUR_JUMP_DELIM, true);
364                         done = console_line_cursor_set(ci, pos);
365                         break;
366         }
367
368         if (done) {
369                 ScrArea *sa = CTX_wm_area(C);
370                 ARegion *ar = CTX_wm_region(C);
371
372                 ED_area_tag_redraw(sa);
373                 console_scroll_bottom(ar);
374         }
375
376
377         return OPERATOR_FINISHED;
378 }
379
380 void CONSOLE_OT_move(wmOperatorType *ot)
381 {
382         /* identifiers */
383         ot->name = "Move Cursor";
384         ot->description = "Move cursor position";
385         ot->idname = "CONSOLE_OT_move";
386
387         /* api callbacks */
388         ot->exec = console_move_exec;
389         ot->poll = ED_operator_console_active;
390
391         /* properties */
392         RNA_def_enum(ot->srna, "type", console_move_type_items, LINE_BEGIN, "Type", "Where to move cursor to");
393 }
394
395 #define TAB_LENGTH 4
396 static int console_insert_exec(bContext *C, wmOperator *op)
397 {
398         SpaceConsole *sc = CTX_wm_space_console(C);
399         ARegion *ar = CTX_wm_region(C);
400         ConsoleLine *ci = console_history_verify(C);
401         char *str = RNA_string_get_alloc(op->ptr, "text", NULL, 0);
402         int len;
403
404         if (str[0] == '\t' && str[1] == '\0') {
405                 len = TAB_LENGTH;
406                 MEM_freeN(str);
407                 str = MEM_mallocN(len + 1, "insert_exec");
408                 memset(str, ' ', len);
409                 str[len] = '\0';
410         }
411
412         len = console_line_insert(ci, str);
413
414         MEM_freeN(str);
415
416         if (len == 0) {
417                 return OPERATOR_CANCELLED;
418         }
419         else {
420                 console_select_offset(sc, len);
421         }
422
423         console_textview_update_rect(sc, ar);
424         ED_area_tag_redraw(CTX_wm_area(C));
425
426         console_scroll_bottom(ar);
427
428         return OPERATOR_FINISHED;
429 }
430
431 static int console_insert_invoke(bContext *C, wmOperator *op, const wmEvent *event)
432 {
433         // if (!RNA_struct_property_is_set(op->ptr, "text")) { /* always set from keymap XXX */
434         if (!RNA_string_length(op->ptr, "text")) {
435                 /* if alt/ctrl/super are pressed pass through except for utf8 character event
436                  * (when input method are used for utf8 inputs, the user may assign key event
437                  * including alt/ctrl/super like ctrl+m to commit utf8 string.  in such case,
438                  * the modifiers in the utf8 character event make no sense.) */
439                 if ((event->ctrl || event->oskey) && !event->utf8_buf[0]) {
440                         return OPERATOR_PASS_THROUGH;
441                 }
442                 else {
443                         char str[BLI_UTF8_MAX + 1];
444                         size_t len;
445
446                         if (event->utf8_buf[0]) {
447                                 len = BLI_str_utf8_size_safe(event->utf8_buf);
448                                 memcpy(str, event->utf8_buf, len);
449                         }
450                         else {
451                                 /* in theory, ghost can set value to extended ascii here */
452                                 len = BLI_str_utf8_from_unicode(event->ascii, str);
453                         }
454                         str[len] = '\0';
455                         RNA_string_set(op->ptr, "text", str);
456                 }
457         }
458         return console_insert_exec(C, op);
459 }
460
461 void CONSOLE_OT_insert(wmOperatorType *ot)
462 {
463         PropertyRNA *prop;
464
465         /* identifiers */
466         ot->name = "Insert";
467         ot->description = "Insert text at cursor position";
468         ot->idname = "CONSOLE_OT_insert";
469
470         /* api callbacks */
471         ot->exec = console_insert_exec;
472         ot->invoke = console_insert_invoke;
473         ot->poll = ED_operator_console_active;
474
475         /* properties */
476         prop = RNA_def_string(ot->srna, "text", NULL, 0, "Text", "Text to insert at the cursor position");
477         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
478 }
479
480 static int console_indent_exec(bContext *C, wmOperator *UNUSED(op))
481 {
482         SpaceConsole *sc = CTX_wm_space_console(C);
483         ARegion *ar = CTX_wm_region(C);
484         ConsoleLine *ci = console_history_verify(C);
485         int spaces;
486         int len;
487
488         for (spaces = 0; spaces < ci->len; spaces++) {
489                 if (ci->line[spaces] != ' ')
490                         break;
491         }
492
493         len = TAB_LENGTH - spaces % TAB_LENGTH;
494
495         console_line_verify_length(ci, ci->len + len);
496
497         memmove(ci->line + len, ci->line, ci->len + 1);
498         memset(ci->line, ' ', len);
499         ci->len += len;
500         BLI_assert(ci->len >= 0);
501         console_line_cursor_set(ci, ci->cursor + len);
502         console_select_offset(sc, len);
503
504         console_textview_update_rect(sc, ar);
505         ED_area_tag_redraw(CTX_wm_area(C));
506
507         console_scroll_bottom(ar);
508
509         return OPERATOR_FINISHED;
510 }
511
512 void CONSOLE_OT_indent(wmOperatorType *ot)
513 {
514         /* identifiers */
515         ot->name = "Indent";
516         ot->description = "Add 4 spaces at line beginning";
517         ot->idname = "CONSOLE_OT_indent";
518
519         /* api callbacks */
520         ot->exec = console_indent_exec;
521         ot->poll = ED_operator_console_active;
522 }
523
524 static int console_unindent_exec(bContext *C, wmOperator *UNUSED(op))
525 {
526         SpaceConsole *sc = CTX_wm_space_console(C);
527         ARegion *ar = CTX_wm_region(C);
528         ConsoleLine *ci = console_history_verify(C);
529         int spaces;
530         int len;
531
532         for (spaces = 0; spaces < ci->len; spaces++) {
533                 if (ci->line[spaces] != ' ')
534                         break;
535         }
536
537         if (spaces == 0)
538                 return OPERATOR_CANCELLED;
539
540         len = spaces % TAB_LENGTH;
541         if (len == 0)
542                 len = TAB_LENGTH;
543
544         console_line_verify_length(ci, ci->len - len);
545
546         memmove(ci->line, ci->line + len, (ci->len - len) + 1);
547         ci->len -= len;
548         BLI_assert(ci->len >= 0);
549
550         console_line_cursor_set(ci, ci->cursor - len);
551         console_select_offset(sc, -len);
552
553         console_textview_update_rect(sc, ar);
554         ED_area_tag_redraw(CTX_wm_area(C));
555
556         console_scroll_bottom(ar);
557
558         return OPERATOR_FINISHED;
559 }
560
561 void CONSOLE_OT_unindent(wmOperatorType *ot)
562 {
563         /* identifiers */
564         ot->name = "Unindent";
565         ot->description = "Delete 4 spaces from line beginning";
566         ot->idname = "CONSOLE_OT_unindent";
567
568         /* api callbacks */
569         ot->exec = console_unindent_exec;
570         ot->poll = ED_operator_console_active;
571 }
572
573 static const EnumPropertyItem console_delete_type_items[] = {
574         {DEL_NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
575         {DEL_PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
576         {DEL_NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
577         {DEL_PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
578         {0, NULL, 0, NULL, NULL}
579 };
580
581 static int console_delete_exec(bContext *C, wmOperator *op)
582 {
583         SpaceConsole *sc = CTX_wm_space_console(C);
584         ARegion *ar = CTX_wm_region(C);
585         ConsoleLine *ci = console_history_verify(C);
586         int pos;
587         int stride;
588
589         const short type = RNA_enum_get(op->ptr, "type");
590         bool done = false;
591
592         if (ci->len == 0) {
593                 return OPERATOR_CANCELLED;
594         }
595
596         switch (type) {
597                 case DEL_NEXT_CHAR:
598                 case DEL_NEXT_WORD:
599                         if (ci->cursor < ci->len) {
600                                 pos = ci->cursor;
601                                 BLI_str_cursor_step_utf8(ci->line, ci->len,
602                                                          &pos, STRCUR_DIR_NEXT,
603                                                          (type == DEL_NEXT_CHAR) ? STRCUR_JUMP_NONE : STRCUR_JUMP_DELIM, true);
604                                 stride = pos - ci->cursor;
605                                 if (stride) {
606                                         memmove(ci->line + ci->cursor, ci->line + ci->cursor + stride, (ci->len - (ci->cursor + stride)) + 1);
607                                         ci->len -= stride;
608                                         BLI_assert(ci->len >= 0);
609                                         done = true;
610                                 }
611                         }
612                         break;
613                 case DEL_PREV_CHAR:
614                 case DEL_PREV_WORD:
615                         if (ci->cursor > 0) {
616                                 pos = ci->cursor;
617                                 BLI_str_cursor_step_utf8(ci->line, ci->len,
618                                                          &pos, STRCUR_DIR_PREV,
619                                                          (type == DEL_PREV_CHAR) ? STRCUR_JUMP_NONE : STRCUR_JUMP_DELIM, true);
620                                 stride = ci->cursor - pos;
621                                 if (stride) {
622                                         ci->cursor -= stride; /* same as above */
623                                         memmove(ci->line + ci->cursor, ci->line + ci->cursor + stride, (ci->len - (ci->cursor + stride)) + 1);
624                                         ci->len -= stride;
625                                         BLI_assert(ci->len >= 0);
626                                         done = true;
627                                 }
628                         }
629                         break;
630         }
631
632         if (!done) {
633                 return OPERATOR_CANCELLED;
634         }
635         else {
636                 console_select_offset(sc, -stride);
637         }
638
639         console_textview_update_rect(sc, ar);
640         ED_area_tag_redraw(CTX_wm_area(C));
641
642         console_scroll_bottom(ar);
643
644         return OPERATOR_FINISHED;
645 }
646
647
648 void CONSOLE_OT_delete(wmOperatorType *ot)
649 {
650         /* identifiers */
651         ot->name = "Delete";
652         ot->description = "Delete text by cursor position";
653         ot->idname = "CONSOLE_OT_delete";
654
655         /* api callbacks */
656         ot->exec = console_delete_exec;
657         ot->poll = ED_operator_console_active;
658
659         /* properties */
660         RNA_def_enum(ot->srna, "type", console_delete_type_items, DEL_NEXT_CHAR, "Type", "Which part of the text to delete");
661 }
662
663 static int console_clear_line_exec(bContext *C, wmOperator *UNUSED(op))
664 {
665         SpaceConsole *sc = CTX_wm_space_console(C);
666         ARegion *ar = CTX_wm_region(C);
667         ConsoleLine *ci = console_history_verify(C);
668
669         if (ci->len == 0) {
670                 return OPERATOR_CANCELLED;
671         }
672
673         console_history_add(sc, ci);
674         console_history_add(sc, NULL);
675         console_select_offset(sc, -ci->len);
676
677         console_textview_update_rect(sc, ar);
678
679         ED_area_tag_redraw(CTX_wm_area(C));
680
681         console_scroll_bottom(ar);
682
683         return OPERATOR_FINISHED;
684 }
685
686 void CONSOLE_OT_clear_line(wmOperatorType *ot)
687 {
688         /* identifiers */
689         ot->name = "Clear Line";
690         ot->description = "Clear the line and store in history";
691         ot->idname = "CONSOLE_OT_clear_line";
692
693         /* api callbacks */
694         ot->exec = console_clear_line_exec;
695         ot->poll = ED_operator_console_active;
696 }
697
698 /* the python exec operator uses this */
699 static int console_clear_exec(bContext *C, wmOperator *op)
700 {
701         SpaceConsole *sc = CTX_wm_space_console(C);
702         ARegion *ar = CTX_wm_region(C);
703
704         const bool scrollback = RNA_boolean_get(op->ptr, "scrollback");
705         const bool history = RNA_boolean_get(op->ptr, "history");
706
707         /*ConsoleLine *ci = */ console_history_verify(C);
708
709         if (scrollback) { /* last item in mistory */
710                 while (sc->scrollback.first)
711                         console_scrollback_free(sc, sc->scrollback.first);
712         }
713
714         if (history) {
715                 while (sc->history.first)
716                         console_history_free(sc, sc->history.first);
717                 console_history_verify(C);
718         }
719
720         console_textview_update_rect(sc, ar);
721         ED_area_tag_redraw(CTX_wm_area(C));
722
723         return OPERATOR_FINISHED;
724 }
725
726 void CONSOLE_OT_clear(wmOperatorType *ot)
727 {
728         /* identifiers */
729         ot->name = "Clear";
730         ot->description = "Clear text by type";
731         ot->idname = "CONSOLE_OT_clear";
732
733         /* api callbacks */
734         ot->exec = console_clear_exec;
735         ot->poll = ED_operator_console_active;
736
737         /* properties */
738         RNA_def_boolean(ot->srna, "scrollback", 1, "Scrollback", "Clear the scrollback history");
739         RNA_def_boolean(ot->srna, "history", 0, "History", "Clear the command history");
740 }
741
742
743
744 /* the python exec operator uses this */
745 static int console_history_cycle_exec(bContext *C, wmOperator *op)
746 {
747         SpaceConsole *sc = CTX_wm_space_console(C);
748         ARegion *ar = CTX_wm_region(C);
749
750         ConsoleLine *ci = console_history_verify(C); /* TODO - stupid, just prevents crashes when no command line */
751         const bool reverse = RNA_boolean_get(op->ptr, "reverse"); /* assumes down, reverse is up */
752         int prev_len = ci->len;
753
754         /* keep a copy of the line above so when history is cycled
755          * this is the only function that needs to know about the double-up */
756         if (ci->prev) {
757                 ConsoleLine *ci_prev = (ConsoleLine *)ci->prev;
758
759                 if (STREQ(ci->line, ci_prev->line))
760                         console_history_free(sc, ci_prev);
761         }
762
763         if (reverse) { /* last item in history */
764                 ci = sc->history.last;
765                 BLI_remlink(&sc->history, ci);
766                 BLI_addhead(&sc->history, ci);
767         }
768         else {
769                 ci = sc->history.first;
770                 BLI_remlink(&sc->history, ci);
771                 BLI_addtail(&sc->history, ci);
772         }
773
774         {   /* add a duplicate of the new arg and remove all other instances */
775                 ConsoleLine *cl;
776                 while ((cl = console_history_find(sc, ci->line, ci)))
777                         console_history_free(sc, cl);
778
779                 console_history_add(sc, (ConsoleLine *)sc->history.last);
780         }
781
782         ci = sc->history.last;
783         console_select_offset(sc, ci->len - prev_len);
784
785         /* could be wrapped so update scroll rect */
786         console_textview_update_rect(sc, ar);
787         ED_area_tag_redraw(CTX_wm_area(C));
788
789         console_scroll_bottom(ar);
790
791         return OPERATOR_FINISHED;
792 }
793
794 void CONSOLE_OT_history_cycle(wmOperatorType *ot)
795 {
796         /* identifiers */
797         ot->name = "History Cycle";
798         ot->description = "Cycle through history";
799         ot->idname = "CONSOLE_OT_history_cycle";
800
801         /* api callbacks */
802         ot->exec = console_history_cycle_exec;
803         ot->poll = ED_operator_console_active;
804
805         /* properties */
806         RNA_def_boolean(ot->srna, "reverse", 0, "Reverse", "Reverse cycle history");
807 }
808
809
810 /* the python exec operator uses this */
811 static int console_history_append_exec(bContext *C, wmOperator *op)
812 {
813         SpaceConsole *sc = CTX_wm_space_console(C);
814         ARegion *ar = CTX_wm_region(C);
815         ScrArea *sa = CTX_wm_area(C);
816         ConsoleLine *ci = console_history_verify(C);
817         char *str = RNA_string_get_alloc(op->ptr, "text", NULL, 0); /* own this text in the new line, don't free */
818         int cursor = RNA_int_get(op->ptr, "current_character");
819         const bool rem_dupes = RNA_boolean_get(op->ptr, "remove_duplicates");
820         int prev_len = ci->len;
821
822         if (rem_dupes) {
823                 ConsoleLine *cl;
824
825                 while ((cl = console_history_find(sc, ci->line, ci)))
826                         console_history_free(sc, cl);
827
828                 if (STREQ(str, ci->line)) {
829                         MEM_freeN(str);
830                         return OPERATOR_FINISHED;
831                 }
832         }
833
834         ci = console_history_add_str(sc, str, 1); /* own the string */
835         console_select_offset(sc, ci->len - prev_len);
836         console_line_cursor_set(ci, cursor);
837
838         ED_area_tag_redraw(sa);
839
840         /* when calling render modally this can be NULL when calling:
841          * bpy.ops.render.render('INVOKE_DEFAULT') */
842         if (ar) {
843                 console_scroll_bottom(ar);
844         }
845
846         return OPERATOR_FINISHED;
847 }
848
849 void CONSOLE_OT_history_append(wmOperatorType *ot)
850 {
851         /* identifiers */
852         ot->name = "History Append";
853         ot->description = "Append history at cursor position";
854         ot->idname = "CONSOLE_OT_history_append";
855
856         /* api callbacks */
857         ot->exec = console_history_append_exec;
858         ot->poll = ED_operator_console_active;
859
860         /* properties */
861         RNA_def_string(ot->srna, "text", NULL, 0, "Text", "Text to insert at the cursor position");
862         RNA_def_int(ot->srna, "current_character", 0, 0, INT_MAX, "Cursor", "The index of the cursor", 0, 10000);
863         RNA_def_boolean(ot->srna, "remove_duplicates", 0, "Remove Duplicates", "Remove duplicate items in the history");
864 }
865
866
867 /* the python exec operator uses this */
868 static int console_scrollback_append_exec(bContext *C, wmOperator *op)
869 {
870         SpaceConsole *sc = CTX_wm_space_console(C);
871         ARegion *ar = CTX_wm_region(C);
872         ConsoleLine *ci;
873
874         char *str = RNA_string_get_alloc(op->ptr, "text", NULL, 0); /* own this text in the new line, don't free */
875         int type = RNA_enum_get(op->ptr, "type");
876
877         console_history_verify(C);
878
879         ci = console_scrollback_add_str(sc, str, 1); /* own the string */
880         ci->type = type;
881
882         console_scrollback_limit(sc);
883
884         /* 'ar' can be null depending on the operator that runs
885          * rendering with invoke default for eg causes this */
886         if (ar) {
887                 console_textview_update_rect(sc, ar);
888         }
889
890         ED_area_tag_redraw(CTX_wm_area(C));
891
892         return OPERATOR_FINISHED;
893 }
894
895 void CONSOLE_OT_scrollback_append(wmOperatorType *ot)
896 {
897         /* defined in DNA_space_types.h */
898         static const EnumPropertyItem console_line_type_items[] = {
899                 {CONSOLE_LINE_OUTPUT,   "OUTPUT", 0, "Output", ""},
900                 {CONSOLE_LINE_INPUT,    "INPUT", 0, "Input", ""},
901                 {CONSOLE_LINE_INFO,     "INFO", 0, "Information", ""},
902                 {CONSOLE_LINE_ERROR,    "ERROR", 0, "Error", ""},
903                 {0, NULL, 0, NULL, NULL}
904         };
905
906         /* identifiers */
907         ot->name = "Scrollback Append";
908         ot->description = "Append scrollback text by type";
909         ot->idname = "CONSOLE_OT_scrollback_append";
910
911         /* api callbacks */
912         ot->exec = console_scrollback_append_exec;
913         ot->poll = ED_operator_console_active;
914
915         /* properties */
916         RNA_def_string(ot->srna, "text", NULL, 0, "Text", "Text to insert at the cursor position");
917         RNA_def_enum(ot->srna, "type", console_line_type_items, CONSOLE_LINE_OUTPUT, "Type", "Console output type");
918 }
919
920
921 static int console_copy_exec(bContext *C, wmOperator *UNUSED(op))
922 {
923         SpaceConsole *sc = CTX_wm_space_console(C);
924
925         DynStr *buf_dyn;
926         char *buf_str;
927
928         ConsoleLine *cl;
929         int sel[2];
930         int offset = 0;
931
932         ConsoleLine cl_dummy = {NULL};
933
934         if (sc->sel_start == sc->sel_end)
935                 return OPERATOR_CANCELLED;
936
937         console_scrollback_prompt_begin(sc, &cl_dummy);
938
939         for (cl = sc->scrollback.first; cl; cl = cl->next) {
940                 offset += cl->len + 1;
941         }
942
943         if (offset == 0) {
944                 console_scrollback_prompt_end(sc, &cl_dummy);
945                 return OPERATOR_CANCELLED;
946         }
947
948         buf_dyn = BLI_dynstr_new();
949         offset -= 1;
950         sel[0] = offset - sc->sel_end;
951         sel[1] = offset - sc->sel_start;
952
953         for (cl = sc->scrollback.first; cl; cl = cl->next) {
954                 if (sel[0] <= cl->len && sel[1] >= 0) {
955                         int sta = max_ii(sel[0], 0);
956                         int end = min_ii(sel[1], cl->len);
957
958                         if (BLI_dynstr_get_len(buf_dyn))
959                                 BLI_dynstr_append(buf_dyn, "\n");
960
961                         BLI_dynstr_nappend(buf_dyn, cl->line + sta, end - sta);
962                 }
963
964                 sel[0] -= cl->len + 1;
965                 sel[1] -= cl->len + 1;
966         }
967
968         buf_str = BLI_dynstr_get_cstring(buf_dyn);
969
970         BLI_dynstr_free(buf_dyn);
971         WM_clipboard_text_set(buf_str, 0);
972
973         MEM_freeN(buf_str);
974
975         console_scrollback_prompt_end(sc, &cl_dummy);
976
977         return OPERATOR_FINISHED;
978 }
979
980 void CONSOLE_OT_copy(wmOperatorType *ot)
981 {
982         /* identifiers */
983         ot->name = "Copy to Clipboard";
984         ot->description = "Copy selected text to clipboard";
985         ot->idname = "CONSOLE_OT_copy";
986
987         /* api callbacks */
988         ot->poll = ED_operator_console_active;
989         ot->exec = console_copy_exec;
990
991         /* properties */
992 }
993
994 static int console_paste_exec(bContext *C, wmOperator *UNUSED(op))
995 {
996         SpaceConsole *sc = CTX_wm_space_console(C);
997         ARegion *ar = CTX_wm_region(C);
998         ConsoleLine *ci = console_history_verify(C);
999         int buf_len;
1000
1001         char *buf_str = WM_clipboard_text_get(false, &buf_len);
1002         char *buf_step, *buf_next;
1003
1004         if (buf_str == NULL)
1005                 return OPERATOR_CANCELLED;
1006
1007         buf_step = buf_str;
1008
1009         while ((buf_next = buf_step) && buf_next[0] != '\0') {
1010                 buf_step = strchr(buf_next, '\n');
1011                 if (buf_step) {
1012                         *buf_step = '\0';
1013                         buf_step++;
1014                 }
1015
1016                 if (buf_next != buf_str) {
1017                         WM_operator_name_call(C, "CONSOLE_OT_execute", WM_OP_EXEC_DEFAULT, NULL);
1018                         ci = console_history_verify(C);
1019                 }
1020
1021                 console_select_offset(sc, console_line_insert(ci, buf_next));
1022         }
1023
1024         MEM_freeN(buf_str);
1025
1026         console_textview_update_rect(sc, ar);
1027         ED_area_tag_redraw(CTX_wm_area(C));
1028
1029         console_scroll_bottom(ar);
1030
1031         return OPERATOR_FINISHED;
1032 }
1033
1034 void CONSOLE_OT_paste(wmOperatorType *ot)
1035 {
1036         /* identifiers */
1037         ot->name = "Paste from Clipboard";
1038         ot->description = "Paste text from clipboard";
1039         ot->idname = "CONSOLE_OT_paste";
1040
1041         /* api callbacks */
1042         ot->poll = ED_operator_console_active;
1043         ot->exec = console_paste_exec;
1044
1045         /* properties */
1046 }
1047
1048 typedef struct SetConsoleCursor {
1049         int sel_old[2];
1050         int sel_init;
1051 } SetConsoleCursor;
1052
1053 // TODO, cursor placement without selection
1054 static void console_cursor_set_to_pos(SpaceConsole *sc, ARegion *ar, SetConsoleCursor *scu, int mval[2], int UNUSED(sel))
1055 {
1056         int pos;
1057         pos = console_char_pick(sc, ar, mval);
1058
1059         if (scu->sel_init == INT_MAX) {
1060                 scu->sel_init = pos;
1061                 sc->sel_start = sc->sel_end = pos;
1062                 return;
1063         }
1064
1065         if (pos < scu->sel_init) {
1066                 sc->sel_start = pos;
1067                 sc->sel_end = scu->sel_init;
1068         }
1069         else if (pos > sc->sel_start) {
1070                 sc->sel_start = scu->sel_init;
1071                 sc->sel_end = pos;
1072         }
1073         else {
1074                 sc->sel_start = sc->sel_end = pos;
1075         }
1076 }
1077
1078 static void console_modal_select_apply(bContext *C, wmOperator *op, const wmEvent *event)
1079 {
1080         SpaceConsole *sc = CTX_wm_space_console(C);
1081         ARegion *ar = CTX_wm_region(C);
1082         SetConsoleCursor *scu = op->customdata;
1083         int mval[2];
1084         int sel_prev[2];
1085
1086         mval[0] = event->mval[0];
1087         mval[1] = event->mval[1];
1088
1089         sel_prev[0] = sc->sel_start;
1090         sel_prev[1] = sc->sel_end;
1091
1092         console_cursor_set_to_pos(sc, ar, scu, mval, true);
1093
1094         /* only redraw if the selection changed */
1095         if (sel_prev[0] != sc->sel_start || sel_prev[1] != sc->sel_end) {
1096                 ED_area_tag_redraw(CTX_wm_area(C));
1097         }
1098 }
1099
1100 static void console_cursor_set_exit(bContext *UNUSED(C), wmOperator *op)
1101 {
1102 //      SpaceConsole *sc = CTX_wm_space_console(C);
1103         SetConsoleCursor *scu = op->customdata;
1104
1105 #if 0
1106         if (txt_has_sel(text)) {
1107                 buffer = txt_sel_to_buf(text);
1108                 WM_clipboard_text_set(buffer, 1);
1109                 MEM_freeN(buffer);
1110         }
1111 #endif
1112
1113         MEM_freeN(scu);
1114 }
1115
1116 static int console_modal_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1117 {
1118         SpaceConsole *sc = CTX_wm_space_console(C);
1119 //      ARegion *ar = CTX_wm_region(C);
1120         SetConsoleCursor *scu;
1121
1122         op->customdata = MEM_callocN(sizeof(SetConsoleCursor), "SetConsoleCursor");
1123         scu = op->customdata;
1124
1125         scu->sel_old[0] = sc->sel_start;
1126         scu->sel_old[1] = sc->sel_end;
1127
1128         scu->sel_init = INT_MAX;
1129
1130         WM_event_add_modal_handler(C, op);
1131
1132         console_modal_select_apply(C, op, event);
1133
1134         return OPERATOR_RUNNING_MODAL;
1135 }
1136
1137 static int console_modal_select(bContext *C, wmOperator *op, const wmEvent *event)
1138 {
1139         switch (event->type) {
1140                 case LEFTMOUSE:
1141                 case MIDDLEMOUSE:
1142                 case RIGHTMOUSE:
1143                         if (event->val == KM_RELEASE) {
1144                                 console_cursor_set_exit(C, op);
1145                                 return OPERATOR_FINISHED;
1146                         }
1147                         break;
1148                 case MOUSEMOVE:
1149                         console_modal_select_apply(C, op, event);
1150                         break;
1151         }
1152
1153         return OPERATOR_RUNNING_MODAL;
1154 }
1155
1156 static void console_modal_select_cancel(bContext *C, wmOperator *op)
1157 {
1158         console_cursor_set_exit(C, op);
1159 }
1160
1161 void CONSOLE_OT_select_set(wmOperatorType *ot)
1162 {
1163         /* identifiers */
1164         ot->name = "Set Selection";
1165         ot->idname = "CONSOLE_OT_select_set";
1166         ot->description = "Set the console selection";
1167
1168         /* api callbacks */
1169         ot->invoke = console_modal_select_invoke;
1170         ot->modal = console_modal_select;
1171         ot->cancel = console_modal_select_cancel;
1172         ot->poll = ED_operator_console_active;
1173 }
1174
1175 static int console_selectword_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1176 {
1177         SpaceConsole *sc = CTX_wm_space_console(C);
1178         ARegion *ar = CTX_wm_region(C);
1179
1180         ConsoleLine cl_dummy = {NULL};
1181         ConsoleLine *cl;
1182         int ret = OPERATOR_CANCELLED;
1183         int pos, offset, n;
1184
1185         pos = console_char_pick(sc, ar, event->mval);
1186
1187         console_scrollback_prompt_begin(sc, &cl_dummy);
1188
1189         if (console_line_column_from_index(sc, pos, &cl, &offset, &n)) {
1190                 int sel[2] = {n, n};
1191
1192                 BLI_str_cursor_step_utf8(
1193                         cl->line, cl->len,
1194                         &sel[0], STRCUR_DIR_NEXT,
1195                         STRCUR_JUMP_DELIM, true);
1196
1197                 BLI_str_cursor_step_utf8(
1198                         cl->line, cl->len,
1199                         &sel[1], STRCUR_DIR_PREV,
1200                         STRCUR_JUMP_DELIM, true);
1201
1202                 sel[0] = offset - sel[0];
1203                 sel[1] = offset - sel[1];
1204
1205                 if ((sel[0] != sc->sel_start) || (sel[1] != sc->sel_end)) {
1206                         sc->sel_start = sel[0];
1207                         sc->sel_end   = sel[1];
1208                         ED_area_tag_redraw(CTX_wm_area(C));
1209                         ret = OPERATOR_FINISHED;
1210                 }
1211         }
1212
1213         console_scrollback_prompt_end(sc, &cl_dummy);
1214         return ret;
1215 }
1216
1217 void CONSOLE_OT_select_word(wmOperatorType *ot)
1218 {
1219         /* identifiers */
1220         ot->name = "Select Word";
1221         ot->description = "Select word at cursor position";
1222         ot->idname = "CONSOLE_OT_select_word";
1223
1224         /* api callbacks */
1225         ot->invoke = console_selectword_invoke;
1226         ot->poll = ED_operator_console_active;
1227 }