1// MIT License
2
3// Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#include "code.h"
24#include "ext/history.h"
25
26#include <ctype.h>
27#include "tic_assert.h"
28
29#define TEXT_CURSOR_DELAY (TIC80_FRAMERATE / 2)
30#define TEXT_CURSOR_BLINK_PERIOD TIC80_FRAMERATE
31#define BOOKMARK_WIDTH 7
32#define CODE_EDITOR_WIDTH (TIC80_WIDTH - BOOKMARK_WIDTH)
33#define CODE_EDITOR_HEIGHT (TIC80_HEIGHT - TOOLBAR_SIZE - STUDIO_TEXT_HEIGHT)
34#define TEXT_BUFFER_HEIGHT (CODE_EDITOR_HEIGHT / STUDIO_TEXT_HEIGHT)
35#define SIDEBAR_WIDTH (12 * TIC_FONT_WIDTH)
36
37#if defined(TIC80_PRO)
38# define MAX_CODE sizeof(tic_code)
39#else
40# define MAX_CODE TIC_BANK_SIZE
41#endif
42
43typedef struct CodeState CodeState;
44
45static_assert(sizeof(CodeState) == 2, "CodeStateSize");
46
47enum
48{
49#define CODE_COLOR_DEF(VAR) SyntaxType_##VAR,
50 CODE_COLORS_LIST(CODE_COLOR_DEF)
51#undef CODE_COLOR_DEF
52};
53
54static void packState(Code* code)
55{
56 const char* src = code->src;
57 for(CodeState* s = code->state, *end = s + TIC_CODE_SIZE; s != end; ++s)
58 {
59 s->cursor = src == code->cursor.position;
60 s->sym = *src++;
61 }
62}
63
64static void unpackState(Code* code)
65{
66 char* src = code->src;
67 for(CodeState* s = code->state, *end = s + TIC_CODE_SIZE; s != end; ++s)
68 {
69 if(s->cursor)
70 code->cursor.position = src;
71
72 *src++ = s->sym;
73 }
74}
75
76static void history(Code* code)
77{
78 packState(code);
79 history_add(code->history);
80}
81
82static void drawStatus(Code* code)
83{
84 enum {Height = TIC_FONT_HEIGHT + 1, StatusY = TIC80_HEIGHT - TIC_FONT_HEIGHT};
85
86 tic_api_rect(code->tic, 0, TIC80_HEIGHT - Height, TIC80_WIDTH, Height, code->status.color);
87 tic_api_print(code->tic, code->status.line, 0, StatusY, getConfig(code->studio)->theme.code.BG, true, 1, false);
88 tic_api_print(code->tic, code->status.size, TIC80_WIDTH - (s32)strlen(code->status.size) * TIC_FONT_WIDTH,
89 StatusY, getConfig(code->studio)->theme.code.BG, true, 1, false);
90}
91
92static char* getPosByLine(char* ptr, s32 line)
93{
94 s32 y = 0;
95 while(*ptr)
96 {
97 if(y == line) break;
98 if(*ptr++ == '\n') y++;
99 }
100
101 return ptr;
102}
103
104static char* getNextLineByPos(Code* code, char* pos)
105{
106 while(*pos && *pos++ != '\n');
107 return pos;
108}
109
110static inline CodeState* getState(Code* code, const char* pos)
111{
112 return code->state + (pos - code->src);
113}
114
115static void toggleBookmark(Code* code, char* codePos)
116{
117 CodeState* start = getState(code, codePos);
118 const CodeState* end = getState(code, getNextLineByPos(code, codePos));
119
120 bool bookmarked = false;
121 CodeState* ptr = start;
122 while(ptr < end)
123 if(ptr++->bookmark)
124 bookmarked = true;
125
126 if(bookmarked)
127 {
128 CodeState* ptr = start;
129 while(ptr < end)
130 ptr++->bookmark = 0;
131 }
132 else start->bookmark = 1;
133
134 history(code);
135}
136
137static void drawBookmarks(Code* code)
138{
139 tic_mem* tic = code->tic;
140
141 enum {Width = BOOKMARK_WIDTH, Height = TIC80_HEIGHT - TOOLBAR_SIZE*2};
142 tic_rect rect = {0, TOOLBAR_SIZE, Width, Height};
143
144 tic_api_rect(code->tic, rect.x, rect.y, rect.w, rect.h, tic_color_grey);
145
146 if(checkMousePos(code->studio, &rect))
147 {
148 setCursor(code->studio, tic_cursor_hand);
149
150 showTooltip(code->studio, "BOOKMARK [ctrl+f1]");
151
152 s32 line = (tic_api_mouse(tic).y - rect.y) / STUDIO_TEXT_HEIGHT;
153
154 drawBitIcon(code->studio, tic_icon_bookmark, rect.x, rect.y + line * STUDIO_TEXT_HEIGHT - 1, tic_color_dark_grey);
155
156 if(checkMouseClick(code->studio, &rect, tic_mouse_left))
157 toggleBookmark(code, getPosByLine(code->src, line + code->scroll.y));
158 }
159
160 const char* pointer = code->src;
161 const CodeState* syntaxPointer = code->state;
162 s32 y = -code->scroll.y;
163
164 while(*pointer)
165 {
166 if(syntaxPointer++->bookmark)
167 {
168 drawBitIcon(code->studio, tic_icon_bookmark, rect.x, rect.y + y * STUDIO_TEXT_HEIGHT, tic_color_black);
169 drawBitIcon(code->studio, tic_icon_bookmark, rect.x, rect.y + y * STUDIO_TEXT_HEIGHT - 1, tic_color_yellow);
170 }
171
172 if(*pointer++ == '\n')y++;
173 }
174}
175
176static inline s32 getFontWidth(Code* code)
177{
178 return code->altFont ? TIC_ALTFONT_WIDTH : TIC_FONT_WIDTH;
179}
180
181static inline void drawChar(tic_mem* tic, char symbol, s32 x, s32 y, u8 color, bool alt)
182{
183 tic_api_print(tic, (char[]){symbol, '\0'}, x, y, color, true, 1, alt);
184}
185
186static void drawCursor(Code* code, s32 x, s32 y, char symbol)
187{
188 bool inverse = code->cursor.delay || code->tickCounter % TEXT_CURSOR_BLINK_PERIOD < TEXT_CURSOR_BLINK_PERIOD / 2;
189
190 if(inverse)
191 {
192 if(code->shadowText)
193 tic_api_rect(code->tic, x, y, getFontWidth(code)+1, TIC_FONT_HEIGHT+1, 0);
194
195 tic_api_rect(code->tic, x-1, y-1, getFontWidth(code)+1, TIC_FONT_HEIGHT+1, getConfig(code->studio)->theme.code.cursor);
196
197 if(symbol)
198 drawChar(code->tic, symbol, x, y, getConfig(code->studio)->theme.code.BG, code->altFont);
199 }
200}
201
202static void drawMatchedDelim(Code* code, s32 x, s32 y, char symbol, u8 color)
203{
204 tic_api_rectb(code->tic, x-1, y-1, (getFontWidth(code))+1, TIC_FONT_HEIGHT+1,
205 getConfig(code->studio)->theme.code.cursor);
206 drawChar(code->tic, symbol, x, y, color, code->altFont);
207}
208
209static void drawCode(Code* code, bool withCursor)
210{
211 tic_rect rect = {BOOKMARK_WIDTH, TOOLBAR_SIZE, CODE_EDITOR_WIDTH, CODE_EDITOR_HEIGHT};
212
213 s32 xStart = rect.x - code->scroll.x * getFontWidth(code);
214 s32 x = xStart;
215 s32 y = rect.y - code->scroll.y * STUDIO_TEXT_HEIGHT;
216 const char* pointer = code->src;
217
218 u8 selectColor = getConfig(code->studio)->theme.code.select;
219
220 const u8* colors = (const u8*)&getConfig(code->studio)->theme.code;
221 const CodeState* syntaxPointer = code->state;
222
223 struct { char* start; char* end; } selection =
224 {
225 MIN(code->cursor.selection, code->cursor.position),
226 MAX(code->cursor.selection, code->cursor.position)
227 };
228
229 struct { s32 x; s32 y; char symbol; } cursor = {-1, -1, 0};
230 struct { s32 x; s32 y; char symbol; u8 color; } matchedDelim = {-1, -1, 0, 0};
231
232 while(*pointer)
233 {
234 char symbol = *pointer;
235
236 if(x >= -getFontWidth(code) && x < TIC80_WIDTH && y >= -TIC_FONT_HEIGHT && y < TIC80_HEIGHT )
237 {
238 if(code->cursor.selection && pointer >= selection.start && pointer < selection.end)
239 {
240 if(code->shadowText)
241 tic_api_rect(code->tic, x, y, getFontWidth(code)+1, TIC_FONT_HEIGHT+1, tic_color_black);
242
243 tic_api_rect(code->tic, x-1, y-1, getFontWidth(code)+1, TIC_FONT_HEIGHT+1, selectColor);
244 drawChar(code->tic, symbol, x, y, tic_color_dark_grey, code->altFont);
245 }
246 else
247 {
248 if(code->shadowText)
249 drawChar(code->tic, symbol, x+1, y+1, 0, code->altFont);
250
251 drawChar(code->tic, symbol, x, y, colors[syntaxPointer->syntax], code->altFont);
252 }
253 }
254
255 if(code->cursor.position == pointer)
256 cursor.x = x, cursor.y = y, cursor.symbol = symbol;
257
258 if(code->matchedDelim == pointer)
259 {
260 matchedDelim.x = x, matchedDelim.y = y, matchedDelim.symbol = symbol,
261 matchedDelim.color = colors[syntaxPointer->syntax];
262 }
263
264 if(symbol == '\n')
265 {
266 x = xStart;
267 y += STUDIO_TEXT_HEIGHT;
268 }
269 else x += getFontWidth(code);
270
271 pointer++;
272 syntaxPointer++;
273 }
274
275 drawBookmarks(code);
276
277 if(code->cursor.position == pointer)
278 cursor.x = x, cursor.y = y;
279
280 if(withCursor && cursor.x >= BOOKMARK_WIDTH && cursor.y >= 0)
281 drawCursor(code, cursor.x, cursor.y, cursor.symbol);
282
283 if(matchedDelim.symbol) {
284 drawMatchedDelim(code, matchedDelim.x, matchedDelim.y,
285 matchedDelim.symbol, matchedDelim.color);
286 }
287}
288
289static void getCursorPosition(Code* code, s32* x, s32* y)
290{
291 *x = 0;
292 *y = 0;
293
294 const char* pointer = code->src;
295
296 while(*pointer)
297 {
298 if(code->cursor.position == pointer) return;
299
300 if(*pointer == '\n')
301 {
302 *x = 0;
303 (*y)++;
304 }
305 else (*x)++;
306
307 pointer++;
308 }
309}
310
311static s32 getLinesCount(Code* code)
312{
313 char* text = code->src;
314 s32 count = 0;
315
316 while(*text)
317 if(*text++ == '\n')
318 count++;
319
320 return count;
321}
322
323static void removeInvalidChars(char* code)
324{
325 // remove \r symbol
326 char* s; char* d;
327 for(s = d = code; (*d = *s); d += (*s++ != '\r'));
328}
329
330const char matchingDelim(const char current)
331{
332 char match = 0;
333 switch (current)
334 {
335 case '(': match = ')'; break;
336 case ')': match = '('; break;
337 case '[': match = ']'; break;
338 case ']': match = '['; break;
339 case '{': match = '}'; break;
340 case '}': match = '{'; break;
341 }
342 return match;
343}
344
345const char* findMatchedDelim(Code* code, const char* current)
346{
347 const char* start = code->src;
348 // delimiters inside comments and strings don't get to be matched!
349 if(code->state[current - start].syntax == SyntaxType_COMMENT ||
350 code->state[current - start].syntax == SyntaxType_STRING) return 0;
351
352 char initial = *current;
353 char seeking = matchingDelim(initial);
354 if(seeking == 0) return NULL;
355
356 s8 dir = (initial == '(' || initial == '[' || initial == '{') ? 1 : -1;
357
358 while(*current && (start < current))
359 {
360 current += dir;
361 // skip over anything inside a comment or string
362 if(code->state[current - start].syntax == SyntaxType_COMMENT ||
363 code->state[current - start].syntax == SyntaxType_STRING) continue;
364 if(*current == seeking) return current;
365 if(*current == initial) current = findMatchedDelim(code, current);
366 if(!current) break;
367 }
368
369 return NULL;
370}
371
372static void updateEditor(Code* code)
373{
374 s32 column = 0;
375 s32 line = 0;
376 getCursorPosition(code, &column, &line);
377 if(getConfig(code->studio)->theme.code.matchDelimiters)
378 code->matchedDelim = findMatchedDelim(code, code->cursor.position);
379
380 const s32 BufferWidth = CODE_EDITOR_WIDTH / getFontWidth(code);
381
382 if(column < code->scroll.x) code->scroll.x = column;
383 else if(column >= code->scroll.x + BufferWidth)
384 code->scroll.x = column - BufferWidth + 1;
385
386 if(line < code->scroll.y) code->scroll.y = line;
387 else if(line >= code->scroll.y + TEXT_BUFFER_HEIGHT)
388 code->scroll.y = line - TEXT_BUFFER_HEIGHT + 1;
389
390 code->cursor.delay = TEXT_CURSOR_DELAY;
391
392 sprintf(code->status.line, "line %i/%i col %i", line + 1, getLinesCount(code) + 1, column + 1);
393 {
394 s32 codeLen = strlen(code->src);
395 sprintf(code->status.size, "size %i/%i", codeLen, MAX_CODE);
396 code->status.color = codeLen > MAX_CODE ? tic_color_red : tic_color_white;
397 }
398}
399
400static inline bool islineend(char c) {return c == '\n' || c == '\0';}
401static inline bool isalpha_(char c) {return isalpha(c) || c == '_';}
402static inline bool isalnum_(char c) {return isalnum(c) || c == '_';}
403
404static void setCodeState(CodeState* state, u8 color, s32 start, s32 size)
405{
406 for(CodeState* s = state + start, *end = s + size; s != end; ++s)
407 s->syntax = color;
408}
409
410static void parseCode(const tic_script_config* config, const char* start, CodeState* state)
411{
412 const char* ptr = start;
413
414 const char* blockCommentStart = NULL;
415 const char* blockCommentStart2 = NULL;
416 const char* blockStringStart = NULL;
417 const char* blockStdStringStart = NULL;
418 const char* singleCommentStart = NULL;
419 const char* wordStart = NULL;
420 const char* numberStart = NULL;
421
422start:
423 while(true)
424 {
425 char c = ptr[0];
426
427 if(blockCommentStart)
428 {
429 const char* end = strstr(ptr, config->blockCommentEnd);
430
431 ptr = end ? end + strlen(config->blockCommentEnd) : blockCommentStart + strlen(blockCommentStart);
432 setCodeState(state, SyntaxType_COMMENT, (s32)(blockCommentStart - start), (s32)(ptr - blockCommentStart));
433 blockCommentStart = NULL;
434
435 // !TODO: stupid MS compiler doesn't see 'continue' here in release, so lets use 'goto' instead, investigate why
436 goto start;
437 }
438 else if(blockCommentStart2)
439 {
440 const char* end = strstr(ptr, config->blockCommentEnd2);
441
442 ptr = end ? end + strlen(config->blockCommentEnd2) : blockCommentStart2 + strlen(blockCommentStart2);
443 setCodeState(state, SyntaxType_COMMENT, (s32)(blockCommentStart2 - start), (s32)(ptr - blockCommentStart2));
444 blockCommentStart2 = NULL;
445 goto start;
446 }
447 else if(blockStringStart)
448 {
449 const char* end = strstr(ptr, config->blockStringEnd);
450
451 ptr = end ? end + strlen(config->blockStringEnd) : blockStringStart + strlen(blockStringStart);
452 setCodeState(state, SyntaxType_STRING, (s32)(blockStringStart - start), (s32)(ptr - blockStringStart));
453 blockStringStart = NULL;
454 continue;
455 }
456 else if(blockStdStringStart)
457 {
458 const char* blockStart = blockStdStringStart+1;
459
460 while(true)
461 {
462 const char* pos = strchr(blockStart, *blockStdStringStart);
463
464 if(pos)
465 {
466 if(*(pos-1) == '\\' && *(pos-2) != '\\') blockStart = pos + 1;
467 else
468 {
469 ptr = pos + 1;
470 break;
471 }
472 }
473 else
474 {
475 ptr = blockStdStringStart + strlen(blockStdStringStart);
476 break;
477 }
478 }
479
480 setCodeState(state, SyntaxType_STRING, (s32)(blockStdStringStart - start), (s32)(ptr - blockStdStringStart));
481 blockStdStringStart = NULL;
482 continue;
483 }
484 else if(singleCommentStart)
485 {
486 while(!islineend(*ptr))ptr++;
487
488 setCodeState(state, SyntaxType_COMMENT, (s32)(singleCommentStart - start), (s32)(ptr - singleCommentStart));
489 singleCommentStart = NULL;
490 continue;
491 }
492 else if(wordStart)
493 {
494 while(!islineend(*ptr) && isalnum_(*ptr)) ptr++;
495
496 s32 len = (s32)(ptr - wordStart);
497 bool keyword = false;
498 {
499 for(s32 i = 0; i < config->keywordsCount; i++)
500 if(len == strlen(config->keywords[i]) && memcmp(wordStart, config->keywords[i], len) == 0)
501 {
502 setCodeState(state, SyntaxType_KEYWORD, (s32)(wordStart - start),len);
503 keyword = true;
504 break;
505 }
506 }
507
508 if(!keyword)
509 {
510 static const char* const ApiKeywords[] =
511 {
512#define TIC_CALLBACK_DEF(name, ...) #name,
513 TIC_CALLBACK_LIST(TIC_CALLBACK_DEF)
514#undef TIC_CALLBACK_DEF
515
516#define API_KEYWORD_DEF(name, ...) #name,
517 TIC_API_LIST(API_KEYWORD_DEF)
518#undef API_KEYWORD_DEF
519 };
520
521 for(s32 i = 0; i < COUNT_OF(ApiKeywords); i++)
522 if(len == strlen(ApiKeywords[i]) && memcmp(wordStart, ApiKeywords[i], len) == 0)
523 {
524 setCodeState(state, SyntaxType_API, (s32)(wordStart - start), len);
525 break;
526 }
527 }
528
529 wordStart = NULL;
530 continue;
531 }
532 else if(numberStart)
533 {
534 while(!islineend(*ptr))
535 {
536 char c = *ptr;
537
538 if(isdigit(c)) ptr++;
539 else if(numberStart[0] == '0'
540 && (numberStart[1] == 'x' || numberStart[1] == 'X')
541 && isxdigit(numberStart[2]))
542 {
543 if((ptr - numberStart < 2) || isxdigit(c)) ptr++;
544 else break;
545 }
546 else if(c == '.' || c == 'e' || c == 'E')
547 {
548 if(isdigit(ptr[1])) ptr++;
549 else break;
550 }
551 else break;
552 }
553
554 setCodeState(state, SyntaxType_NUMBER, (s32)(numberStart - start), (s32)(ptr - numberStart));
555 numberStart = NULL;
556 continue;
557 }
558 else
559 {
560 if(config->blockCommentStart && memcmp(ptr, config->blockCommentStart, strlen(config->blockCommentStart)) == 0)
561 {
562 blockCommentStart = ptr;
563 ptr += strlen(config->blockCommentStart);
564 continue;
565 }
566 if(config->blockCommentStart2 && memcmp(ptr, config->blockCommentStart2, strlen(config->blockCommentStart2)) == 0)
567 {
568 blockCommentStart2 = ptr;
569 ptr += strlen(config->blockCommentStart2);
570 continue;
571 }
572 else if(config->blockStringStart && memcmp(ptr, config->blockStringStart, strlen(config->blockStringStart)) == 0)
573 {
574 blockStringStart = ptr;
575 ptr += strlen(config->blockStringStart);
576 continue;
577 }
578 else if(c == '"' || c == '\'')
579 {
580 blockStdStringStart = ptr;
581 ptr++;
582 continue;
583 }
584 else if(config->singleComment && memcmp(ptr, config->singleComment, strlen(config->singleComment)) == 0)
585 {
586 singleCommentStart = ptr;
587 ptr += strlen(config->singleComment);
588 continue;
589 }
590 else if(isalpha_(c))
591 {
592 wordStart = ptr;
593 ptr++;
594 continue;
595 }
596 else if(isdigit(c) || (c == '.' && isdigit(ptr[1])))
597 {
598 numberStart = ptr;
599 ptr++;
600 continue;
601 }
602 else if(ispunct(c)) state[ptr - start].syntax = SyntaxType_SIGN;
603 }
604
605 if(!c) break;
606
607 ptr++;
608 }
609}
610
611static void parseSyntaxColor(Code* code)
612{
613 for(CodeState* s = code->state, *end = s + TIC_CODE_SIZE; s != end; ++s)
614 s->syntax = SyntaxType_FG;
615
616 tic_mem* tic = code->tic;
617
618 const tic_script_config* config = tic_core_script_config(tic);
619
620 parseCode(config, code->src, code->state);
621}
622
623static char* getLineByPos(Code* code, char* pos)
624{
625 char* text = code->src;
626 char* line = text;
627
628 while(text < pos)
629 if(*text++ == '\n')
630 line = text;
631
632 return line;
633}
634
635static char* getLine(Code* code)
636{
637 return getLineByPos(code, code->cursor.position);
638}
639
640static char* getPrevLineByPos(Code* code, char* pos)
641{
642 char* text = code->src;
643 char* prevLine = text;
644 char* line = text;
645
646 while(text < pos)
647 if(*text++ == '\n')
648 {
649 prevLine = line;
650 line = text;
651 }
652
653 return prevLine;
654}
655
656static char* getPrevLine(Code* code)
657{
658 return getPrevLineByPos(code, code->cursor.position);
659}
660
661static char* getNextLine(Code* code)
662{
663 return getNextLineByPos(code, code->cursor.position);
664}
665
666static s32 getLineSize(const char* line)
667{
668 s32 size = 0;
669 while(*line != '\n' && *line++) size++;
670
671 return size;
672}
673
674static void updateColumn(Code* code)
675{
676 code->cursor.column = (s32)(code->cursor.position - getLine(code));
677}
678
679static void updateCursorPosition(Code* code, char* position)
680{
681 code->cursor.position = position;
682 updateColumn(code);
683 updateEditor(code);
684}
685
686static void setCursorPosition(Code* code, s32 cx, s32 cy)
687{
688 s32 x = 0;
689 s32 y = 0;
690 char* pointer = code->src;
691
692 while(*pointer)
693 {
694 if(y == cy && x == cx)
695 {
696 updateCursorPosition(code, pointer);
697 return;
698 }
699
700 if(*pointer == '\n')
701 {
702 if(y == cy && cx > x)
703 {
704 updateCursorPosition(code, pointer);
705 return;
706 }
707
708 x = 0;
709 y++;
710 }
711 else x++;
712
713 pointer++;
714 }
715
716 updateCursorPosition(code, pointer);
717}
718
719static void endLine(Code* code)
720{
721 while(*code->cursor.position)
722 {
723 code->cursor.position++;
724 }
725 updateColumn(code);
726}
727
728static void upLine(Code* code)
729{
730 char* prevLine = getPrevLine(code);
731 size_t prevSize = getLineSize(prevLine);
732 size_t size = code->cursor.column;
733
734 code->cursor.position = prevLine + (prevSize > size ? size : prevSize);
735}
736
737static void downLine(Code* code)
738{
739 char* nextLine = getNextLine(code);
740 size_t nextSize = getLineSize(nextLine);
741 size_t size = code->cursor.column;
742
743 code->cursor.position = nextLine + (nextSize > size ? size : nextSize);
744}
745
746static void leftColumn(Code* code)
747{
748 char* start = code->src;
749
750 if(code->cursor.position > start)
751 {
752 code->cursor.position--;
753 updateColumn(code);
754 }
755}
756
757static void rightColumn(Code* code)
758{
759 if(*code->cursor.position)
760 {
761 code->cursor.position++;
762 updateColumn(code);
763 }
764}
765
766static char* leftWordPos(Code* code)
767{
768 const char* start = code->src;
769 char* pos = code->cursor.position - 1;
770
771 if(pos > start)
772 {
773 if(isalnum_(*pos)) while(pos > start && isalnum_(*(pos-1))) pos--;
774 else while(pos > start && !isalnum_(*(pos-1))) pos--;
775 return pos;
776 }
777
778 return code->cursor.position;
779}
780
781static void leftWord(Code* code)
782{
783 code->cursor.position = leftWordPos(code);
784 updateColumn(code);
785}
786
787static char* rightWordPos(Code* code)
788{
789 const char* end = code->src + strlen(code->src);
790 char* pos = code->cursor.position;
791
792 if(pos < end)
793 {
794 if(isalnum_(*pos)) while(pos < end && isalnum_(*pos)) pos++;
795 else while(pos < end && !isalnum_(*pos)) pos++;
796
797 }
798
799 return pos;
800}
801
802static void rightWord(Code* code)
803{
804 code->cursor.position = rightWordPos(code);
805 updateColumn(code);
806}
807
808static void goHome(Code* code)
809{
810 code->cursor.position = getLine(code);
811
812 updateColumn(code);
813}
814
815static void goEnd(Code* code)
816{
817 char* line = getLine(code);
818 code->cursor.position = line + getLineSize(line);
819
820 updateColumn(code);
821}
822
823static void goCodeHome(Code *code)
824{
825 code->cursor.position = code->src;
826
827 updateColumn(code);
828}
829
830static void goCodeEnd(Code *code)
831{
832 code->cursor.position = code->src + strlen(code->src);
833
834 updateColumn(code);
835}
836
837static void pageUp(Code* code)
838{
839 s32 column = 0;
840 s32 line = 0;
841 getCursorPosition(code, &column, &line);
842 setCursorPosition(code, column, line > TEXT_BUFFER_HEIGHT ? line - TEXT_BUFFER_HEIGHT : 0);
843}
844
845static void pageDown(Code* code)
846{
847 s32 column = 0;
848 s32 line = 0;
849 getCursorPosition(code, &column, &line);
850 s32 lines = getLinesCount(code);
851 setCursorPosition(code, column, line < lines - TEXT_BUFFER_HEIGHT ? line + TEXT_BUFFER_HEIGHT : lines);
852}
853
854static void deleteCode(Code* code, char* start, char* end)
855{
856 s32 size = (s32)strlen(end) + 1;
857 memmove(start, end, size);
858
859 // delete code state
860 memmove(getState(code, start), getState(code, end), size * sizeof(CodeState));
861}
862
863static void insertCodeSize(Code* code, char* dst, const char* src, s32 size)
864{
865 s32 restSize = (s32)strlen(dst) + 1;
866 memmove(dst + size, dst, restSize);
867 memcpy(dst, src, size);
868
869 // insert code state
870 {
871 CodeState* pos = getState(code, dst);
872 memmove(pos + size, pos, restSize * sizeof(CodeState));
873 memset(pos, 0, size * sizeof(CodeState));
874 }
875}
876
877static void insertCode(Code* code, char* dst, const char* src)
878{
879 insertCodeSize(code, dst, src, strlen(src));
880}
881
882static bool replaceSelection(Code* code)
883{
884 char* pos = code->cursor.position;
885 char* sel = code->cursor.selection;
886
887 if(sel && sel != pos)
888 {
889 char* start = MIN(sel, pos);
890 char* end = MAX(sel, pos);
891
892 deleteCode(code, start, end);
893
894 code->cursor.position = start;
895 code->cursor.selection = NULL;
896
897 history(code);
898
899 parseSyntaxColor(code);
900
901 return true;
902 }
903
904 return false;
905}
906
907static void deleteChar(Code* code)
908{
909 if(!replaceSelection(code))
910 {
911 deleteCode(code, code->cursor.position, code->cursor.position + 1);
912 history(code);
913 parseSyntaxColor(code);
914 }
915
916 updateEditor(code);
917}
918
919static void backspaceChar(Code* code)
920{
921 if(!replaceSelection(code) && code->cursor.position > code->src)
922 {
923 char* pos = --code->cursor.position;
924 deleteCode(code, pos, pos + 1);
925 history(code);
926 parseSyntaxColor(code);
927 }
928
929 updateEditor(code);
930}
931
932static void deleteWord(Code* code)
933{
934 const char* end = code->src + strlen(code->src);
935 char* pos = code->cursor.position;
936
937 if(pos < end)
938 {
939 if(isalnum_(*pos)) while(pos < end && isalnum_(*pos)) pos++;
940 else while(pos < end && !isalnum_(*pos)) pos++;
941
942 deleteCode(code, code->cursor.position, pos);
943
944 history(code);
945 parseSyntaxColor(code);
946 }
947}
948
949static void backspaceWord(Code* code)
950{
951 const char* start = code->src;
952 char* pos = code->cursor.position-1;
953
954 if(pos > start)
955 {
956 if(isalnum_(*pos)) while(pos > start && isalnum_(*(pos-1))) pos--;
957 else while(pos > start && !isalnum_(*(pos-1))) pos--;
958
959 deleteCode(code, pos, code->cursor.position);
960
961 code->cursor.position = pos;
962 history(code);
963 parseSyntaxColor(code);
964 }
965}
966
967
968
969static void inputSymbolBase(Code* code, char sym)
970{
971 if (strlen(code->src) >= MAX_CODE)
972 return;
973 if(getConfig(code->studio)->theme.code.autoDelimiters && (sym == '(' || sym == '[' || sym == '{'))
974 {
975 insertCode(code, code->cursor.position++, (const char[]){sym, matchingDelim(sym), '\0'});
976 } else {
977 insertCode(code, code->cursor.position++, (const char[]){sym, '\0'});
978 }
979
980 history(code);
981
982 updateColumn(code);
983
984 parseSyntaxColor(code);
985}
986
987static void inputSymbol(Code* code, char sym)
988{
989 replaceSelection(code);
990
991 inputSymbolBase(code, sym);
992}
993
994static void newLine(Code* code)
995{
996 if(!replaceSelection(code))
997 {
998 char* ptr = getLine(code);
999 size_t size = 0;
1000 char firstChar = *ptr;
1001
1002 while(*ptr == '\t' || *ptr == ' ') ptr++, size++;
1003
1004 if(ptr > code->cursor.position)
1005 size -= ptr - code->cursor.position;
1006
1007 inputSymbol(code, '\n');
1008
1009 for(size_t i = 0; i < size; i++)
1010 inputSymbol(code, firstChar);
1011
1012 updateEditor(code);
1013 }
1014}
1015
1016static void selectAll(Code* code)
1017{
1018 code->cursor.selection = code->src;
1019 code->cursor.position = code->cursor.selection + strlen(code->cursor.selection);
1020}
1021
1022static void copyToClipboard(Code* code)
1023{
1024 char* pos = code->cursor.position;
1025 char* sel = code->cursor.selection;
1026
1027 char* start = NULL;
1028 size_t size = 0;
1029
1030 if(sel && sel != pos)
1031 {
1032 start = MIN(sel, pos);
1033 size = MAX(sel, pos) - start;
1034 }
1035 else
1036 {
1037 start = getLine(code);
1038 size = getNextLine(code) - start;
1039 }
1040
1041 char* clipboard = (char*)malloc(size+1);
1042
1043 if(clipboard)
1044 {
1045 memcpy(clipboard, start, size);
1046 clipboard[size] = '\0';
1047 tic_sys_clipboard_set(clipboard);
1048 free(clipboard);
1049 }
1050}
1051
1052static void cutToClipboard(Code* code)
1053{
1054 if(code->cursor.selection == NULL || code->cursor.position == code->cursor.selection)
1055 {
1056 code->cursor.position = getLine(code);
1057 code->cursor.selection = getNextLine(code);
1058 }
1059
1060 copyToClipboard(code);
1061 replaceSelection(code);
1062 history(code);
1063}
1064
1065static void copyFromClipboard(Code* code)
1066{
1067 if(tic_sys_clipboard_has())
1068 {
1069 char* clipboard = tic_sys_clipboard_get();
1070
1071 if(clipboard)
1072 {
1073 removeInvalidChars(clipboard);
1074 size_t size = strlen(clipboard);
1075
1076 if(size)
1077 {
1078 replaceSelection(code);
1079
1080 // cut clipboard code if overall code > max code size
1081 {
1082 size_t codeSize = strlen(code->src);
1083
1084 if (codeSize + size > MAX_CODE)
1085 {
1086 size = MAX_CODE - codeSize;
1087 clipboard[size] = '\0';
1088 }
1089 }
1090
1091 insertCode(code, code->cursor.position, clipboard);
1092
1093 code->cursor.position += size;
1094
1095 history(code);
1096
1097 parseSyntaxColor(code);
1098 }
1099
1100 tic_sys_clipboard_free(clipboard);
1101 }
1102 }
1103}
1104
1105static void update(Code* code)
1106{
1107 updateEditor(code);
1108 parseSyntaxColor(code);
1109}
1110
1111static void undo(Code* code)
1112{
1113 history_undo(code->history);
1114 unpackState(code);
1115
1116 update(code);
1117}
1118
1119static void redo(Code* code)
1120{
1121 history_redo(code->history);
1122 unpackState(code);
1123
1124 update(code);
1125}
1126
1127static void doTab(Code* code, bool shift, bool crtl)
1128{
1129 char* cursor_position = code->cursor.position;
1130 char* cursor_selection = code->cursor.selection;
1131
1132 bool has_selection = cursor_selection && cursor_selection != cursor_position;
1133 bool modifier_key_pressed = shift || crtl;
1134
1135 if(has_selection || modifier_key_pressed)
1136 {
1137 char* start;
1138 char* end;
1139
1140 bool changed = false;
1141
1142 if(cursor_selection) {
1143 start = MIN(cursor_selection, cursor_position);
1144 end = MAX(cursor_selection, cursor_position);
1145 } else {
1146 start = end = cursor_position;
1147 }
1148
1149 char* line = start = getLineByPos(code, start);
1150
1151 while(line)
1152 {
1153 if(shift)
1154 {
1155 if(*line == '\t' || *line == ' ')
1156 {
1157 deleteCode(code, line, line + 1);
1158 end--;
1159 changed = true;
1160 }
1161 }
1162 else
1163 {
1164 insertCode(code, line, "\t");
1165 end++;
1166
1167 changed = true;
1168 }
1169
1170 line = getNextLineByPos(code, line);
1171 if(line >= end) break;
1172 }
1173
1174 if(changed) {
1175
1176 if(has_selection) {
1177 code->cursor.position = start;
1178 code->cursor.selection = end;
1179 }
1180 else if (start <= end) code->cursor.position = end;
1181
1182 history(code);
1183 parseSyntaxColor(code);
1184 }
1185 }
1186 else inputSymbolBase(code, '\t');
1187}
1188
1189// Add a block-ending keyword or symbol, and put the cursor in the line between.
1190static void newLineAutoClose(Code* code)
1191{
1192 const char* blockEnd = tic_core_script_config(code->tic)->blockEnd;
1193 if (blockEnd != NULL)
1194 {
1195 newLine(code);
1196
1197 while(*blockEnd)
1198 inputSymbol(code, *blockEnd++);
1199
1200 upLine(code);
1201 goEnd(code);
1202 doTab(code, false, true);
1203 }
1204}
1205
1206static void setFindMode(Code* code)
1207{
1208 if(code->cursor.selection)
1209 {
1210 const char* end = MAX(code->cursor.position, code->cursor.selection);
1211 const char* start = MIN(code->cursor.position, code->cursor.selection);
1212 size_t len = end - start;
1213
1214 if(len > 0 && len < sizeof code->popup.text - 1)
1215 {
1216 memset(code->popup.text, 0, sizeof code->popup.text);
1217 memcpy(code->popup.text, start, len);
1218 }
1219 }
1220}
1221
1222static void setGotoMode(Code* code)
1223{
1224 code->jump.line = -1;
1225}
1226
1227static s32 funcCompare(const void* a, const void* b)
1228{
1229 const tic_outline_item* item1 = (const tic_outline_item*)a;
1230 const tic_outline_item* item2 = (const tic_outline_item*)b;
1231
1232 return strcmp(item1->pos, item2->pos);
1233}
1234
1235static void normalizeScroll(Code* code)
1236{
1237 if(code->scroll.x < 0) code->scroll.x = 0;
1238 if(code->scroll.y < 0) code->scroll.y = 0;
1239 else
1240 {
1241 s32 lines = getLinesCount(code);
1242 if(code->scroll.y > lines) code->scroll.y = lines;
1243 }
1244}
1245
1246static void centerScroll(Code* code)
1247{
1248 s32 col, line;
1249 getCursorPosition(code, &col, &line);
1250 code->scroll.x = col - CODE_EDITOR_WIDTH / getFontWidth(code) / 2;
1251 code->scroll.y = line - TEXT_BUFFER_HEIGHT / 2;
1252
1253 normalizeScroll(code);
1254}
1255
1256static void updateSidebarCode(Code* code)
1257{
1258 tic_mem* tic = code->tic;
1259
1260 const tic_outline_item* item = code->sidebar.items + code->sidebar.index;
1261
1262 if(code->sidebar.size && item && item->pos)
1263 {
1264 code->cursor.position = (char*)item->pos;
1265 code->cursor.selection = (char*)item->pos + item->size;
1266 }
1267 else
1268 {
1269 code->cursor.position = code->src;
1270 code->cursor.selection = NULL;
1271 }
1272
1273 centerScroll(code);
1274 updateEditor(code);
1275}
1276
1277static bool isFilterMatch(const char* buffer, s32 size, const char* filter)
1278{
1279 while(size--)
1280 if(tolower(*buffer++) == tolower(*filter))
1281 filter++;
1282
1283 return *filter == 0;
1284}
1285
1286static void drawFilterMatch(Code *code, s32 x, s32 y, const char* orig, s32 size, const char* filter)
1287{
1288 while(size--)
1289 {
1290 bool match = tolower(*orig) == tolower(*filter);
1291 u8 color = match ? tic_color_orange : tic_color_white;
1292
1293 if(code->shadowText)
1294 drawChar(code->tic, *orig, x+1, y+1, tic_color_black, code->altFont);
1295
1296 drawChar(code->tic, *orig, x, y, color, code->altFont);
1297 x += getFontWidth(code);
1298 if(match)
1299 filter++;
1300
1301 orig++;
1302 }
1303}
1304
1305static void initSidebarMode(Code* code)
1306{
1307 tic_mem* tic = code->tic;
1308
1309 code->sidebar.size = 0;
1310
1311 const tic_script_config* config = tic_core_script_config(tic);
1312
1313 if(config->getOutline)
1314 {
1315 s32 size = 0;
1316 const tic_outline_item* items = config->getOutline(code->src, &size);
1317
1318 if(items)
1319 {
1320 for(const tic_outline_item *it = items, *end = items + size; it != end ; ++it)
1321 {
1322 if(code->state[it->pos - code->src].syntax == SyntaxType_COMMENT)
1323 continue;
1324
1325 const char* filter = code->popup.text;
1326 if(*filter && !isFilterMatch(it->pos, it->size, filter))
1327 continue;
1328
1329 s32 last = code->sidebar.size++;
1330 code->sidebar.items = realloc(code->sidebar.items, code->sidebar.size * sizeof(tic_outline_item));
1331 code->sidebar.items[last] = *it;
1332 }
1333 }
1334 }
1335}
1336
1337static void setBookmarkMode(Code* code)
1338{
1339 code->sidebar.index = 0;
1340 code->sidebar.scroll = 0;
1341 code->sidebar.size = 0;
1342
1343 const char* ptr = code->src;
1344 const CodeState* state = code->state;
1345
1346 while(*ptr)
1347 {
1348 if(state->bookmark)
1349 {
1350 s32 last = code->sidebar.size++;
1351 code->sidebar.items = realloc(code->sidebar.items, code->sidebar.size * sizeof(tic_outline_item));
1352 tic_outline_item* item = &code->sidebar.items[last];
1353
1354 item->pos = ptr;
1355 item->size = getLineSize(ptr);
1356 }
1357
1358 ptr++;
1359 state++;
1360 }
1361
1362 updateSidebarCode(code);
1363}
1364
1365static void setOutlineMode(Code* code)
1366{
1367 code->sidebar.index = 0;
1368 code->sidebar.scroll = 0;
1369
1370 initSidebarMode(code);
1371
1372 qsort(code->sidebar.items, code->sidebar.size, sizeof(tic_outline_item), funcCompare);
1373 updateSidebarCode(code);
1374}
1375
1376static bool isIdle(Code* code)
1377{
1378 return code->anim.movie == &code->anim.idle;
1379}
1380
1381static void setCodeMode(Code* code, s32 mode)
1382{
1383 if(isIdle(code) && code->mode != mode)
1384 {
1385 code->anim.movie = resetMovie(&code->anim.show);
1386
1387 strcpy(code->popup.text, "");
1388
1389 code->popup.prevPos = code->cursor.position;
1390 code->popup.prevSel = code->cursor.selection;
1391
1392 switch(mode)
1393 {
1394 case TEXT_FIND_MODE: setFindMode(code); break;
1395 case TEXT_GOTO_MODE: setGotoMode(code); break;
1396 case TEXT_BOOKMARK_MODE: setBookmarkMode(code); break;
1397 case TEXT_OUTLINE_MODE: setOutlineMode(code); break;
1398 default: break;
1399 }
1400
1401 code->mode = mode;
1402 }
1403}
1404
1405static int getNumberOfLines(Code* code){
1406 char* pos = code->cursor.position;
1407 char* sel = code->cursor.selection;
1408
1409 char* start = MIN(pos, sel);
1410 while(*start == '\n') start++;
1411
1412 char* end = MAX(pos, sel);
1413 while(*end == '\n') end--;
1414
1415 char* iter = start;
1416 size_t lines = 1;
1417 while(iter <= end){
1418 if(*iter == '\n'){
1419 ++lines;
1420 }
1421 ++iter;
1422 }
1423 return lines;
1424}
1425
1426static char** getLines(Code* code, int lines){
1427 char* pos = code->cursor.position;
1428 char* sel = code->cursor.selection;
1429
1430
1431 char* start = MIN(pos, sel);
1432 while(*start == '\n') ++start;
1433
1434 char* end = MAX(pos, sel);
1435 while(*end == '\n') --end;
1436
1437 char* iter = start;
1438 iter = start;
1439
1440 char** line_locations = malloc(sizeof(char*)*lines+1);
1441 line_locations[0] = start;
1442
1443 for(int i = 1; i < lines; ++i){
1444 while(iter <=end && *iter!='\n') ++iter;
1445 line_locations[i] = ++iter;
1446 }
1447 return line_locations;
1448}
1449
1450static inline bool isLineCommented(const char* comment, const char* line){
1451 size_t size = strlen(comment);
1452 return memcmp(line, comment, size) == 0;
1453};
1454
1455static void addCommentToLine(Code* code, char* line, size_t size, const char* comment){
1456
1457 const char* end = line + getLineSize(line);
1458
1459 while((*line == ' ' || *line == '\t') && line < end) line++;
1460
1461 if(!isLineCommented(comment, line))
1462 {
1463 if (strlen(code->src) + size >= MAX_CODE)
1464 return;
1465
1466 insertCode(code, line, comment);
1467
1468 if(code->cursor.position > line)
1469 code->cursor.position += size;
1470 }
1471 else
1472 {
1473 deleteCode(code, line, line + size);
1474
1475 if(code->cursor.position > line + size)
1476 code->cursor.position -= size;
1477 }
1478
1479 code->cursor.selection = NULL;
1480
1481 history(code);
1482
1483 parseSyntaxColor(code);
1484}
1485
1486static void commentLine(Code* code)
1487{
1488 const char* comment = tic_core_script_config(code->tic)->singleComment;
1489 size_t size = strlen(comment);
1490
1491 if(code->cursor.selection){
1492 int selectionLines = getNumberOfLines(code);
1493 char** lines = getLines(code, selectionLines);
1494
1495 int comment_cursor = 0;
1496
1497 for (int i = 0; i<selectionLines; ++i){
1498
1499 char* first = lines[i];
1500 while(*first == ' ' || *first == '\t') ++first;
1501 bool lineIsComment = isLineCommented(comment, first);
1502
1503 if(i < selectionLines - 1) {
1504 if(*first != '\n' && lineIsComment) --comment_cursor;
1505 else if (*first !='\n') ++comment_cursor;
1506
1507 lines[i + 1] += (size * comment_cursor);
1508 }
1509
1510 if(*first !='\n') {
1511 addCommentToLine(code, lines[i], size, comment);
1512 }
1513 }
1514
1515 free(lines);
1516
1517 }
1518 else addCommentToLine(code, getLine(code), size, comment);
1519}
1520
1521static void dupLine(Code* code)
1522{
1523 char *start = getLine(code);
1524 if(*start)
1525 {
1526 s32 size = getLineSize(start) + 1;
1527
1528 insertCodeSize(code, start, start, size);
1529
1530 code->cursor.position += size;
1531
1532 history(code);
1533 parseSyntaxColor(code);
1534 updateEditor(code);
1535 }
1536}
1537
1538static bool goPrevBookmark(Code* code, char* ptr)
1539{
1540 const CodeState* state = getState(code, ptr);
1541 while(ptr >= code->src)
1542 {
1543 if(state->bookmark)
1544 {
1545 updateCursorPosition(code, ptr);
1546 centerScroll(code);
1547 return true;
1548 }
1549
1550 ptr--;
1551 state--;
1552 }
1553
1554 return false;
1555}
1556
1557static bool goNextBookmark(Code* code, char* ptr)
1558{
1559 const CodeState* state = getState(code, ptr);
1560 while(*ptr)
1561 {
1562 if(state->bookmark)
1563 {
1564 updateCursorPosition(code, ptr);
1565 centerScroll(code);
1566 return true;
1567 }
1568
1569 ptr++;
1570 state++;
1571 }
1572
1573 return false;
1574}
1575
1576static void processKeyboard(Code* code)
1577{
1578 tic_mem* tic = code->tic;
1579
1580 if(tic->ram->input.keyboard.data == 0) return;
1581
1582 bool usedClipboard = true;
1583
1584 switch(getClipboardEvent(code->studio))
1585 {
1586 case TIC_CLIPBOARD_CUT: cutToClipboard(code); break;
1587 case TIC_CLIPBOARD_COPY: copyToClipboard(code); break;
1588 case TIC_CLIPBOARD_PASTE: copyFromClipboard(code); break;
1589 default: usedClipboard = false; break;
1590 }
1591
1592 if(usedClipboard)
1593 {
1594 updateEditor(code);
1595 return;
1596 }
1597
1598 bool shift = tic_api_key(tic, tic_key_shift);
1599 bool ctrl = tic_api_key(tic, tic_key_ctrl);
1600 bool alt = tic_api_key(tic, tic_key_alt);
1601
1602 bool changedSelection = false;
1603 if(keyWasPressed(code->studio, tic_key_up)
1604 || keyWasPressed(code->studio, tic_key_down)
1605 || keyWasPressed(code->studio, tic_key_left)
1606 || keyWasPressed(code->studio, tic_key_right)
1607 || keyWasPressed(code->studio, tic_key_home)
1608 || keyWasPressed(code->studio, tic_key_end)
1609 || keyWasPressed(code->studio, tic_key_pageup)
1610 || keyWasPressed(code->studio, tic_key_pagedown))
1611 {
1612 if(!shift) code->cursor.selection = NULL;
1613 else if(code->cursor.selection == NULL) code->cursor.selection = code->cursor.position;
1614 changedSelection = true;
1615 }
1616
1617 bool usedKeybinding = true;
1618
1619 // handle bookmarks
1620 if(keyWasPressed(code->studio, tic_key_f1))
1621 {
1622 if(ctrl && shift)
1623 {
1624 for(CodeState* s = code->state, *end = s + TIC_CODE_SIZE; s != end; ++s)
1625 s->bookmark = 0;
1626 }
1627 else if(ctrl)
1628 {
1629 toggleBookmark(code, getLineByPos(code, code->cursor.position));
1630 }
1631 else if(shift)
1632 {
1633 if(!goPrevBookmark(code, getPrevLineByPos(code, code->cursor.position)))
1634 goPrevBookmark(code, code->src + strlen(code->src));
1635 }
1636 else
1637 {
1638 if(!goNextBookmark(code, getNextLineByPos(code, code->cursor.position)))
1639 goNextBookmark(code, code->src);
1640 }
1641 }
1642 else if(ctrl || alt)
1643 {
1644 bool ctrlHandled = true;
1645 if(ctrl)
1646 {
1647 if(keyWasPressed(code->studio, tic_key_tab)) doTab(code, shift, ctrl);
1648 else if(keyWasPressed(code->studio, tic_key_a)) selectAll(code);
1649 else if(keyWasPressed(code->studio, tic_key_z)) undo(code);
1650 else if(keyWasPressed(code->studio, tic_key_y)) redo(code);
1651 else if(keyWasPressed(code->studio, tic_key_f)) setCodeMode(code, TEXT_FIND_MODE);
1652 else if(keyWasPressed(code->studio, tic_key_g)) setCodeMode(code, TEXT_GOTO_MODE);
1653 else if(keyWasPressed(code->studio, tic_key_b)) setCodeMode(code, TEXT_BOOKMARK_MODE);
1654 else if(keyWasPressed(code->studio, tic_key_o)) setCodeMode(code, TEXT_OUTLINE_MODE);
1655 else if(keyWasPressed(code->studio, tic_key_n)) downLine(code);
1656 else if(keyWasPressed(code->studio, tic_key_p)) upLine(code);
1657 else if(keyWasPressed(code->studio, tic_key_e)) endLine(code);
1658 else if(keyWasPressed(code->studio, tic_key_d)) dupLine(code);
1659 else if(keyWasPressed(code->studio, tic_key_slash)) commentLine(code);
1660 else if(keyWasPressed(code->studio, tic_key_home)) goCodeHome(code);
1661 else if(keyWasPressed(code->studio, tic_key_end)) goCodeEnd(code);
1662 else ctrlHandled = false;
1663 }
1664
1665 bool ctrlAltHandled = true;
1666 if(keyWasPressed(code->studio, tic_key_left)) leftWord(code);
1667 else if(keyWasPressed(code->studio, tic_key_right)) rightWord(code);
1668 else if(keyWasPressed(code->studio, tic_key_delete)) deleteWord(code);
1669 else if(keyWasPressed(code->studio, tic_key_backspace)) backspaceWord(code);
1670 else ctrlAltHandled = false;
1671
1672 usedKeybinding = (ctrlHandled || ctrlAltHandled);
1673 }
1674 else
1675 {
1676 if(keyWasPressed(code->studio, tic_key_up)) upLine(code);
1677 else if(keyWasPressed(code->studio, tic_key_down)) downLine(code);
1678 else if(keyWasPressed(code->studio, tic_key_left)) leftColumn(code);
1679 else if(keyWasPressed(code->studio, tic_key_right)) rightColumn(code);
1680 else if(keyWasPressed(code->studio, tic_key_home)) goHome(code);
1681 else if(keyWasPressed(code->studio, tic_key_end)) goEnd(code);
1682 else if(keyWasPressed(code->studio, tic_key_pageup)) pageUp(code);
1683 else if(keyWasPressed(code->studio, tic_key_pagedown)) pageDown(code);
1684 else if(keyWasPressed(code->studio, tic_key_delete)) deleteChar(code);
1685 else if(keyWasPressed(code->studio, tic_key_backspace)) backspaceChar(code);
1686 else if(keyWasPressed(code->studio, tic_key_return)) newLine(code);
1687 else if(keyWasPressed(code->studio, tic_key_tab)) doTab(code, shift, ctrl);
1688 else usedKeybinding = false;
1689 }
1690
1691 if(!usedKeybinding)
1692 {
1693 if(shift && keyWasPressed(code->studio, tic_key_return))
1694 {
1695 newLineAutoClose(code);
1696 usedKeybinding = true;
1697 }
1698 }
1699
1700 if(changedSelection || usedKeybinding) updateEditor(code);
1701}
1702
1703static void processMouse(Code* code)
1704{
1705 tic_mem* tic = code->tic;
1706
1707 tic_rect rect = {BOOKMARK_WIDTH, TOOLBAR_SIZE, CODE_EDITOR_WIDTH, CODE_EDITOR_HEIGHT};
1708
1709 if(checkMousePos(code->studio, &rect))
1710 {
1711 bool useDrag = (code->mode == TEXT_DRAG_CODE && checkMouseDown(code->studio, &rect, tic_mouse_left))
1712 || checkMouseDown(code->studio, &rect, tic_mouse_right);
1713 setCursor(code->studio, code->mode == TEXT_DRAG_CODE || useDrag ? tic_cursor_hand : tic_cursor_ibeam);
1714
1715 if(code->scroll.active)
1716 {
1717 if(useDrag)
1718 {
1719 code->scroll.x = (code->scroll.start.x - tic_api_mouse(tic).x) / getFontWidth(code);
1720 code->scroll.y = (code->scroll.start.y - tic_api_mouse(tic).y) / STUDIO_TEXT_HEIGHT;
1721
1722 normalizeScroll(code);
1723 }
1724 else code->scroll.active = false;
1725 }
1726 else
1727 {
1728 if(useDrag)
1729 {
1730 code->scroll.active = true;
1731
1732 code->scroll.start.x = tic_api_mouse(tic).x + code->scroll.x * getFontWidth(code);
1733 code->scroll.start.y = tic_api_mouse(tic).y + code->scroll.y * STUDIO_TEXT_HEIGHT;
1734 }
1735 else
1736 {
1737 if(checkMouseDblClick(code->studio, &rect, tic_mouse_left))
1738 {
1739 code->cursor.selection = leftWordPos(code);
1740 code->cursor.position = rightWordPos(code);
1741 }
1742 else if(checkMouseDown(code->studio, &rect, tic_mouse_left))
1743 {
1744 s32 mx = tic_api_mouse(tic).x;
1745 s32 my = tic_api_mouse(tic).y;
1746
1747 s32 x = (mx - rect.x) / getFontWidth(code);
1748 s32 y = (my - rect.y) / STUDIO_TEXT_HEIGHT;
1749
1750 char* position = code->cursor.position;
1751 setCursorPosition(code, x + code->scroll.x, y + code->scroll.y);
1752
1753 if(tic_api_key(tic, tic_key_shift))
1754 {
1755 code->cursor.selection = code->cursor.position;
1756 code->cursor.position = position;
1757 }
1758 else if(!code->cursor.mouseDownPosition)
1759 {
1760 code->cursor.selection = code->cursor.position;
1761 code->cursor.mouseDownPosition = code->cursor.position;
1762 }
1763 }
1764 else
1765 {
1766 if(code->cursor.mouseDownPosition == code->cursor.position)
1767 code->cursor.selection = NULL;
1768
1769 code->cursor.mouseDownPosition = NULL;
1770 }
1771 }
1772 }
1773 }
1774}
1775
1776static void textDragTick(Code* code)
1777{
1778 tic_mem* tic = code->tic;
1779
1780 processMouse(code);
1781
1782 tic_api_cls(code->tic, getConfig(code->studio)->theme.code.BG);
1783
1784 drawCode(code, true);
1785 drawStatus(code);
1786}
1787
1788static void textEditTick(Code* code)
1789{
1790 tic_mem* tic = code->tic;
1791
1792 // process scroll
1793 {
1794 tic80_input* input = &code->tic->ram->input;
1795
1796 tic_point scroll = {input->mouse.scrollx, input->mouse.scrolly};
1797
1798 if(tic_api_key(tic, tic_key_shift))
1799 scroll.x = scroll.y;
1800
1801 s32* val = scroll.x ? &code->scroll.x : scroll.y ? &code->scroll.y : NULL;
1802
1803 if(val)
1804 {
1805 enum{Scroll = 3};
1806 s32 delta = scroll.x ? scroll.x : scroll.y;
1807 *val += delta > 0 ? -Scroll : Scroll;
1808 normalizeScroll(code);
1809 }
1810 }
1811
1812 processKeyboard(code);
1813
1814 if(!tic_api_key(tic, tic_key_ctrl) && !tic_api_key(tic, tic_key_alt))
1815 {
1816 char sym = getKeyboardText(code->studio);
1817
1818 if(sym)
1819 {
1820 inputSymbol(code, sym);
1821 updateEditor(code);
1822 }
1823 }
1824
1825 processMouse(code);
1826
1827 tic_api_cls(code->tic, getConfig(code->studio)->theme.code.BG);
1828
1829 drawCode(code, true);
1830 drawStatus(code);
1831}
1832
1833static void drawPopupBar(Code* code, const char* title)
1834{
1835 s32 pos = code->anim.pos;
1836
1837 enum {TextX = BOOKMARK_WIDTH};
1838
1839
1840 tic_api_rect(code->tic, 0, TOOLBAR_SIZE + pos, TIC80_WIDTH, TIC_FONT_HEIGHT + 1, tic_color_grey);
1841
1842 s32 textY = (TOOLBAR_SIZE + 1) + pos;
1843
1844 if(code->shadowText)
1845 tic_api_print(code->tic, title, TextX+1, textY+1, tic_color_black, true, 1, code->altFont);
1846
1847 tic_api_print(code->tic, title, TextX, textY, tic_color_white, true, 1, code->altFont);
1848
1849 if(code->shadowText)
1850 tic_api_print(code->tic, code->popup.text, TextX + (s32)strlen(title) * getFontWidth(code) + 1, textY+1, tic_color_black, true, 1, code->altFont);
1851
1852 tic_api_print(code->tic, code->popup.text, TextX + (s32)strlen(title) * getFontWidth(code), textY, tic_color_white, true, 1, code->altFont);
1853
1854 drawCursor(code, TextX+(s32)(strlen(title) + strlen(code->popup.text)) * getFontWidth(code), textY, ' ');
1855}
1856
1857static void updateFindCode(Code* code, char* pos)
1858{
1859 if(pos)
1860 {
1861 code->cursor.position = pos;
1862 code->cursor.selection = pos + strlen(code->popup.text);
1863
1864 centerScroll(code);
1865 updateEditor(code);
1866 }
1867}
1868
1869static char* upStrStr(const char* start, const char* from, const char* substr)
1870{
1871 const char* ptr = from-1;
1872 size_t len = strlen(substr);
1873
1874 if(len > 0)
1875 {
1876 while(ptr >= start)
1877 {
1878 if(memcmp(ptr, substr, len) == 0)
1879 return (char*)ptr;
1880
1881 ptr--;
1882 }
1883 }
1884
1885 return NULL;
1886}
1887
1888static char* downStrStr(const char* start, const char* from, const char* substr)
1889{
1890 return strstr(from, substr);
1891}
1892
1893static void textFindTick(Code* code)
1894{
1895 if(keyWasPressed(code->studio, tic_key_return)) setCodeMode(code, TEXT_EDIT_MODE);
1896 else if(keyWasPressed(code->studio, tic_key_up)
1897 || keyWasPressed(code->studio, tic_key_down)
1898 || keyWasPressed(code->studio, tic_key_left)
1899 || keyWasPressed(code->studio, tic_key_right))
1900 {
1901 if(*code->popup.text)
1902 {
1903 bool reverse = keyWasPressed(code->studio, tic_key_up) || keyWasPressed(code->studio, tic_key_left);
1904 char* (*func)(const char*, const char*, const char*) = reverse ? upStrStr : downStrStr;
1905 char* from = reverse ? MIN(code->cursor.position, code->cursor.selection) : MAX(code->cursor.position, code->cursor.selection);
1906 char* pos = func(code->src, from, code->popup.text);
1907 updateFindCode(code, pos);
1908 }
1909 }
1910 else if(keyWasPressed(code->studio, tic_key_backspace))
1911 {
1912 if(*code->popup.text)
1913 {
1914 code->popup.text[strlen(code->popup.text)-1] = '\0';
1915 updateFindCode(code, strstr(code->src, code->popup.text));
1916 }
1917 }
1918
1919 char sym = getKeyboardText(code->studio);
1920
1921 if(sym)
1922 {
1923 if(strlen(code->popup.text) + 1 < sizeof code->popup.text)
1924 {
1925 char str[] = {sym , 0};
1926 strcat(code->popup.text, str);
1927 updateFindCode(code, strstr(code->src, code->popup.text));
1928 }
1929 }
1930
1931 tic_api_cls(code->tic, getConfig(code->studio)->theme.code.BG);
1932
1933 drawCode(code, false);
1934 drawPopupBar(code, "FIND:");
1935 drawStatus(code);
1936}
1937
1938static void updateGotoCode(Code* code)
1939{
1940 s32 line = atoi(code->popup.text);
1941
1942 if(line) line--;
1943
1944 s32 count = getLinesCount(code);
1945
1946 if(line > count) line = count;
1947
1948 code->cursor.selection = NULL;
1949 setCursorPosition(code, 0, line);
1950
1951 code->jump.line = line;
1952
1953 centerScroll(code);
1954 updateEditor(code);
1955}
1956
1957static void textGoToTick(Code* code)
1958{
1959 tic_mem* tic = code->tic;
1960
1961 if(keyWasPressed(code->studio, tic_key_return))
1962 {
1963 if(*code->popup.text)
1964 updateGotoCode(code);
1965
1966 setCodeMode(code, TEXT_EDIT_MODE);
1967 }
1968 else if(keyWasPressed(code->studio, tic_key_backspace))
1969 {
1970 if(*code->popup.text)
1971 {
1972 code->popup.text[strlen(code->popup.text)-1] = '\0';
1973 updateGotoCode(code);
1974 }
1975 }
1976
1977 char sym = getKeyboardText(code->studio);
1978
1979 if(sym)
1980 {
1981 if(strlen(code->popup.text)+1 < sizeof code->popup.text && sym >= '0' && sym <= '9')
1982 {
1983 char str[] = {sym, 0};
1984 strcat(code->popup.text, str);
1985 updateGotoCode(code);
1986 }
1987 }
1988
1989 tic_api_cls(tic, getConfig(code->studio)->theme.code.BG);
1990
1991 if(code->jump.line >= 0)
1992 tic_api_rect(tic, 0, (code->jump.line - code->scroll.y) * (TIC_FONT_HEIGHT+1) + TOOLBAR_SIZE,
1993 TIC80_WIDTH, TIC_FONT_HEIGHT+2, getConfig(code->studio)->theme.code.select);
1994
1995 drawCode(code, false);
1996 drawPopupBar(code, "GOTO:");
1997 drawStatus(code);
1998}
1999
2000static void drawSidebarBar(Code* code, s32 x, s32 y)
2001{
2002 tic_mem* tic = code->tic;
2003 tic_rect rect = {x, y, TIC80_WIDTH - x, TIC80_HEIGHT - y};
2004
2005 if(checkMousePos(code->studio, &rect))
2006 {
2007 s32 mx = tic_api_mouse(tic).y - rect.y;
2008 mx /= STUDIO_TEXT_HEIGHT;
2009 mx += code->sidebar.scroll;
2010
2011 if(mx >= 0 && mx < code->sidebar.size && code->sidebar.items[mx].pos)
2012 {
2013 setCursor(code->studio, tic_cursor_hand);
2014
2015 if(checkMouseDown(code->studio, &rect, tic_mouse_left))
2016 {
2017 code->sidebar.index = mx;
2018 updateSidebarCode(code);
2019 }
2020
2021 if(checkMouseClick(code->studio, &rect, tic_mouse_left))
2022 setCodeMode(code, TEXT_EDIT_MODE);
2023 }
2024 }
2025
2026 tic_api_rect(code->tic, rect.x-1, rect.y, rect.w+1, rect.h, tic_color_grey);
2027
2028 y -= code->sidebar.scroll * STUDIO_TEXT_HEIGHT - 1;
2029
2030 char filter[STUDIO_TEXT_BUFFER_WIDTH];
2031 strncpy(filter, code->popup.text, sizeof(filter));
2032
2033 if(code->sidebar.size)
2034 {
2035 tic_api_rect(code->tic, rect.x - 1, rect.y + (code->sidebar.index - code->sidebar.scroll) * STUDIO_TEXT_HEIGHT,
2036 rect.w + 1, TIC_FONT_HEIGHT + 2, tic_color_red);
2037
2038 for(const tic_outline_item* ptr = code->sidebar.items, *end = ptr + code->sidebar.size;
2039 ptr != end; ptr++, y += STUDIO_TEXT_HEIGHT)
2040 drawFilterMatch(code, x, y, ptr->pos, ptr->size, filter);
2041 }
2042 else
2043 {
2044 if(code->shadowText)
2045 tic_api_print(code->tic, "(empty)", x+1, y+1, tic_color_black, true, 1, code->altFont);
2046
2047 tic_api_print(code->tic, "(empty)", x, y, tic_color_white, true, 1, code->altFont);
2048 }
2049}
2050
2051static void normSidebarScroll(Code* code)
2052{
2053 code->sidebar.scroll = code->sidebar.size > TEXT_BUFFER_HEIGHT
2054 ? CLAMP(code->sidebar.scroll, 0, code->sidebar.size - TEXT_BUFFER_HEIGHT) : 0;
2055}
2056
2057static void updateSidebarIndex(Code* code, s32 value)
2058{
2059 if(code->sidebar.size == 0)
2060 return;
2061
2062 code->sidebar.index = CLAMP(value, 0, code->sidebar.size - 1);
2063
2064 if(code->sidebar.index - code->sidebar.scroll < 0)
2065 code->sidebar.scroll -= TEXT_BUFFER_HEIGHT;
2066 else if(code->sidebar.index - code->sidebar.scroll >= TEXT_BUFFER_HEIGHT)
2067 code->sidebar.scroll += TEXT_BUFFER_HEIGHT;
2068
2069 updateSidebarCode(code);
2070 normSidebarScroll(code);
2071}
2072
2073static void processSidebar(Code* code)
2074{
2075 // process scroll
2076 {
2077 tic80_input* input = &code->tic->ram->input;
2078
2079 if(input->mouse.scrolly)
2080 {
2081 enum{Scroll = 3};
2082 s32 delta = input->mouse.scrolly > 0 ? -Scroll : Scroll;
2083 code->sidebar.scroll += delta;
2084 normSidebarScroll(code);
2085 }
2086 }
2087
2088 if(keyWasPressed(code->studio, tic_key_up))
2089 updateSidebarIndex(code, code->sidebar.index - 1);
2090
2091 else if(keyWasPressed(code->studio, tic_key_down))
2092 updateSidebarIndex(code, code->sidebar.index + 1);
2093
2094 else if(keyWasPressed(code->studio, tic_key_left) || keyWasPressed(code->studio, tic_key_pageup))
2095 updateSidebarIndex(code, code->sidebar.index - TEXT_BUFFER_HEIGHT);
2096
2097 else if(keyWasPressed(code->studio, tic_key_right) || keyWasPressed(code->studio, tic_key_pagedown))
2098 updateSidebarIndex(code, code->sidebar.index + TEXT_BUFFER_HEIGHT);
2099
2100 else if(keyWasPressed(code->studio, tic_key_home))
2101 updateSidebarIndex(code, 0);
2102
2103 else if(keyWasPressed(code->studio, tic_key_end))
2104 updateSidebarIndex(code, code->sidebar.size - 1);
2105
2106 else if(keyWasPressed(code->studio, tic_key_return))
2107 {
2108 updateSidebarCode(code);
2109 setCodeMode(code, TEXT_EDIT_MODE);
2110 }
2111}
2112
2113static void textBookmarkTick(Code* code)
2114{
2115 processSidebar(code);
2116
2117 tic_api_cls(code->tic, getConfig(code->studio)->theme.code.BG);
2118
2119 drawCode(code, false);
2120 drawStatus(code);
2121 drawSidebarBar(code, (TIC80_WIDTH - SIDEBAR_WIDTH) + code->anim.sidebar, TIC_FONT_HEIGHT+1);
2122}
2123
2124static void textOutlineTick(Code* code)
2125{
2126 processSidebar(code);
2127
2128 if(keyWasPressed(code->studio, tic_key_backspace))
2129 {
2130 if(*code->popup.text)
2131 {
2132 code->popup.text[strlen(code->popup.text)-1] = '\0';
2133 setOutlineMode(code);
2134 }
2135 }
2136
2137 char sym = getKeyboardText(code->studio);
2138
2139 if(sym)
2140 {
2141 if(strlen(code->popup.text) + 1 < sizeof code->popup.text)
2142 {
2143 char str[] = {sym, 0};
2144 strcat(code->popup.text, str);
2145 setOutlineMode(code);
2146 }
2147 }
2148
2149 tic_api_cls(code->tic, getConfig(code->studio)->theme.code.BG);
2150
2151 drawCode(code, false);
2152 drawStatus(code);
2153 drawSidebarBar(code, (TIC80_WIDTH - SIDEBAR_WIDTH) + code->anim.sidebar, 2*(TIC_FONT_HEIGHT+1));
2154 drawPopupBar(code, "FUNC:");
2155}
2156
2157static void drawFontButton(Code* code, s32 x, s32 y)
2158{
2159 tic_mem* tic = code->tic;
2160
2161 enum {Size = TIC_FONT_WIDTH};
2162 tic_rect rect = {x, y, Size, Size};
2163
2164 bool over = false;
2165 if(checkMousePos(code->studio, &rect))
2166 {
2167 setCursor(code->studio, tic_cursor_hand);
2168
2169 showTooltip(code->studio, "SWITCH FONT");
2170
2171 over = true;
2172
2173 if(checkMouseClick(code->studio, &rect, tic_mouse_left))
2174 {
2175 code->altFont = !code->altFont;
2176 }
2177 }
2178
2179 drawChar(tic, 'F', x, y, over ? tic_color_grey : tic_color_light_grey, code->altFont);
2180}
2181
2182static void drawShadowButton(Code* code, s32 x, s32 y)
2183{
2184 tic_mem* tic = code->tic;
2185
2186 enum {Size = TIC_FONT_WIDTH};
2187 tic_rect rect = {x, y, Size, Size};
2188
2189 bool over = false;
2190 if(checkMousePos(code->studio, &rect))
2191 {
2192 setCursor(code->studio, tic_cursor_hand);
2193
2194 showTooltip(code->studio, "SHOW SHADOW");
2195
2196 over = true;
2197
2198 if(checkMouseClick(code->studio, &rect, tic_mouse_left))
2199 {
2200 code->shadowText = !code->shadowText;
2201 }
2202 }
2203
2204 drawBitIcon(code->studio, tic_icon_shadow, x, y, over && !code->shadowText ? tic_color_grey : tic_color_light_grey);
2205
2206 if(code->shadowText)
2207 drawBitIcon(code->studio, tic_icon_shadow2, x, y, tic_color_black);
2208}
2209
2210static void drawRunButton(Code* code, s32 x, s32 y)
2211{
2212 tic_mem* tic = code->tic;
2213
2214 enum {Size = TIC_FONT_WIDTH};
2215 tic_rect rect = {x, y, Size, Size};
2216
2217 bool over = false;
2218 if(checkMousePos(code->studio, &rect))
2219 {
2220 setCursor(code->studio, tic_cursor_hand);
2221 showTooltip(code->studio, "RUN [ctrl+r]");
2222 over = true;
2223
2224 if(checkMouseClick(code->studio, &rect, tic_mouse_left))
2225 runGame(code->studio);
2226 }
2227
2228 drawBitIcon(code->studio, tic_icon_run, x, y, over ? tic_color_grey : tic_color_light_grey);
2229}
2230
2231static void drawCodeToolbar(Code* code)
2232{
2233 tic_api_rect(code->tic, 0, 0, TIC80_WIDTH, TOOLBAR_SIZE, tic_color_white);
2234
2235 static const struct Button {u8 icon; const char* tip;} Buttons[] =
2236 {
2237 {tic_icon_hand, "DRAG [right mouse]"},
2238 {tic_icon_find, "FIND [ctrl+f]"},
2239 {tic_icon_goto, "GOTO [ctrl+g]"},
2240 {tic_icon_bookmark, "BOOKMARKS [ctrl+b]"},
2241 {tic_icon_outline, "OUTLINE [ctrl+o]"},
2242 };
2243
2244 enum {Count = COUNT_OF(Buttons), Size = 7};
2245
2246 for(s32 i = 0; i < Count; i++)
2247 {
2248 const struct Button* btn = &Buttons[i];
2249 tic_rect rect = {TIC80_WIDTH + (i - Count) * Size, 0, Size, Size};
2250
2251 bool over = false;
2252 if(checkMousePos(code->studio, &rect))
2253 {
2254 setCursor(code->studio, tic_cursor_hand);
2255
2256 showTooltip(code->studio, btn->tip);
2257
2258 over = true;
2259
2260 if(checkMouseClick(code->studio, &rect, tic_mouse_left))
2261 {
2262 if(code->mode == i) code->escape(code);
2263 else setCodeMode(code, i);
2264 }
2265 }
2266
2267 bool active = i == code->mode && isIdle(code);
2268 if (active)
2269 {
2270 tic_api_rect(code->tic, rect.x, rect.y, Size, Size, tic_color_grey);
2271 drawBitIcon(code->studio, btn->icon, rect.x, rect.y + 1, tic_color_black);
2272 }
2273
2274 drawBitIcon(code->studio, btn->icon, rect.x, rect.y, active ? tic_color_white : (over ? tic_color_grey : tic_color_light_grey));
2275 }
2276
2277 drawFontButton(code, TIC80_WIDTH - (Count+3) * Size, 1);
2278 drawShadowButton(code, TIC80_WIDTH - (Count+2) * Size, 0);
2279 drawRunButton(code, TIC80_WIDTH - (Count+1) * Size, 0);
2280
2281 drawToolbar(code->studio, code->tic, false);
2282}
2283
2284static void tick(Code* code)
2285{
2286 processAnim(code->anim.movie, code);
2287
2288 if(code->cursor.delay)
2289 code->cursor.delay--;
2290
2291 switch(code->mode)
2292 {
2293 case TEXT_DRAG_CODE: textDragTick(code); break;
2294 case TEXT_EDIT_MODE: textEditTick(code); break;
2295 case TEXT_FIND_MODE: textFindTick(code); break;
2296 case TEXT_GOTO_MODE: textGoToTick(code); break;
2297 case TEXT_BOOKMARK_MODE:textBookmarkTick(code); break;
2298 case TEXT_OUTLINE_MODE: textOutlineTick(code); break;
2299 }
2300
2301 drawCodeToolbar(code);
2302
2303 code->tickCounter++;
2304}
2305
2306static void escape(Code* code)
2307{
2308 switch(code->mode)
2309 {
2310 case TEXT_EDIT_MODE:
2311 break;
2312 case TEXT_DRAG_CODE:
2313 setCodeMode(code, TEXT_EDIT_MODE);
2314 break;
2315 default:
2316 code->anim.movie = resetMovie(&code->anim.hide);
2317
2318 code->cursor.position = code->popup.prevPos;
2319 code->cursor.selection = code->popup.prevSel;
2320 code->popup.prevSel = code->popup.prevPos = NULL;
2321
2322 updateEditor(code);
2323 }
2324}
2325
2326static void onStudioEvent(Code* code, StudioEvent event)
2327{
2328 switch(event)
2329 {
2330 case TIC_TOOLBAR_CUT: cutToClipboard(code); break;
2331 case TIC_TOOLBAR_COPY: copyToClipboard(code); break;
2332 case TIC_TOOLBAR_PASTE: copyFromClipboard(code); break;
2333 case TIC_TOOLBAR_UNDO: undo(code); break;
2334 case TIC_TOOLBAR_REDO: redo(code); break;
2335 }
2336}
2337
2338static void emptyDone(void* data) {}
2339
2340static void setIdle(void* data)
2341{
2342 Code* code = data;
2343 code->anim.movie = resetMovie(&code->anim.idle);
2344}
2345
2346static void setEditMode(void* data)
2347{
2348 Code* code = data;
2349 code->anim.movie = resetMovie(&code->anim.idle);
2350 setCodeMode(code, TEXT_EDIT_MODE);
2351}
2352
2353static void freeAnim(Code* code)
2354{
2355 FREE(code->anim.show.items);
2356 FREE(code->anim.hide.items);
2357}
2358
2359void initCode(Code* code, Studio* studio)
2360{
2361 bool firstLoad = code->state == NULL;
2362 FREE(code->state);
2363 freeAnim(code);
2364
2365 if(code->history) history_delete(code->history);
2366
2367 tic_code* src = &getMemory(studio)->cart.code;
2368
2369 *code = (Code)
2370 {
2371 .studio = studio,
2372 .tic = getMemory(studio),
2373 .src = src->data,
2374 .tick = tick,
2375 .escape = escape,
2376 .cursor = {{src->data, NULL, 0}, NULL, 0},
2377 .scroll = {0, 0, {0, 0}, false},
2378 .state = calloc(TIC_CODE_SIZE, sizeof(CodeState)),
2379 .tickCounter = 0,
2380 .history = NULL,
2381 .mode = TEXT_EDIT_MODE,
2382 .jump = {.line = -1},
2383 .popup =
2384 {
2385 .prevPos = NULL,
2386 .prevSel = NULL,
2387 },
2388 .sidebar =
2389 {
2390 .items = NULL,
2391 .size = 0,
2392 .index = 0,
2393 .scroll = 0,
2394 },
2395 .matchedDelim = NULL,
2396 .altFont = firstLoad ? getConfig(studio)->theme.code.altFont : code->altFont,
2397 .shadowText = getConfig(studio)->theme.code.shadow,
2398 .anim =
2399 {
2400 .idle = {.done = emptyDone,},
2401
2402 .show = MOVIE_DEF(STUDIO_ANIM_TIME, setIdle,
2403 {
2404 {-TOOLBAR_SIZE, 0, STUDIO_ANIM_TIME, &code->anim.pos, AnimEaseIn},
2405 {SIDEBAR_WIDTH, 0, STUDIO_ANIM_TIME, &code->anim.sidebar, AnimEaseIn},
2406 }),
2407
2408 .hide = MOVIE_DEF(STUDIO_ANIM_TIME, setEditMode,
2409 {
2410 {0, -TOOLBAR_SIZE, STUDIO_ANIM_TIME, &code->anim.pos, AnimEaseIn},
2411 {0, SIDEBAR_WIDTH, STUDIO_ANIM_TIME, &code->anim.sidebar, AnimEaseIn},
2412 }),
2413 },
2414 .event = onStudioEvent,
2415 .update = update,
2416 };
2417
2418 code->anim.movie = resetMovie(&code->anim.idle);
2419
2420 packState(code);
2421 code->history = history_create(code->state, sizeof(CodeState) * TIC_CODE_SIZE);
2422
2423 update(code);
2424}
2425
2426void freeCode(Code* code)
2427{
2428 freeAnim(code);
2429
2430 history_delete(code->history);
2431 free(code->state);
2432 free(code);
2433}
2434