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