- added GCC warning -Wstrict-prototypes
[blender-staging.git] / source / blender / editors / space_console / console_ops.c
1 /**
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * Contributor(s): Campbell Barton
21  *
22  * ***** END GPL LICENSE BLOCK *****
23  */
24
25 #include <stdlib.h>
26 #include <string.h>
27 #include <ctype.h> /* ispunct */
28 #include <sys/stat.h>
29
30 #include "MEM_guardedalloc.h"
31
32 #include "DNA_userdef_types.h"
33
34 #include "BLI_blenlib.h"
35 #include "BLI_dynstr.h"
36
37 #include "BKE_context.h"
38 #include "BKE_text.h" /* only for character utility funcs */
39
40 #include "WM_api.h"
41 #include "WM_types.h"
42
43 #include "UI_view2d.h"
44 #include "ED_screen.h"
45
46 #include "RNA_access.h"
47 #include "RNA_define.h"
48
49 #include "console_intern.h"
50
51 static void console_textview_update_rect(SpaceConsole *sc, ARegion *ar)
52 {
53         View2D *v2d= &ar->v2d;
54
55         UI_view2d_totRect_set(v2d, ar->winx-1, console_textview_height(sc, ar));
56 }
57
58 static void console_select_offset(SpaceConsole *sc, const int offset)
59 {
60         sc->sel_start += offset;
61         sc->sel_end += offset;
62 }
63
64 void console_history_free(SpaceConsole *sc, ConsoleLine *cl)
65 {
66         BLI_remlink(&sc->history, cl);
67         MEM_freeN(cl->line);
68         MEM_freeN(cl);
69 }
70 void console_scrollback_free(SpaceConsole *sc, ConsoleLine *cl)
71 {
72         BLI_remlink(&sc->scrollback, cl);
73         MEM_freeN(cl->line);
74         MEM_freeN(cl);
75 }
76
77 void console_scrollback_limit(SpaceConsole *sc)
78 {
79         int tot;
80         
81         if (U.scrollback < 32) U.scrollback= 128; // XXX - save in user defaults
82         
83         for(tot= BLI_countlist(&sc->scrollback); tot > U.scrollback; tot--)
84                 console_scrollback_free(sc, sc->scrollback.first);
85 }
86
87 static ConsoleLine * console_history_find(SpaceConsole *sc, const char *str, ConsoleLine *cl_ignore)
88 {
89         ConsoleLine *cl;
90
91         for(cl= sc->history.last; cl; cl= cl->prev) {
92                 if (cl==cl_ignore)
93                         continue;
94
95                 if(strcmp(str, cl->line)==0)
96                         return cl;
97         }
98
99         return NULL;
100 }
101
102 /* return 0 if no change made, clamps the range */
103 static int console_line_cursor_set(ConsoleLine *cl, int cursor)
104 {
105         int cursor_new;
106         
107         if(cursor < 0)                          cursor_new= 0;
108         else if(cursor > cl->len)       cursor_new= cl->len;
109         else                                            cursor_new= cursor;
110         
111         if(cursor_new == cl->cursor)
112                 return 0;
113         
114         cl->cursor= cursor_new;
115         return 1;
116 }
117
118 static char cursor_char(ConsoleLine *cl)
119 {
120         /* assume cursor is clamped */
121         return cl->line[cl->cursor];
122 }
123
124 static char cursor_char_prev(ConsoleLine *cl)
125 {
126         /* assume cursor is clamped */
127         if(cl->cursor <= 0)
128                 return '\0';
129
130         return cl->line[cl->cursor-1];
131 }
132
133 #if 0 // XXX unused 
134 static char cursor_char_next(ConsoleLine *cl)
135 {
136         /* assume cursor is clamped */
137         if(cl->cursor + 1 >= cl->len)
138                 return '\0';
139
140         return cl->line[cl->cursor+1];
141 }
142
143 static void console_lb_debug__internal(ListBase *lb)
144 {
145         ConsoleLine *cl;
146
147         printf("%d: ", BLI_countlist(lb));
148         for(cl= lb->first; cl; cl= cl->next)
149                 printf("<%s> ", cl->line);
150         printf("\n");
151
152 }
153
154 static void console_history_debug(const bContext *C)
155 {
156         SpaceConsole *sc= CTX_wm_space_console(C);
157
158         
159         console_lb_debug__internal(&sc->history);
160 }
161 #endif
162
163 static ConsoleLine *console_lb_add__internal(ListBase *lb, ConsoleLine *from)
164 {
165         ConsoleLine *ci= MEM_callocN(sizeof(ConsoleLine), "ConsoleLine Add");
166         
167         if(from) {
168                 ci->line= BLI_strdup(from->line);
169                 ci->len= strlen(ci->line);
170                 ci->len_alloc= ci->len;
171                 
172                 ci->cursor= from->cursor;
173                 ci->type= from->type;
174         } else {
175                 ci->line= MEM_callocN(64, "console-in-line");
176                 ci->len_alloc= 64;
177                 ci->len= 0;
178         }
179         
180         BLI_addtail(lb, ci);
181         return ci;
182 }
183
184 static ConsoleLine *console_history_add(const bContext *C, ConsoleLine *from)
185 {
186         SpaceConsole *sc= CTX_wm_space_console(C);
187         
188         return console_lb_add__internal(&sc->history, from);
189 }
190
191 #if 0 /* may use later ? */
192 static ConsoleLine *console_scrollback_add(const bContext *C, ConsoleLine *from)
193 {
194         SpaceConsole *sc= CTX_wm_space_console(C);
195         
196         return console_lb_add__internal(&sc->scrollback, from);
197 }
198 #endif
199
200 static ConsoleLine *console_lb_add_str__internal(ListBase *lb, char *str, int own)
201 {
202         ConsoleLine *ci= MEM_callocN(sizeof(ConsoleLine), "ConsoleLine Add");
203         if(own)         ci->line= str;
204         else            ci->line= BLI_strdup(str);
205         
206         ci->len = ci->len_alloc = strlen(str);
207         
208         BLI_addtail(lb, ci);
209         return ci;
210 }
211 ConsoleLine *console_history_add_str(SpaceConsole *sc, char *str, int own)
212 {
213         return console_lb_add_str__internal(&sc->history, str, own);
214 }
215 ConsoleLine *console_scrollback_add_str(SpaceConsole *sc, char *str, int own)
216 {
217         ConsoleLine *ci= console_lb_add_str__internal(&sc->scrollback, str, own);
218         console_select_offset(sc, ci->len + 1);
219         return ci;
220 }
221
222 ConsoleLine *console_history_verify(const bContext *C)
223 {
224         SpaceConsole *sc= CTX_wm_space_console(C);
225         ConsoleLine *ci= sc->history.last;
226         if(ci==NULL)
227                 ci= console_history_add(C, NULL);
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                 int new_len= len * 2; /* new length */
238                 char *new_line= MEM_callocN(new_len, "console line");
239                 memcpy(new_line, ci->line, ci->len);
240                 MEM_freeN(ci->line);
241                 
242                 ci->line= new_line;
243                 ci->len_alloc= new_len;
244         }
245 }
246
247 static int console_line_insert(ConsoleLine *ci, char *str)
248 {
249         int len = strlen(str);
250         
251         if(len>0 && str[len-1]=='\n') {/* stop new lines being pasted at the end of lines */
252                 str[len-1]= '\0';
253                 len--;
254         }
255
256         if(len==0)
257                 return 0;
258         
259         console_line_verify_length(ci, len + ci->len);
260         
261         memmove(ci->line+ci->cursor+len, ci->line+ci->cursor, (ci->len - ci->cursor)+1);
262         memcpy(ci->line+ci->cursor, str, len);
263         
264         ci->len += len;
265         ci->cursor += len;
266         
267         return len;
268 }
269
270 /* static funcs for text editing */
271
272 /* similar to the text editor, with some not used. keep compatible */
273 static EnumPropertyItem move_type_items[]= {
274         {LINE_BEGIN, "LINE_BEGIN", 0, "Line Begin", ""},
275         {LINE_END, "LINE_END", 0, "Line End", ""},
276         {PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
277         {NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
278         {PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
279         {NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
280         {0, NULL, 0, NULL, NULL}};
281
282 static int move_exec(bContext *C, wmOperator *op)
283 {
284         ConsoleLine *ci= console_history_verify(C);
285         
286         int type= RNA_enum_get(op->ptr, "type");
287         int done= 0;
288         
289         switch(type) {
290         case LINE_BEGIN:
291                 done= console_line_cursor_set(ci, 0);
292                 break;
293         case LINE_END:
294                 done= console_line_cursor_set(ci, INT_MAX);
295                 break;
296         case PREV_CHAR:
297                 done= console_line_cursor_set(ci, ci->cursor-1);
298                 break;
299         case NEXT_CHAR:
300                 done= console_line_cursor_set(ci, ci->cursor+1);
301                 break;
302
303         /* - if the character is a delimiter then skip delimiters (including white space)
304          * - when jump over the word */
305         case PREV_WORD:
306                 while(text_check_delim(cursor_char_prev(ci)))
307                         if(console_line_cursor_set(ci, ci->cursor-1)==FALSE)
308                                 break;
309
310                 while(text_check_delim(cursor_char_prev(ci))==FALSE)
311                         if(console_line_cursor_set(ci, ci->cursor-1)==FALSE)
312                                 break;
313
314                 /* This isnt used for NEXT_WORD because when going back
315                  * its more useful to have the cursor directly after a word then whitespace */
316                 while(text_check_whitespace(cursor_char_prev(ci))==TRUE)
317                         if(console_line_cursor_set(ci, ci->cursor-1)==FALSE)
318                                 break;
319
320                 done= 1; /* assume changed */
321                 break;
322         case NEXT_WORD:
323                 while(text_check_delim(cursor_char(ci))==TRUE)
324                         if (console_line_cursor_set(ci, ci->cursor+1)==FALSE)
325                                 break;
326
327                 while(text_check_delim(cursor_char(ci))==FALSE)
328                         if (console_line_cursor_set(ci, ci->cursor+1)==FALSE)
329                                 break;
330
331                 done= 1; /* assume changed */
332                 break;
333         }
334         
335         if(done) {
336                 ED_area_tag_redraw(CTX_wm_area(C));
337         }
338         
339         return OPERATOR_FINISHED;
340 }
341
342 void CONSOLE_OT_move(wmOperatorType *ot)
343 {
344         /* identifiers */
345         ot->name= "Move Cursor";
346         ot->description= "Move cursor position";
347         ot->idname= "CONSOLE_OT_move";
348         
349         /* api callbacks */
350         ot->exec= move_exec;
351         ot->poll= ED_operator_console_active;
352
353         /* properties */
354         RNA_def_enum(ot->srna, "type", move_type_items, LINE_BEGIN, "Type", "Where to move cursor to.");
355 }
356
357 #define TAB_LENGTH 4
358 static int insert_exec(bContext *C, wmOperator *op)
359 {
360         SpaceConsole *sc= CTX_wm_space_console(C);
361         ARegion *ar= CTX_wm_region(C);
362         ConsoleLine *ci= console_history_verify(C);
363         char *str= RNA_string_get_alloc(op->ptr, "text", NULL, 0);
364         int len;
365
366         // XXX, alligned tab key hack
367         if(str[0]=='\t' && str[1]=='\0') {
368                 len= TAB_LENGTH - (ci->cursor % TAB_LENGTH);
369                 MEM_freeN(str);
370                 str= MEM_mallocN(len + 1, "insert_exec");
371                 memset(str, ' ', len);
372                 str[len]= '\0';
373         }
374
375         len= console_line_insert(ci, str);
376         
377         MEM_freeN(str);
378         
379         if(len==0) {
380                 return OPERATOR_CANCELLED;
381         }
382         else {
383                 console_select_offset(sc, len);
384         }
385
386         console_textview_update_rect(sc, ar);
387         ED_area_tag_redraw(CTX_wm_area(C));
388         
389         return OPERATOR_FINISHED;
390 }
391
392 static int insert_invoke(bContext *C, wmOperator *op, wmEvent *event)
393 {
394         // if(!RNA_property_is_set(op->ptr, "text")) { /* always set from keymap XXX */
395         if(!RNA_string_length(op->ptr, "text")) {
396                 /* if alt/ctrl/super are pressed pass through */
397                 if(event->ctrl || event->oskey) {
398                         return OPERATOR_PASS_THROUGH;
399                 }
400                 else {
401                         char str[2];
402                         str[0]= event->ascii;
403                         str[1]= '\0';
404
405                         RNA_string_set(op->ptr, "text", str);
406                 }
407         }
408         return insert_exec(C, op);
409 }
410
411 void CONSOLE_OT_insert(wmOperatorType *ot)
412 {
413         /* identifiers */
414         ot->name= "Insert";
415         ot->description= "Insert text at cursor position";
416         ot->idname= "CONSOLE_OT_insert";
417         
418         /* api callbacks */
419         ot->exec= insert_exec;
420         ot->invoke= insert_invoke;
421         ot->poll= ED_operator_console_active;
422
423         /* properties */
424         RNA_def_string(ot->srna, "text", "", 0, "Text", "Text to insert at the cursor position.");
425 }
426
427
428 static EnumPropertyItem delete_type_items[]= {
429         {DEL_NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
430         {DEL_PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
431 //      {DEL_NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
432 //      {DEL_PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
433         {0, NULL, 0, NULL, NULL}};
434
435 static int delete_exec(bContext *C, wmOperator *op)
436 {
437         SpaceConsole *sc= CTX_wm_space_console(C);
438         ARegion *ar= CTX_wm_region(C);
439         ConsoleLine *ci= console_history_verify(C);
440
441         const short type= RNA_enum_get(op->ptr, "type");
442         int done = 0;
443         
444         if(ci->len==0) {
445                 return OPERATOR_CANCELLED;
446         }
447         
448         switch(type) {
449         case DEL_NEXT_CHAR:
450                 if(ci->cursor < ci->len) {
451                         memmove(ci->line + ci->cursor, ci->line + ci->cursor+1, (ci->len - ci->cursor)+1);
452                         ci->len--;
453                         done= 1;
454                 }
455                 break;
456         case DEL_PREV_CHAR:
457                 if(ci->cursor > 0) {
458                         ci->cursor--; /* same as above */
459                         memmove(ci->line + ci->cursor, ci->line + ci->cursor+1, (ci->len - ci->cursor)+1);
460                         ci->len--;
461                         done= 1;
462                 }
463                 break;
464         }
465         
466         if(!done) {
467                 return OPERATOR_CANCELLED;
468         }
469         else {
470                 console_select_offset(sc, -1);
471         }
472
473         console_textview_update_rect(sc, ar);
474         ED_area_tag_redraw(CTX_wm_area(C));
475         
476         return OPERATOR_FINISHED;
477 }
478
479
480 void CONSOLE_OT_delete(wmOperatorType *ot)
481 {
482         /* identifiers */
483         ot->name= "Delete";
484         ot->description= "Delete text by cursor position";
485         ot->idname= "CONSOLE_OT_delete";
486         
487         /* api callbacks */
488         ot->exec= delete_exec;
489         ot->poll= ED_operator_console_active;
490
491         /* properties */
492         RNA_def_enum(ot->srna, "type", delete_type_items, DEL_NEXT_CHAR, "Type", "Which part of the text to delete.");
493 }
494
495
496 /* the python exec operator uses this */
497 static int clear_exec(bContext *C, wmOperator *op)
498 {
499         SpaceConsole *sc= CTX_wm_space_console(C);
500         ARegion *ar= CTX_wm_region(C);
501         
502         short scrollback= RNA_boolean_get(op->ptr, "scrollback");
503         short history= RNA_boolean_get(op->ptr, "history");
504         
505         /*ConsoleLine *ci= */ console_history_verify(C);
506         
507         if(scrollback) { /* last item in mistory */
508                 while(sc->scrollback.first)
509                         console_scrollback_free(sc, sc->scrollback.first);
510         }
511         
512         if(history) {
513                 while(sc->history.first)
514                         console_history_free(sc, sc->history.first);
515         }
516
517         console_textview_update_rect(sc, ar);
518         ED_area_tag_redraw(CTX_wm_area(C));
519
520         return OPERATOR_FINISHED;
521 }
522
523 void CONSOLE_OT_clear(wmOperatorType *ot)
524 {
525         /* identifiers */
526         ot->name= "Clear";
527         ot->description= "Clear text by type";
528         ot->idname= "CONSOLE_OT_clear";
529         
530         /* api callbacks */
531         ot->exec= clear_exec;
532         ot->poll= ED_operator_console_active;
533         
534         /* properties */
535         RNA_def_boolean(ot->srna, "scrollback", 1, "Scrollback", "Clear the scrollback history");
536         RNA_def_boolean(ot->srna, "history", 0, "History", "Clear the command history");
537 }
538
539
540
541 /* the python exec operator uses this */
542 static int history_cycle_exec(bContext *C, wmOperator *op)
543 {
544         SpaceConsole *sc= CTX_wm_space_console(C);
545         ARegion *ar= CTX_wm_region(C);
546
547         ConsoleLine *ci= console_history_verify(C); /* TODO - stupid, just prevernts crashes when no command line */
548         short reverse= RNA_boolean_get(op->ptr, "reverse"); /* assumes down, reverse is up */
549         int prev_len= ci->len;
550
551         /* keep a copy of the line above so when history is cycled
552          * this is the only function that needs to know about the double-up */
553         if(ci->prev) {
554                 ConsoleLine *ci_prev= (ConsoleLine *)ci->prev;
555
556                 if(strcmp(ci->line, ci_prev->line)==0)
557                         console_history_free(sc, ci_prev);
558         }
559
560         if(reverse) { /* last item in mistory */
561                 ci= sc->history.last;
562                 BLI_remlink(&sc->history, ci);
563                 BLI_addhead(&sc->history, ci);
564         }
565         else {
566                 ci= sc->history.first;
567                 BLI_remlink(&sc->history, ci);
568                 BLI_addtail(&sc->history, ci);
569         }
570
571         {       /* add a duplicate of the new arg and remove all other instances */
572                 ConsoleLine *cl;
573                 while((cl= console_history_find(sc, ci->line, ci)))
574                         console_history_free(sc, cl);
575
576                 console_history_add(C, (ConsoleLine *)sc->history.last);
577         }
578         
579         ci= sc->history.last;
580         console_select_offset(sc, ci->len - prev_len);
581
582         /* could be wrapped so update scroll rect */
583         console_textview_update_rect(sc, ar);
584         ED_area_tag_redraw(CTX_wm_area(C));
585
586         return OPERATOR_FINISHED;
587 }
588
589 void CONSOLE_OT_history_cycle(wmOperatorType *ot)
590 {
591         /* identifiers */
592         ot->name= "History Cycle";
593         ot->description= "Cycle through history";
594         ot->idname= "CONSOLE_OT_history_cycle";
595         
596         /* api callbacks */
597         ot->exec= history_cycle_exec;
598         ot->poll= ED_operator_console_active;
599         
600         /* properties */
601         RNA_def_boolean(ot->srna, "reverse", 0, "Reverse", "reverse cycle history");
602 }
603
604
605 /* the python exec operator uses this */
606 static int history_append_exec(bContext *C, wmOperator *op)
607 {
608         SpaceConsole *sc= CTX_wm_space_console(C);
609         ScrArea *sa= CTX_wm_area(C);
610         ConsoleLine *ci= console_history_verify(C);
611         char *str= RNA_string_get_alloc(op->ptr, "text", NULL, 0); /* own this text in the new line, dont free */
612         int cursor= RNA_int_get(op->ptr, "current_character");
613         short rem_dupes= RNA_boolean_get(op->ptr, "remove_duplicates");
614         int prev_len= ci->len;
615
616         if(rem_dupes) {
617                 ConsoleLine *cl;
618
619                 while((cl= console_history_find(sc, ci->line, ci)))
620                         console_history_free(sc, cl);
621
622                 if(strcmp(str, ci->line)==0) {
623                         MEM_freeN(str);
624                         return OPERATOR_FINISHED;
625                 }
626         }
627
628         ci= console_history_add_str(sc, str, 1); /* own the string */
629         console_select_offset(sc, ci->len - prev_len);
630         console_line_cursor_set(ci, cursor);
631
632         ED_area_tag_redraw(sa);
633
634         return OPERATOR_FINISHED;
635 }
636
637 void CONSOLE_OT_history_append(wmOperatorType *ot)
638 {
639         /* identifiers */
640         ot->name= "History Append";
641         ot->description= "Append history at cursor position";
642         ot->idname= "CONSOLE_OT_history_append";
643         
644         /* api callbacks */
645         ot->exec= history_append_exec;
646         ot->poll= ED_operator_console_active;
647         
648         /* properties */
649         RNA_def_string(ot->srna, "text", "", 0, "Text", "Text to insert at the cursor position.");      
650         RNA_def_int(ot->srna, "current_character", 0, 0, INT_MAX, "Cursor", "The index of the cursor.", 0, 10000);
651         RNA_def_boolean(ot->srna, "remove_duplicates", 0, "Remove Duplicates", "Remove duplicate items in the history");
652 }
653
654
655 /* the python exec operator uses this */
656 static int scrollback_append_exec(bContext *C, wmOperator *op)
657 {
658         SpaceConsole *sc= CTX_wm_space_console(C);
659         ARegion *ar= CTX_wm_region(C);
660
661         ConsoleLine *ci= console_history_verify(C);
662         
663         char *str= RNA_string_get_alloc(op->ptr, "text", NULL, 0); /* own this text in the new line, dont free */
664         int type= RNA_enum_get(op->ptr, "type");
665         
666         ci= console_scrollback_add_str(sc, str, 1); /* own the string */
667         ci->type= type;
668         
669         console_scrollback_limit(sc);
670
671         console_textview_update_rect(sc, ar);
672         ED_area_tag_redraw(CTX_wm_area(C));
673         
674         return OPERATOR_FINISHED;
675 }
676
677 void CONSOLE_OT_scrollback_append(wmOperatorType *ot)
678 {
679         /* defined in DNA_space_types.h */
680         static EnumPropertyItem console_line_type_items[] = {
681                 {CONSOLE_LINE_OUTPUT,   "OUTPUT", 0, "Output", ""},
682                 {CONSOLE_LINE_INPUT,    "INPUT", 0, "Input", ""},
683                 {CONSOLE_LINE_INFO,             "INFO", 0, "Information", ""},
684                 {CONSOLE_LINE_ERROR,    "ERROR", 0, "Error", ""},
685                 {0, NULL, 0, NULL, NULL}};
686
687         /* identifiers */
688         ot->name= "Scrollback Append";
689         ot->description= "Append scrollback text by type";
690         ot->idname= "CONSOLE_OT_scrollback_append";
691         
692         /* api callbacks */
693         ot->exec= scrollback_append_exec;
694         ot->poll= ED_operator_console_active;
695         
696         /* properties */
697         RNA_def_string(ot->srna, "text", "", 0, "Text", "Text to insert at the cursor position.");      
698         RNA_def_enum(ot->srna, "type", console_line_type_items, CONSOLE_LINE_OUTPUT, "Type", "Console output type.");
699 }
700
701
702 static int copy_exec(bContext *C, wmOperator *UNUSED(op))
703 {
704         SpaceConsole *sc= CTX_wm_space_console(C);
705         int buf_len;
706
707         DynStr *buf_dyn= BLI_dynstr_new();
708         char *buf_str;
709         
710         ConsoleLine *cl;
711         int sel[2];
712         int offset= 0;
713
714         ConsoleLine cl_dummy= {0};
715
716 #if 0
717         /* copy whole file */
718         for(cl= sc->scrollback.first; cl; cl= cl->next) {
719                 BLI_dynstr_append(buf_dyn, cl->line);
720                 BLI_dynstr_append(buf_dyn, "\n");
721         }
722 #endif
723
724         if(sc->sel_start == sc->sel_end)
725                 return OPERATOR_CANCELLED;
726
727         console_scrollback_prompt_begin(sc, &cl_dummy);
728
729         for(cl= sc->scrollback.first; cl; cl= cl->next) {
730                 offset += cl->len + 1;
731         }
732
733         if(offset==0) {
734                 console_scrollback_prompt_end(sc, &cl_dummy);
735                 return OPERATOR_CANCELLED;
736         }
737
738         offset -= 1;
739         sel[0]= offset - sc->sel_end;
740         sel[1]= offset - sc->sel_start;
741
742         for(cl= sc->scrollback.first; cl; cl= cl->next) {
743                 if(sel[0] <= cl->len && sel[1] >= 0) {
744                         int sta= MAX2(sel[0], 0);
745                         int end= MIN2(sel[1], cl->len);
746
747                         if(BLI_dynstr_get_len(buf_dyn))
748                                 BLI_dynstr_append(buf_dyn, "\n");
749
750                         BLI_dynstr_nappend(buf_dyn, cl->line + sta, end - sta);
751                 }
752
753                 sel[0] -= cl->len + 1;
754                 sel[1] -= cl->len + 1;
755         }
756
757         buf_str= BLI_dynstr_get_cstring(buf_dyn);
758         buf_len= BLI_dynstr_get_len(buf_dyn);
759         BLI_dynstr_free(buf_dyn);
760         WM_clipboard_text_set(buf_str, 0);
761
762         MEM_freeN(buf_str);
763
764         console_scrollback_prompt_end(sc, &cl_dummy);
765
766         return OPERATOR_FINISHED;
767 }
768
769 void CONSOLE_OT_copy(wmOperatorType *ot)
770 {
771         /* identifiers */
772         ot->name= "Copy to Clipboard";
773         ot->description= "Copy selected text to clipboard";
774         ot->idname= "CONSOLE_OT_copy";
775
776         /* api callbacks */
777         ot->poll= ED_operator_console_active;
778         ot->exec= copy_exec;
779
780         /* properties */
781 }
782
783 static int paste_exec(bContext *C, wmOperator *UNUSED(op))
784 {
785         SpaceConsole *sc= CTX_wm_space_console(C);
786         ARegion *ar= CTX_wm_region(C);
787         ConsoleLine *ci= console_history_verify(C);
788
789         char *buf_str= WM_clipboard_text_get(0);
790         char *buf_step, *buf_next;
791
792         if(buf_str==NULL)
793                 return OPERATOR_CANCELLED;
794
795         buf_next= buf_str;
796         buf_step= buf_str;
797
798         while((buf_next=buf_step) && buf_next[0] != '\0') {
799                 buf_step= strchr(buf_next, '\n');
800                 if(buf_step) {
801                         *buf_step= '\0';
802                         buf_step++;
803                 }
804
805                 if(buf_next != buf_str) {
806                         WM_operator_name_call(C, "CONSOLE_OT_execute", WM_OP_EXEC_DEFAULT, NULL);
807                         ci= console_history_verify(C);
808                 }
809
810                 console_select_offset(sc, console_line_insert(ci, buf_next));
811         }
812
813         MEM_freeN(buf_str);
814
815         console_textview_update_rect(sc, ar);
816         ED_area_tag_redraw(CTX_wm_area(C));
817
818         return OPERATOR_FINISHED;
819 }
820
821 void CONSOLE_OT_paste(wmOperatorType *ot)
822 {
823         /* identifiers */
824         ot->name= "Paste from Clipboard";
825         ot->description= "Paste text from clipboard";
826         ot->idname= "CONSOLE_OT_paste";
827
828         /* api callbacks */
829         ot->poll= ED_operator_console_active;
830         ot->exec= paste_exec;
831
832         /* properties */
833 }
834
835 typedef struct SetConsoleCursor {
836         int sel_old[2];
837         int sel_init;
838 } SetConsoleCursor;
839
840 // TODO, cursor placement without selection
841 static void set_cursor_to_pos(SpaceConsole *sc, ARegion *ar, SetConsoleCursor *scu, int mval[2], int UNUSED(sel))
842 {
843         int pos;
844         pos= console_char_pick(sc, ar, mval);
845
846         if(scu->sel_init == INT_MAX) {
847                 scu->sel_init= pos;
848                 sc->sel_start = sc->sel_end = pos;
849                 return;
850         }
851
852         if (pos < scu->sel_init) {
853                 sc->sel_start = pos;
854                 sc->sel_end = scu->sel_init;
855         }
856         else if (pos > sc->sel_start) {
857                 sc->sel_start = scu->sel_init;
858                 sc->sel_end = pos;
859         }
860         else {
861                 sc->sel_start = sc->sel_end = pos;
862         }
863 }
864
865 static void console_modal_select_apply(bContext *C, wmOperator *op, wmEvent *event)
866 {
867         SpaceConsole *sc= CTX_wm_space_console(C);
868         ARegion *ar= CTX_wm_region(C);
869         SetConsoleCursor *scu= op->customdata;
870         int mval[2];
871         int sel_prev[2];
872
873         mval[0]= event->mval[0];
874         mval[1]= event->mval[1];
875
876         sel_prev[0]= sc->sel_start;
877         sel_prev[1]= sc->sel_end;
878         
879         set_cursor_to_pos(sc, ar, scu, mval, TRUE);
880
881         /* only redraw if the selection changed */
882         if(sel_prev[0] != sc->sel_start || sel_prev[1] != sc->sel_end) {
883                 ED_area_tag_redraw(CTX_wm_area(C));
884         }
885 }
886
887 static void set_cursor_exit(bContext *UNUSED(C), wmOperator *op)
888 {
889 //      SpaceConsole *sc= CTX_wm_space_console(C);
890         SetConsoleCursor *scu= op->customdata;
891
892         /*
893         if(txt_has_sel(text)) {
894                 buffer = txt_sel_to_buf(text);
895                 WM_clipboard_text_set(buffer, 1);
896                 MEM_freeN(buffer);
897         }*/
898
899         MEM_freeN(scu);
900 }
901
902 static int console_modal_select_invoke(bContext *C, wmOperator *op, wmEvent *event)
903 {
904         SpaceConsole *sc= CTX_wm_space_console(C);
905 //      ARegion *ar= CTX_wm_region(C);
906         SetConsoleCursor *scu;
907
908         op->customdata= MEM_callocN(sizeof(SetConsoleCursor), "SetConsoleCursor");
909         scu= op->customdata;
910
911         scu->sel_old[0]= sc->sel_start;
912         scu->sel_old[1]= sc->sel_end;
913
914         scu->sel_init = INT_MAX;
915
916         WM_event_add_modal_handler(C, op);
917
918         console_modal_select_apply(C, op, event);
919
920         return OPERATOR_RUNNING_MODAL;
921 }
922
923 static int console_modal_select(bContext *C, wmOperator *op, wmEvent *event)
924 {
925         switch(event->type) {
926                 case LEFTMOUSE:
927                 case MIDDLEMOUSE:
928                 case RIGHTMOUSE:
929                         set_cursor_exit(C, op);
930                         return OPERATOR_FINISHED;
931                 case MOUSEMOVE:
932                         console_modal_select_apply(C, op, event);
933                         break;
934         }
935
936         return OPERATOR_RUNNING_MODAL;
937 }
938
939 static int console_modal_select_cancel(bContext *C, wmOperator *op)
940 {
941         set_cursor_exit(C, op);
942         return OPERATOR_FINISHED;
943 }
944
945 void CONSOLE_OT_select_set(wmOperatorType *ot)
946 {
947         /* identifiers */
948         ot->name= "Set Selection";
949         ot->idname= "CONSOLE_OT_select_set";
950         ot->description= "Set the console selection";
951
952         /* api callbacks */
953         ot->invoke= console_modal_select_invoke;
954         ot->modal= console_modal_select;
955         ot->cancel= console_modal_select_cancel;
956         ot->poll= ED_operator_console_active;
957 }