2017-09-26 08:59:34 +02:00
|
|
|
// MIT License
|
|
|
|
|
|
|
|
// Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com
|
|
|
|
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
|
|
// in the Software without restriction, including without limitation the rights
|
|
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
|
|
// copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
// SOFTWARE.
|
|
|
|
|
|
|
|
#include "code.h"
|
|
|
|
#include "history.h"
|
|
|
|
|
2017-12-20 21:39:21 +01:00
|
|
|
#include <ctype.h>
|
|
|
|
|
2017-09-26 08:59:34 +02:00
|
|
|
#define TEXT_CURSOR_DELAY (TIC_FRAMERATE / 2)
|
|
|
|
#define TEXT_CURSOR_BLINK_PERIOD TIC_FRAMERATE
|
|
|
|
#define TEXT_BUFFER_WIDTH STUDIO_TEXT_BUFFER_WIDTH
|
2017-10-20 09:32:31 +02:00
|
|
|
#define TEXT_BUFFER_HEIGHT ((TIC80_HEIGHT - TOOLBAR_SIZE - STUDIO_TEXT_HEIGHT) / STUDIO_TEXT_HEIGHT)
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
struct OutlineItem
|
|
|
|
{
|
|
|
|
char name[STUDIO_TEXT_BUFFER_WIDTH];
|
|
|
|
char* pos;
|
|
|
|
};
|
|
|
|
|
2017-10-20 09:32:31 +02:00
|
|
|
#define OUTLINE_SIZE ((TIC80_HEIGHT - TOOLBAR_SIZE*2)/TIC_FONT_HEIGHT)
|
2017-09-26 08:59:34 +02:00
|
|
|
#define OUTLINE_ITEMS_SIZE (OUTLINE_SIZE * sizeof(OutlineItem))
|
|
|
|
|
|
|
|
static void history(Code* code)
|
|
|
|
{
|
|
|
|
if(history_add(code->history))
|
|
|
|
history_add(code->cursorHistory);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void drawStatus(Code* code)
|
|
|
|
{
|
|
|
|
const s32 Height = TIC_FONT_HEIGHT + 1;
|
2017-10-20 09:37:30 +02:00
|
|
|
code->tic->api.rect(code->tic, 0, TIC80_HEIGHT - Height, TIC80_WIDTH, Height, (tic_color_white));
|
2017-09-26 08:59:34 +02:00
|
|
|
code->tic->api.fixed_text(code->tic, code->status, 0, TIC80_HEIGHT - TIC_FONT_HEIGHT, getConfig()->theme.code.bg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void drawCursor(Code* code, s32 x, s32 y, char symbol)
|
|
|
|
{
|
|
|
|
bool inverse = code->cursor.delay || code->tickCounter % TEXT_CURSOR_BLINK_PERIOD < TEXT_CURSOR_BLINK_PERIOD / 2;
|
|
|
|
|
|
|
|
if(inverse)
|
|
|
|
{
|
|
|
|
code->tic->api.rect(code->tic, x-1, y-1, TIC_FONT_WIDTH+1, TIC_FONT_HEIGHT+1, getConfig()->theme.code.cursor);
|
|
|
|
|
|
|
|
if(symbol)
|
|
|
|
code->tic->api.draw_char(code->tic, symbol, x, y, getConfig()->theme.code.bg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void drawCode(Code* code, bool withCursor)
|
|
|
|
{
|
|
|
|
s32 xStart = code->rect.x - code->scroll.x * STUDIO_TEXT_WIDTH;
|
|
|
|
s32 x = xStart;
|
|
|
|
s32 y = code->rect.y - code->scroll.y * STUDIO_TEXT_HEIGHT;
|
2017-12-14 15:08:04 +01:00
|
|
|
char* pointer = code->src;
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
u8* colorPointer = code->colorBuffer;
|
|
|
|
|
2017-10-13 18:25:10 +02:00
|
|
|
struct { char* start; char* end; } selection = {SDL_min(code->cursor.selection, code->cursor.position),
|
2017-09-26 08:59:34 +02:00
|
|
|
SDL_max(code->cursor.selection, code->cursor.position)};
|
|
|
|
|
|
|
|
struct { s32 x; s32 y; char symbol; } cursor = {-1, -1, 0};
|
|
|
|
|
|
|
|
while(*pointer)
|
|
|
|
{
|
|
|
|
char symbol = *pointer;
|
|
|
|
|
2017-12-20 22:10:44 +01:00
|
|
|
if(x >= -TIC_FONT_WIDTH && x < TIC80_WIDTH && y >= -TIC_FONT_HEIGHT && y < TIC80_HEIGHT )
|
|
|
|
{
|
|
|
|
if(code->cursor.selection && pointer >= selection.start && pointer < selection.end)
|
|
|
|
code->tic->api.rect(code->tic, x-1, y-1, TIC_FONT_WIDTH+1, TIC_FONT_HEIGHT+1, getConfig()->theme.code.select);
|
|
|
|
else if(getConfig()->theme.code.shadow)
|
|
|
|
{
|
|
|
|
code->tic->api.draw_char(code->tic, symbol, x+1, y+1, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
code->tic->api.draw_char(code->tic, symbol, x, y, *colorPointer);
|
|
|
|
}
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
if(code->cursor.position == pointer)
|
|
|
|
cursor.x = x, cursor.y = y, cursor.symbol = symbol;
|
|
|
|
|
|
|
|
if(symbol == '\n')
|
|
|
|
{
|
|
|
|
x = xStart;
|
|
|
|
y += STUDIO_TEXT_HEIGHT;
|
|
|
|
}
|
|
|
|
else x += STUDIO_TEXT_WIDTH;
|
|
|
|
|
|
|
|
pointer++;
|
|
|
|
colorPointer++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(code->cursor.position == pointer)
|
|
|
|
cursor.x = x, cursor.y = y;
|
|
|
|
|
|
|
|
if(withCursor && cursor.x >= 0 && cursor.y >= 0)
|
|
|
|
drawCursor(code, cursor.x, cursor.y, cursor.symbol);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void getCursorPosition(Code* code, s32* x, s32* y)
|
|
|
|
{
|
|
|
|
*x = 0;
|
|
|
|
*y = 0;
|
|
|
|
|
2017-12-14 15:08:04 +01:00
|
|
|
const char* pointer = code->src;
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
while(*pointer)
|
|
|
|
{
|
|
|
|
if(code->cursor.position == pointer) return;
|
|
|
|
|
|
|
|
if(*pointer == '\n')
|
|
|
|
{
|
|
|
|
*x = 0;
|
|
|
|
(*y)++;
|
|
|
|
}
|
|
|
|
else (*x)++;
|
|
|
|
|
|
|
|
pointer++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static s32 getLinesCount(Code* code)
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
char* text = code->src;
|
2017-09-26 08:59:34 +02:00
|
|
|
s32 count = 0;
|
|
|
|
|
|
|
|
while(*text)
|
|
|
|
if(*text++ == '\n')
|
|
|
|
count++;
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void removeInvalidChars(char* code)
|
|
|
|
{
|
2017-11-11 15:43:29 +01:00
|
|
|
// remove \r symbol
|
|
|
|
char* s; char* d;
|
|
|
|
for(s = d = code; (*d = *s); d += (*s++ != '\r'));
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void updateEditor(Code* code)
|
|
|
|
{
|
|
|
|
s32 column = 0;
|
|
|
|
s32 line = 0;
|
|
|
|
getCursorPosition(code, &column, &line);
|
|
|
|
|
|
|
|
if(column < code->scroll.x) code->scroll.x = column;
|
2017-10-13 18:25:10 +02:00
|
|
|
else if(column >= code->scroll.x + TEXT_BUFFER_WIDTH)
|
2017-09-26 08:59:34 +02:00
|
|
|
code->scroll.x = column - TEXT_BUFFER_WIDTH + 1;
|
|
|
|
|
|
|
|
if(line < code->scroll.y) code->scroll.y = line;
|
2017-10-13 18:25:10 +02:00
|
|
|
else if(line >= code->scroll.y + TEXT_BUFFER_HEIGHT)
|
2017-09-26 08:59:34 +02:00
|
|
|
code->scroll.y = line - TEXT_BUFFER_HEIGHT + 1;
|
|
|
|
|
|
|
|
code->cursor.delay = TEXT_CURSOR_DELAY;
|
|
|
|
|
|
|
|
// update status
|
|
|
|
{
|
|
|
|
memset(code->status, ' ', sizeof code->status - 1);
|
|
|
|
|
|
|
|
char status[STUDIO_TEXT_BUFFER_WIDTH];
|
|
|
|
s32 count = getLinesCount(code);
|
|
|
|
sprintf(status, "line %i/%i col %i", line + 1, count + 1, column + 1);
|
|
|
|
memcpy(code->status, status, strlen(status));
|
|
|
|
|
2017-12-14 15:08:04 +01:00
|
|
|
size_t codeLen = strlen(code->src);
|
2017-09-26 08:59:34 +02:00
|
|
|
sprintf(status, "%i/%i", (u32)codeLen, TIC_CODE_SIZE);
|
2017-10-13 18:25:10 +02:00
|
|
|
|
2017-12-14 15:08:04 +01:00
|
|
|
memset(code->src + codeLen, '\0', TIC_CODE_SIZE - codeLen);
|
2017-10-13 18:25:10 +02:00
|
|
|
memcpy(code->status + sizeof code->status - strlen(status) - 1, status, strlen(status));
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-20 20:46:08 +01:00
|
|
|
static inline bool islineend(char c) {return c == '\n' || c == '\0';}
|
2017-12-20 21:14:41 +01:00
|
|
|
static inline bool isalpha_(char c) {return isalpha(c) || c == '_';}
|
|
|
|
static inline bool isalnum_(char c) {return isalnum(c) || c == '_';}
|
2017-12-20 20:46:08 +01:00
|
|
|
|
2017-12-20 21:39:21 +01:00
|
|
|
typedef struct
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
2017-12-20 21:39:21 +01:00
|
|
|
const char* blockCommentStart;
|
|
|
|
const char* blockCommentEnd;
|
|
|
|
const char* singleCommentStart;
|
2017-12-20 21:20:11 +01:00
|
|
|
|
2017-12-20 21:39:21 +01:00
|
|
|
const char* const * keywords;
|
|
|
|
s32 keywordsCount;
|
|
|
|
} SyntaxConfig;
|
|
|
|
|
|
|
|
static const char* const LuaKeywords [] =
|
|
|
|
{
|
|
|
|
"and", "break", "do", "else", "elseif",
|
|
|
|
"end", "false", "for", "function", "goto", "if",
|
|
|
|
"in", "local", "nil", "not", "or", "repeat",
|
|
|
|
"return", "then", "true", "until", "while"
|
|
|
|
};
|
|
|
|
|
|
|
|
static const SyntaxConfig LuaSyntaxConfig =
|
|
|
|
{
|
|
|
|
.blockCommentStart = "--[[",
|
|
|
|
.blockCommentEnd = "]]",
|
|
|
|
.singleCommentStart = "--",
|
|
|
|
.keywords = LuaKeywords,
|
|
|
|
.keywordsCount = COUNT_OF(LuaKeywords),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char* const MoonKeywords [] =
|
|
|
|
{
|
|
|
|
"false", "true", "nil", "return",
|
|
|
|
"break", "continue", "for", "while",
|
|
|
|
"if", "else", "elseif", "unless", "switch",
|
|
|
|
"when", "and", "or", "in", "do",
|
|
|
|
"not", "super", "try", "catch",
|
|
|
|
"with", "export", "import", "then",
|
|
|
|
"from", "class", "extends", "new"
|
|
|
|
};
|
|
|
|
|
|
|
|
static const SyntaxConfig MoonSyntaxConfig =
|
|
|
|
{
|
|
|
|
.blockCommentStart = "--[[",
|
|
|
|
.blockCommentEnd = "]]",
|
|
|
|
.singleCommentStart = "--",
|
|
|
|
.keywords = MoonKeywords,
|
|
|
|
.keywordsCount = COUNT_OF(MoonKeywords),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char* const JsKeywords [] =
|
|
|
|
{
|
|
|
|
"break", "do", "instanceof", "typeof", "case", "else", "new",
|
|
|
|
"var", "catch", "finally", "return", "void", "continue", "for",
|
|
|
|
"switch", "while", "debugger", "function", "this", "with",
|
|
|
|
"default", "if", "throw", "delete", "in", "try", "const"
|
|
|
|
};
|
|
|
|
|
|
|
|
static const SyntaxConfig JsSyntaxConfig =
|
|
|
|
{
|
|
|
|
.blockCommentStart = "/*",
|
|
|
|
.blockCommentEnd = "*/",
|
|
|
|
.singleCommentStart = "//",
|
|
|
|
.keywords = JsKeywords,
|
|
|
|
.keywordsCount = COUNT_OF(JsKeywords),
|
|
|
|
};
|
|
|
|
|
|
|
|
static void parse(const char* start, u8* color, const SyntaxConfig* config)
|
|
|
|
{
|
2017-12-20 18:53:19 +01:00
|
|
|
const char* ptr = start;
|
2017-09-26 08:59:34 +02:00
|
|
|
|
2017-12-20 19:15:44 +01:00
|
|
|
const char* blockCommentStart = NULL;
|
2017-12-20 19:57:29 +01:00
|
|
|
const char* blockStringStart = NULL;
|
2017-12-20 20:06:43 +01:00
|
|
|
const char* singleCommentStart = NULL;
|
2017-12-20 21:14:41 +01:00
|
|
|
const char* wordStart = NULL;
|
2017-12-20 20:46:08 +01:00
|
|
|
const char* numberStart = NULL;
|
2017-09-26 08:59:34 +02:00
|
|
|
|
2017-12-20 18:53:19 +01:00
|
|
|
while(true)
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
2017-12-20 18:53:19 +01:00
|
|
|
char c = *ptr;
|
2017-09-26 08:59:34 +02:00
|
|
|
|
2017-12-20 19:15:44 +01:00
|
|
|
if(blockCommentStart)
|
|
|
|
{
|
2017-12-20 21:39:21 +01:00
|
|
|
const char* end = strstr(ptr, config->blockCommentEnd);
|
2017-12-20 19:15:44 +01:00
|
|
|
|
2017-12-20 21:39:21 +01:00
|
|
|
ptr = end ? end + strlen(config->blockCommentEnd) : blockCommentStart + strlen(blockCommentStart);
|
2017-12-20 19:23:59 +01:00
|
|
|
memset(color + (blockCommentStart - start), getConfig()->theme.code.comment, ptr - blockCommentStart);
|
2017-12-20 19:15:44 +01:00
|
|
|
blockCommentStart = NULL;
|
2017-12-20 20:59:45 +01:00
|
|
|
continue;
|
2017-12-20 19:15:44 +01:00
|
|
|
}
|
2017-12-20 19:57:29 +01:00
|
|
|
else if(blockStringStart)
|
|
|
|
{
|
|
|
|
const char* blockStart = blockStringStart+1;
|
|
|
|
|
|
|
|
while(true)
|
|
|
|
{
|
|
|
|
const char* pos = strchr(blockStart, *blockStringStart);
|
|
|
|
|
|
|
|
if(pos)
|
|
|
|
{
|
|
|
|
if(*(pos-1) == '\\' && *(pos-2) != '\\')
|
|
|
|
{
|
|
|
|
blockStart = pos + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ptr = pos + 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ptr = blockStringStart + strlen(blockStringStart);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(color + (blockStringStart - start), getConfig()->theme.code.string, ptr - blockStringStart);
|
|
|
|
blockStringStart = NULL;
|
2017-12-20 20:59:45 +01:00
|
|
|
continue;
|
2017-12-20 19:57:29 +01:00
|
|
|
}
|
2017-12-20 20:06:43 +01:00
|
|
|
else if(singleCommentStart)
|
|
|
|
{
|
2017-12-20 20:46:08 +01:00
|
|
|
while(!islineend(*ptr))ptr++;
|
2017-12-20 20:06:43 +01:00
|
|
|
|
|
|
|
memset(color + (singleCommentStart - start), getConfig()->theme.code.comment, ptr - singleCommentStart);
|
|
|
|
singleCommentStart = NULL;
|
2017-12-20 20:59:45 +01:00
|
|
|
continue;
|
2017-12-20 20:06:43 +01:00
|
|
|
}
|
2017-12-20 21:14:41 +01:00
|
|
|
else if(wordStart)
|
|
|
|
{
|
|
|
|
while(!islineend(*ptr) && isalnum_(*ptr)) ptr++;
|
|
|
|
|
|
|
|
bool keyword = false;
|
|
|
|
{
|
2017-12-20 21:39:21 +01:00
|
|
|
for(s32 i = 0; i < config->keywordsCount; i++)
|
|
|
|
if(memcmp(wordStart, config->keywords[i], strlen(config->keywords[i])) == 0)
|
2017-12-20 21:14:41 +01:00
|
|
|
{
|
|
|
|
memset(color + (wordStart - start), getConfig()->theme.code.keyword, ptr - wordStart);
|
|
|
|
keyword = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!keyword)
|
|
|
|
{
|
|
|
|
static const char* const Api[] = API_KEYWORDS;
|
|
|
|
|
|
|
|
for(s32 i = 0; i < COUNT_OF(Api); i++)
|
|
|
|
if(memcmp(wordStart, Api[i], strlen(Api[i])) == 0)
|
|
|
|
{
|
|
|
|
memset(color + (wordStart - start), getConfig()->theme.code.api, ptr - wordStart);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wordStart = NULL;
|
|
|
|
continue;
|
|
|
|
}
|
2017-12-20 20:46:08 +01:00
|
|
|
else if(numberStart)
|
|
|
|
{
|
|
|
|
while(!islineend(*ptr))
|
|
|
|
{
|
|
|
|
char c = *ptr;
|
|
|
|
|
2017-12-20 20:59:45 +01:00
|
|
|
if(isdigit(c)) ptr++;
|
|
|
|
else if(numberStart[0] == '0'
|
|
|
|
&& (numberStart[1] == 'x' || numberStart[1] == 'X')
|
|
|
|
&& isxdigit(numberStart[2]))
|
2017-12-20 20:46:08 +01:00
|
|
|
{
|
2017-12-20 20:59:45 +01:00
|
|
|
if((ptr - numberStart < 2) || (ptr - numberStart >= 2 && isxdigit(c))) ptr++;
|
2017-12-20 20:46:08 +01:00
|
|
|
else break;
|
|
|
|
}
|
2017-12-20 20:59:45 +01:00
|
|
|
else if(c == '.' || c == 'e' || c == 'E')
|
2017-12-20 20:46:08 +01:00
|
|
|
{
|
2017-12-20 20:59:45 +01:00
|
|
|
if(isdigit(ptr[1])) ptr++;
|
|
|
|
else break;
|
2017-12-20 20:46:08 +01:00
|
|
|
}
|
|
|
|
else break;
|
|
|
|
}
|
|
|
|
|
2017-12-20 20:59:45 +01:00
|
|
|
memset(color + (numberStart - start), getConfig()->theme.code.number, ptr - numberStart);
|
2017-12-20 20:46:08 +01:00
|
|
|
numberStart = NULL;
|
2017-12-20 20:59:45 +01:00
|
|
|
continue;
|
2017-12-20 20:46:08 +01:00
|
|
|
}
|
2017-12-20 19:15:44 +01:00
|
|
|
else
|
|
|
|
{
|
2017-12-20 21:39:21 +01:00
|
|
|
s32 blockCommentStartSize = strlen(config->blockCommentStart);
|
|
|
|
if(c == config->blockCommentStart[0] && memcmp(ptr, config->blockCommentStart, blockCommentStartSize) == 0)
|
2017-12-20 19:15:44 +01:00
|
|
|
{
|
|
|
|
blockCommentStart = ptr;
|
2017-12-20 19:23:59 +01:00
|
|
|
ptr += blockCommentStartSize;
|
2017-12-20 19:15:44 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-12-20 19:57:29 +01:00
|
|
|
if(c == '"' || c == '\'')
|
|
|
|
{
|
|
|
|
blockStringStart = ptr;
|
2017-12-20 21:14:41 +01:00
|
|
|
ptr++;
|
2017-12-20 19:57:29 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-12-20 21:39:21 +01:00
|
|
|
s32 singleCommentStartSize = strlen(config->singleCommentStart);
|
2017-12-20 20:06:43 +01:00
|
|
|
|
2017-12-20 21:39:21 +01:00
|
|
|
if(c == config->singleCommentStart[0] && memcmp(ptr, config->singleCommentStart, singleCommentStartSize) == 0)
|
2017-12-20 20:06:43 +01:00
|
|
|
{
|
|
|
|
singleCommentStart = ptr;
|
|
|
|
ptr += singleCommentStartSize;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-12-20 21:14:41 +01:00
|
|
|
if(isalpha_(c))
|
2017-12-20 20:46:08 +01:00
|
|
|
{
|
2017-12-20 21:14:41 +01:00
|
|
|
wordStart = ptr;
|
|
|
|
ptr++;
|
2017-12-20 20:46:08 +01:00
|
|
|
continue;
|
|
|
|
}
|
2017-12-20 20:59:45 +01:00
|
|
|
else
|
|
|
|
{
|
2017-12-20 21:14:41 +01:00
|
|
|
if(isdigit(c) || (c == '.' && isdigit(ptr[1])))
|
|
|
|
{
|
|
|
|
numberStart = ptr;
|
|
|
|
ptr++;
|
|
|
|
continue;
|
|
|
|
}
|
2017-12-20 21:20:11 +01:00
|
|
|
else if(ispunct(c))
|
|
|
|
color[ptr - start] = getConfig()->theme.code.sign;
|
2017-12-20 20:59:45 +01:00
|
|
|
}
|
2017-12-20 20:06:43 +01:00
|
|
|
}
|
2017-12-20 19:57:29 +01:00
|
|
|
}
|
2017-12-20 19:15:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-20 18:53:19 +01:00
|
|
|
if(!c) break;
|
|
|
|
|
|
|
|
ptr++;
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
2017-12-20 21:39:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void parseSyntaxColor(Code* code)
|
|
|
|
{
|
|
|
|
memset(code->colorBuffer, getConfig()->theme.code.var, sizeof(code->colorBuffer));
|
2017-12-20 18:53:19 +01:00
|
|
|
|
2017-12-20 21:39:21 +01:00
|
|
|
switch(code->tic->api.get_script(code->tic))
|
|
|
|
{
|
|
|
|
case tic_script_moon: parse(code->src, code->colorBuffer, &MoonSyntaxConfig); break;
|
|
|
|
case tic_script_lua: parse(code->src, code->colorBuffer, &LuaSyntaxConfig); break;
|
|
|
|
case tic_script_js: parse(code->src, code->colorBuffer, &JsSyntaxConfig); break;
|
|
|
|
}
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static char* getLineByPos(Code* code, char* pos)
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
char* text = code->src;
|
2017-09-26 08:59:34 +02:00
|
|
|
char* line = text;
|
|
|
|
|
|
|
|
while(text < pos)
|
|
|
|
if(*text++ == '\n')
|
|
|
|
line = text;
|
|
|
|
|
|
|
|
return line;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char* getLine(Code* code)
|
|
|
|
{
|
|
|
|
return getLineByPos(code, code->cursor.position);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char* getPrevLine(Code* code)
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
char* text = code->src;
|
2017-09-26 08:59:34 +02:00
|
|
|
char* pos = code->cursor.position;
|
|
|
|
char* prevLine = text;
|
|
|
|
char* line = text;
|
|
|
|
|
|
|
|
while(text < pos)
|
|
|
|
if(*text++ == '\n')
|
|
|
|
{
|
|
|
|
prevLine = line;
|
|
|
|
line = text;
|
|
|
|
}
|
|
|
|
|
|
|
|
return prevLine;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char* getNextLineByPos(Code* code, char* pos)
|
|
|
|
{
|
|
|
|
while(*pos && *pos++ != '\n');
|
|
|
|
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char* getNextLine(Code* code)
|
|
|
|
{
|
|
|
|
return getNextLineByPos(code, code->cursor.position);
|
|
|
|
}
|
|
|
|
|
|
|
|
static s32 getLineSize(const char* line)
|
|
|
|
{
|
|
|
|
s32 size = 0;
|
|
|
|
while(*line != '\n' && *line++) size++;
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void updateColumn(Code* code)
|
|
|
|
{
|
|
|
|
code->cursor.column = code->cursor.position - getLine(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void updateCursorPosition(Code* code, char* position)
|
|
|
|
{
|
|
|
|
code->cursor.position = position;
|
|
|
|
updateColumn(code);
|
|
|
|
updateEditor(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void setCursorPosition(Code* code, s32 cx, s32 cy)
|
|
|
|
{
|
|
|
|
s32 x = 0;
|
|
|
|
s32 y = 0;
|
2017-12-14 15:08:04 +01:00
|
|
|
char* pointer = code->src;
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
while(*pointer)
|
|
|
|
{
|
|
|
|
if(y == cy && x == cx)
|
|
|
|
{
|
|
|
|
updateCursorPosition(code, pointer);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(*pointer == '\n')
|
|
|
|
{
|
|
|
|
if(y == cy && cx > x)
|
|
|
|
{
|
|
|
|
updateCursorPosition(code, pointer);
|
2017-10-13 18:25:10 +02:00
|
|
|
return;
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
x = 0;
|
|
|
|
y++;
|
|
|
|
}
|
|
|
|
else x++;
|
|
|
|
|
|
|
|
pointer++;
|
|
|
|
}
|
|
|
|
|
|
|
|
updateCursorPosition(code, pointer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void upLine(Code* code)
|
|
|
|
{
|
|
|
|
char* prevLine = getPrevLine(code);
|
|
|
|
size_t prevSize = getLineSize(prevLine);
|
|
|
|
size_t size = code->cursor.column;
|
|
|
|
|
|
|
|
code->cursor.position = prevLine + (prevSize > size ? size : prevSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void downLine(Code* code)
|
|
|
|
{
|
|
|
|
char* nextLine = getNextLine(code);
|
|
|
|
size_t nextSize = getLineSize(nextLine);
|
|
|
|
size_t size = code->cursor.column;
|
|
|
|
|
|
|
|
code->cursor.position = nextLine + (nextSize > size ? size : nextSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void leftColumn(Code* code)
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
char* start = code->src;
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
if(code->cursor.position > start)
|
|
|
|
{
|
|
|
|
code->cursor.position--;
|
|
|
|
updateColumn(code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void rightColumn(Code* code)
|
|
|
|
{
|
|
|
|
if(*code->cursor.position)
|
2017-10-13 18:25:10 +02:00
|
|
|
{
|
2017-09-26 08:59:34 +02:00
|
|
|
code->cursor.position++;
|
|
|
|
updateColumn(code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void leftWord(Code* code)
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
const char* start = code->src;
|
2017-09-26 08:59:34 +02:00
|
|
|
char* pos = code->cursor.position-1;
|
|
|
|
|
|
|
|
if(pos > start)
|
|
|
|
{
|
2017-12-20 21:39:21 +01:00
|
|
|
if(isalnum_(*pos)) while(pos > start && isalnum_(*(pos-1))) pos--;
|
|
|
|
else while(pos > start && !isalnum_(*(pos-1))) pos--;
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
code->cursor.position = pos;
|
|
|
|
|
|
|
|
updateColumn(code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void rightWord(Code* code)
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
const char* end = code->src + strlen(code->src);
|
2017-09-26 08:59:34 +02:00
|
|
|
char* pos = code->cursor.position;
|
|
|
|
|
|
|
|
if(pos < end)
|
|
|
|
{
|
2017-12-20 21:39:21 +01:00
|
|
|
if(isalnum_(*pos)) while(pos < end && isalnum_(*pos)) pos++;
|
|
|
|
else while(pos < end && !isalnum_(*pos)) pos++;
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
code->cursor.position = pos;
|
|
|
|
updateColumn(code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void goHome(Code* code)
|
|
|
|
{
|
|
|
|
code->cursor.position = getLine(code);
|
|
|
|
|
|
|
|
updateColumn(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void goEnd(Code* code)
|
|
|
|
{
|
|
|
|
char* line = getLine(code);
|
|
|
|
code->cursor.position = line + getLineSize(line);
|
|
|
|
|
|
|
|
updateColumn(code);
|
|
|
|
}
|
|
|
|
|
2017-10-13 18:25:10 +02:00
|
|
|
static void goCodeHome(Code *code)
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
code->cursor.position = code->src;
|
2017-10-13 18:25:10 +02:00
|
|
|
|
|
|
|
updateColumn(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void goCodeEnd(Code *code)
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
code->cursor.position = code->src + strlen(code->src);
|
2017-10-13 18:25:10 +02:00
|
|
|
|
|
|
|
updateColumn(code);
|
|
|
|
}
|
|
|
|
|
2017-09-26 08:59:34 +02:00
|
|
|
static void pageUp(Code* code)
|
|
|
|
{
|
|
|
|
s32 column = 0;
|
|
|
|
s32 line = 0;
|
|
|
|
getCursorPosition(code, &column, &line);
|
|
|
|
setCursorPosition(code, column, line > TEXT_BUFFER_HEIGHT ? line - TEXT_BUFFER_HEIGHT : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pageDown(Code* code)
|
|
|
|
{
|
|
|
|
s32 column = 0;
|
|
|
|
s32 line = 0;
|
|
|
|
getCursorPosition(code, &column, &line);
|
|
|
|
s32 lines = getLinesCount(code);
|
2017-11-11 15:43:29 +01:00
|
|
|
setCursorPosition(code, column, line < lines - TEXT_BUFFER_HEIGHT ? line + TEXT_BUFFER_HEIGHT : lines);
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool replaceSelection(Code* code)
|
|
|
|
{
|
|
|
|
char* pos = code->cursor.position;
|
|
|
|
char* sel = code->cursor.selection;
|
|
|
|
|
|
|
|
if(sel && sel != pos)
|
|
|
|
{
|
|
|
|
char* start = SDL_min(sel, pos);
|
|
|
|
char* end = SDL_max(sel, pos);
|
|
|
|
|
|
|
|
memmove(start, end, strlen(end) + 1);
|
|
|
|
|
|
|
|
code->cursor.position = start;
|
|
|
|
code->cursor.selection = NULL;
|
|
|
|
|
|
|
|
history(code);
|
|
|
|
|
|
|
|
parseSyntaxColor(code);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void deleteChar(Code* code)
|
|
|
|
{
|
|
|
|
if(!replaceSelection(code))
|
|
|
|
{
|
|
|
|
char* pos = code->cursor.position;
|
|
|
|
memmove(pos, pos + 1, strlen(pos));
|
|
|
|
history(code);
|
|
|
|
parseSyntaxColor(code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void backspaceChar(Code* code)
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
if(!replaceSelection(code) && code->cursor.position > code->src)
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
|
|
|
char* pos = --code->cursor.position;
|
|
|
|
memmove(pos, pos + 1, strlen(pos));
|
|
|
|
history(code);
|
|
|
|
parseSyntaxColor(code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void inputSymbolBase(Code* code, char sym)
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
if (strlen(code->src) >= sizeof(tic_code))
|
2017-09-26 08:59:34 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
char* pos = code->cursor.position;
|
|
|
|
|
|
|
|
memmove(pos + 1, pos, strlen(pos)+1);
|
|
|
|
|
|
|
|
*code->cursor.position++ = sym;
|
|
|
|
|
|
|
|
history(code);
|
|
|
|
|
|
|
|
updateColumn(code);
|
|
|
|
|
|
|
|
parseSyntaxColor(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void inputSymbol(Code* code, char sym)
|
|
|
|
{
|
|
|
|
replaceSelection(code);
|
|
|
|
|
|
|
|
inputSymbolBase(code, sym);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void newLine(Code* code)
|
|
|
|
{
|
|
|
|
if(!replaceSelection(code))
|
|
|
|
{
|
|
|
|
char* ptr = getLine(code);
|
|
|
|
size_t size = 0;
|
|
|
|
|
|
|
|
while(*ptr == '\t' || *ptr == ' ') ptr++, size++;
|
|
|
|
|
|
|
|
if(ptr > code->cursor.position)
|
|
|
|
size -= ptr - code->cursor.position;
|
|
|
|
|
|
|
|
inputSymbol(code, '\n');
|
|
|
|
|
|
|
|
for(size_t i = 0; i < size; i++)
|
|
|
|
inputSymbol(code, '\t');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void selectAll(Code* code)
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
code->cursor.selection = code->src;
|
2017-09-26 08:59:34 +02:00
|
|
|
code->cursor.position = code->cursor.selection + strlen(code->cursor.selection);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void copyToClipboard(Code* code)
|
|
|
|
{
|
|
|
|
char* pos = code->cursor.position;
|
|
|
|
char* sel = code->cursor.selection;
|
|
|
|
|
|
|
|
char* start = NULL;
|
|
|
|
size_t size = 0;
|
|
|
|
|
|
|
|
if(sel && sel != pos)
|
|
|
|
{
|
|
|
|
start = SDL_min(sel, pos);
|
|
|
|
size = SDL_max(sel, pos) - start;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
start = getLine(code);
|
|
|
|
size = getNextLine(code) - start;
|
|
|
|
}
|
|
|
|
|
|
|
|
char* clipboard = (char*)SDL_malloc(size+1);
|
|
|
|
|
|
|
|
if(clipboard)
|
|
|
|
{
|
|
|
|
memcpy(clipboard, start, size);
|
|
|
|
clipboard[size] = '\0';
|
|
|
|
SDL_SetClipboardText(clipboard);
|
|
|
|
SDL_free(clipboard);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cutToClipboard(Code* code)
|
|
|
|
{
|
|
|
|
copyToClipboard(code);
|
|
|
|
replaceSelection(code);
|
|
|
|
history(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void copyFromClipboard(Code* code)
|
|
|
|
{
|
|
|
|
if(SDL_HasClipboardText())
|
|
|
|
{
|
|
|
|
char* clipboard = SDL_GetClipboardText();
|
|
|
|
|
|
|
|
if(clipboard)
|
|
|
|
{
|
|
|
|
removeInvalidChars(clipboard);
|
|
|
|
size_t size = strlen(clipboard);
|
|
|
|
|
|
|
|
if(size)
|
|
|
|
{
|
|
|
|
replaceSelection(code);
|
|
|
|
|
|
|
|
char* pos = code->cursor.position;
|
|
|
|
|
|
|
|
// cut clipboard code if overall code > max code size
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
size_t codeSize = strlen(code->src);
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
if (codeSize + size > sizeof(tic_code))
|
|
|
|
{
|
|
|
|
size = sizeof(tic_code) - codeSize;
|
|
|
|
clipboard[size] = '\0';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
memmove(pos + size, pos, strlen(pos) + 1);
|
|
|
|
memcpy(pos, clipboard, size);
|
|
|
|
|
|
|
|
code->cursor.position += size;
|
|
|
|
|
|
|
|
history(code);
|
|
|
|
|
|
|
|
parseSyntaxColor(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_free(clipboard);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update(Code* code)
|
|
|
|
{
|
|
|
|
updateEditor(code);
|
|
|
|
parseSyntaxColor(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void undo(Code* code)
|
|
|
|
{
|
|
|
|
history_undo(code->history);
|
|
|
|
history_undo(code->cursorHistory);
|
|
|
|
|
|
|
|
update(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void redo(Code* code)
|
|
|
|
{
|
|
|
|
history_redo(code->history);
|
|
|
|
history_redo(code->cursorHistory);
|
|
|
|
|
|
|
|
update(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void doTab(Code* code, bool shift)
|
|
|
|
{
|
|
|
|
char* pos = code->cursor.position;
|
|
|
|
char* sel = code->cursor.selection;
|
|
|
|
|
|
|
|
if(sel && sel != pos)
|
|
|
|
{
|
|
|
|
char* start = SDL_min(sel, pos);
|
|
|
|
char* end = SDL_max(sel, pos);
|
|
|
|
bool changed = false;
|
|
|
|
|
|
|
|
char* line = start = getLineByPos(code, start);
|
|
|
|
|
|
|
|
while(line && line < end)
|
|
|
|
{
|
|
|
|
if(shift)
|
|
|
|
{
|
|
|
|
if(*line == '\t' || *line == ' ')
|
|
|
|
{
|
|
|
|
memmove(line, line + 1, strlen(line)+1);
|
|
|
|
end--;
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
memmove(line + 1, line, strlen(line)+1);
|
|
|
|
*line = '\t';
|
|
|
|
end++;
|
|
|
|
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
line = getNextLineByPos(code, line);
|
|
|
|
}
|
|
|
|
|
|
|
|
code->cursor.position = start;
|
|
|
|
code->cursor.selection = end;
|
|
|
|
|
|
|
|
if(changed)
|
|
|
|
history(code);
|
|
|
|
|
|
|
|
parseSyntaxColor(code);
|
2017-10-13 18:25:10 +02:00
|
|
|
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
else inputSymbolBase(code, '\t');
|
|
|
|
}
|
|
|
|
|
|
|
|
static void setFindMode(Code* code)
|
|
|
|
{
|
|
|
|
if(code->cursor.selection)
|
|
|
|
{
|
|
|
|
const char* end = SDL_max(code->cursor.position, code->cursor.selection);
|
|
|
|
const char* start = SDL_min(code->cursor.position, code->cursor.selection);
|
|
|
|
size_t len = end - start;
|
2017-10-13 18:25:10 +02:00
|
|
|
|
2017-09-26 08:59:34 +02:00
|
|
|
if(len > 0 && len < sizeof code->popup.text - 1)
|
|
|
|
{
|
|
|
|
memset(code->popup.text, 0, sizeof code->popup.text);
|
|
|
|
memcpy(code->popup.text, start, len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void setGotoMode(Code* code)
|
|
|
|
{
|
|
|
|
code->jump.line = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int funcCompare(const void* a, const void* b)
|
|
|
|
{
|
|
|
|
const OutlineItem* item1 = (const OutlineItem*)a;
|
|
|
|
const OutlineItem* item2 = (const OutlineItem*)b;
|
|
|
|
|
|
|
|
if(item1->pos == NULL) return 1;
|
|
|
|
if(item2->pos == NULL) return -1;
|
|
|
|
|
|
|
|
return strcmp(item1->name, item2->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char* getFuncName(const char* start, char* buffer)
|
|
|
|
{
|
|
|
|
const char* ptr = start;
|
|
|
|
while(*ptr)
|
|
|
|
{
|
|
|
|
char sym = *ptr;
|
|
|
|
|
2017-12-20 21:39:21 +01:00
|
|
|
if(isalpha_(sym) || isdigit(sym) || sym == ':'){}
|
2017-09-26 08:59:34 +02:00
|
|
|
else if(sym == '(') break;
|
|
|
|
else return NULL;
|
|
|
|
|
|
|
|
ptr++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(ptr)
|
|
|
|
{
|
|
|
|
size_t len = ptr - start;
|
|
|
|
|
|
|
|
if(len < STUDIO_TEXT_BUFFER_WIDTH)
|
|
|
|
{
|
|
|
|
memcpy(buffer, start, len);
|
|
|
|
buffer[len] = '\0';
|
|
|
|
|
2017-10-13 18:25:10 +02:00
|
|
|
return buffer;
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void normalizeScroll(Code* code)
|
|
|
|
{
|
|
|
|
if(code->scroll.x < 0) code->scroll.x = 0;
|
|
|
|
if(code->scroll.y < 0) code->scroll.y = 0;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
s32 lines = getLinesCount(code);
|
|
|
|
if(code->scroll.y > lines) code->scroll.y = lines;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void centerScroll(Code* code)
|
|
|
|
{
|
|
|
|
s32 col, line;
|
|
|
|
getCursorPosition(code, &col, &line);
|
|
|
|
code->scroll.x = col - TEXT_BUFFER_WIDTH / 2;
|
|
|
|
code->scroll.y = line - TEXT_BUFFER_HEIGHT / 2;
|
|
|
|
|
|
|
|
normalizeScroll(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void updateOutlineCode(Code* code)
|
|
|
|
{
|
|
|
|
OutlineItem* item = code->outline.items + code->outline.index;
|
|
|
|
|
|
|
|
if(item->pos)
|
|
|
|
{
|
|
|
|
code->cursor.position = item->pos;
|
|
|
|
code->cursor.selection = item->pos + strlen(item->name);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
code->cursor.position = code->src;
|
2017-09-26 08:59:34 +02:00
|
|
|
code->cursor.selection = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
centerScroll(code);
|
|
|
|
updateEditor(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void setMoonscriptOutlineMode(Code* code)
|
|
|
|
{
|
|
|
|
OutlineItem* out = code->outline.items;
|
|
|
|
OutlineItem* end = out + OUTLINE_SIZE;
|
|
|
|
|
2017-12-14 15:08:04 +01:00
|
|
|
char* ptr = code->src;
|
2017-09-26 08:59:34 +02:00
|
|
|
static const char FuncString[] = "=->";
|
|
|
|
|
|
|
|
char buffer[STUDIO_TEXT_BUFFER_WIDTH];
|
|
|
|
char filter[STUDIO_TEXT_BUFFER_WIDTH];
|
|
|
|
strcpy(filter, code->popup.text);
|
|
|
|
SDL_strlwr(filter);
|
2017-10-13 18:25:10 +02:00
|
|
|
|
2017-09-26 08:59:34 +02:00
|
|
|
while(ptr)
|
|
|
|
{
|
|
|
|
ptr = strstr(ptr, FuncString);
|
|
|
|
|
2017-12-14 15:08:04 +01:00
|
|
|
if(ptr && ptr > code->src)
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
|
|
|
char* endPtr = ptr;
|
|
|
|
ptr += sizeof FuncString - 1;
|
|
|
|
|
2017-12-20 21:39:21 +01:00
|
|
|
while(endPtr >= code->src && !isalpha_(*endPtr) && !isdigit(*endPtr)) endPtr--;
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
char* start = endPtr;
|
|
|
|
|
2017-12-20 21:39:21 +01:00
|
|
|
for (const char* val = start-1; val >= code->src && (isalpha_(*val) || isdigit(*val)); val--, start--);
|
2017-09-26 08:59:34 +02:00
|
|
|
|
2017-12-14 15:08:04 +01:00
|
|
|
if(start >= code->src)
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
|
|
|
memset(buffer, 0, sizeof buffer);
|
|
|
|
memcpy(buffer, start, endPtr - start + 1);
|
|
|
|
|
|
|
|
strcpy(out->name, buffer);
|
|
|
|
|
|
|
|
if(*filter)
|
|
|
|
{
|
|
|
|
SDL_strlwr(buffer);
|
|
|
|
if(strstr(buffer, filter))
|
|
|
|
{
|
|
|
|
out->pos = start;
|
|
|
|
out++;
|
|
|
|
}
|
|
|
|
}
|
2017-10-13 18:25:10 +02:00
|
|
|
else
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
|
|
|
out->pos = start;
|
|
|
|
out++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else break;
|
|
|
|
|
|
|
|
if(out >= end) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void setLuaOutlineMode(Code* code)
|
|
|
|
{
|
|
|
|
OutlineItem* out = code->outline.items;
|
|
|
|
OutlineItem* end = out + OUTLINE_SIZE;
|
|
|
|
|
2017-12-14 15:08:04 +01:00
|
|
|
char* ptr = code->src;
|
2017-09-26 08:59:34 +02:00
|
|
|
static const char FuncString[] = "function ";
|
|
|
|
|
|
|
|
char buffer[STUDIO_TEXT_BUFFER_WIDTH];
|
|
|
|
char filter[STUDIO_TEXT_BUFFER_WIDTH];
|
|
|
|
strcpy(filter, code->popup.text);
|
|
|
|
SDL_strlwr(filter);
|
|
|
|
|
|
|
|
while(ptr)
|
|
|
|
{
|
|
|
|
ptr = strstr(ptr, FuncString);
|
|
|
|
|
|
|
|
if(ptr)
|
|
|
|
{
|
|
|
|
ptr += sizeof FuncString - 1;
|
|
|
|
|
|
|
|
if(getFuncName(ptr, buffer))
|
|
|
|
{
|
|
|
|
strcpy(out->name, buffer);
|
|
|
|
|
|
|
|
if(*filter)
|
|
|
|
{
|
|
|
|
SDL_strlwr(buffer);
|
|
|
|
if(strstr(buffer, filter))
|
|
|
|
{
|
|
|
|
out->pos = ptr;
|
|
|
|
out++;
|
|
|
|
}
|
|
|
|
}
|
2017-10-13 18:25:10 +02:00
|
|
|
else
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
|
|
|
out->pos = ptr;
|
|
|
|
out++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(out >= end) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void setOutlineMode(Code* code)
|
|
|
|
{
|
|
|
|
code->outline.index = 0;
|
|
|
|
memset(code->outline.items, 0, OUTLINE_ITEMS_SIZE);
|
|
|
|
|
|
|
|
code->tic->api.get_script(code->tic) == tic_script_moon
|
2017-10-13 18:25:10 +02:00
|
|
|
? setMoonscriptOutlineMode(code)
|
2017-09-26 08:59:34 +02:00
|
|
|
: setLuaOutlineMode(code);
|
|
|
|
|
|
|
|
qsort(code->outline.items, OUTLINE_SIZE, sizeof(OutlineItem), funcCompare);
|
|
|
|
updateOutlineCode(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void setCodeMode(Code* code, s32 mode)
|
|
|
|
{
|
|
|
|
if(code->mode != mode)
|
|
|
|
{
|
|
|
|
strcpy(code->popup.text, "");
|
|
|
|
|
|
|
|
code->popup.prevPos = code->cursor.position;
|
|
|
|
code->popup.prevSel = code->cursor.selection;
|
|
|
|
|
|
|
|
switch(mode)
|
|
|
|
{
|
|
|
|
case TEXT_FIND_MODE: setFindMode(code); break;
|
|
|
|
case TEXT_GOTO_MODE: setGotoMode(code); break;
|
|
|
|
case TEXT_OUTLINE_MODE: setOutlineMode(code); break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
|
|
|
code->mode = mode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void commentLine(Code* code)
|
|
|
|
{
|
|
|
|
static char Comment[] = "-- ";
|
|
|
|
enum {Size = sizeof Comment-1};
|
|
|
|
|
|
|
|
strcpy(Comment, code->tic->api.get_script(code->tic) == tic_script_js ? "// " : "-- ");
|
|
|
|
|
|
|
|
char* line = getLine(code);
|
|
|
|
|
|
|
|
const char* end = line + getLineSize(line);
|
|
|
|
|
|
|
|
while((*line == ' ' || *line == '\t') && line < end) line++;
|
|
|
|
|
|
|
|
if(memcmp(line, Comment, Size))
|
|
|
|
{
|
2017-12-14 15:08:04 +01:00
|
|
|
if (strlen(code->src) + Size >= sizeof(tic_code))
|
2017-09-26 08:59:34 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
memmove(line + Size, line, strlen(line)+1);
|
|
|
|
memcpy(line, Comment, Size);
|
|
|
|
|
|
|
|
if(code->cursor.position > line)
|
|
|
|
code->cursor.position += Size;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
memmove(line, line + Size, strlen(line + Size)+1);
|
|
|
|
|
|
|
|
if(code->cursor.position > line + Size)
|
|
|
|
code->cursor.position -= Size;
|
|
|
|
}
|
|
|
|
|
|
|
|
code->cursor.selection = NULL;
|
|
|
|
|
|
|
|
history(code);
|
|
|
|
|
|
|
|
parseSyntaxColor(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void processKeydown(Code* code, SDL_Keycode keycode)
|
|
|
|
{
|
|
|
|
switch(keycode)
|
|
|
|
{
|
|
|
|
case SDLK_LCTRL:
|
|
|
|
case SDLK_RCTRL:
|
|
|
|
case SDLK_LSHIFT:
|
|
|
|
case SDLK_RSHIFT:
|
|
|
|
case SDLK_LALT:
|
|
|
|
case SDLK_RALT:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(getClipboardEvent(keycode))
|
|
|
|
{
|
|
|
|
case TIC_CLIPBOARD_CUT: cutToClipboard(code); break;
|
|
|
|
case TIC_CLIPBOARD_COPY: copyToClipboard(code); break;
|
|
|
|
case TIC_CLIPBOARD_PASTE: copyFromClipboard(code); break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_Keymod keymod = SDL_GetModState();
|
|
|
|
|
|
|
|
switch(keycode)
|
|
|
|
{
|
|
|
|
case SDLK_UP:
|
|
|
|
case SDLK_DOWN:
|
|
|
|
case SDLK_LEFT:
|
|
|
|
case SDLK_RIGHT:
|
|
|
|
case SDLK_HOME:
|
|
|
|
case SDLK_END:
|
|
|
|
case SDLK_PAGEUP:
|
|
|
|
case SDLK_PAGEDOWN:
|
|
|
|
|
|
|
|
if(!(keymod & KMOD_SHIFT)) code->cursor.selection = NULL;
|
|
|
|
else if(code->cursor.selection == NULL) code->cursor.selection = code->cursor.position;
|
|
|
|
}
|
2017-10-13 18:25:10 +02:00
|
|
|
|
2017-09-26 08:59:34 +02:00
|
|
|
if(keymod & TIC_MOD_CTRL)
|
|
|
|
{
|
|
|
|
if(keymod & KMOD_CTRL)
|
|
|
|
{
|
|
|
|
switch(keycode)
|
|
|
|
{
|
|
|
|
case SDLK_LEFT: leftWord(code); break;
|
|
|
|
case SDLK_RIGHT: rightWord(code); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(keymod & KMOD_GUI)
|
|
|
|
{
|
|
|
|
switch(keycode)
|
|
|
|
{
|
|
|
|
case SDLK_LEFT: goHome(code); break;
|
|
|
|
case SDLK_RIGHT: goEnd(code); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(keycode)
|
|
|
|
{
|
|
|
|
case SDLK_a: selectAll(code); break;
|
|
|
|
case SDLK_z: undo(code); break;
|
|
|
|
case SDLK_y: redo(code); break;
|
|
|
|
case SDLK_f: setCodeMode(code, TEXT_FIND_MODE); break;
|
|
|
|
case SDLK_g: setCodeMode(code, TEXT_GOTO_MODE); break;
|
|
|
|
case SDLK_o: setCodeMode(code, TEXT_OUTLINE_MODE); break;
|
|
|
|
case SDLK_SLASH: commentLine(code); break;
|
2017-10-13 18:25:10 +02:00
|
|
|
case SDLK_HOME: goCodeHome(code); break;
|
|
|
|
case SDLK_END: goCodeEnd(code); break;
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(keymod & KMOD_ALT)
|
|
|
|
{
|
|
|
|
switch(keycode)
|
|
|
|
{
|
|
|
|
case SDLK_LEFT: leftWord(code); break;
|
|
|
|
case SDLK_RIGHT: rightWord(code); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
switch(keycode)
|
|
|
|
{
|
|
|
|
case SDLK_UP: upLine(code); break;
|
|
|
|
case SDLK_DOWN: downLine(code); break;
|
|
|
|
case SDLK_LEFT: leftColumn(code); break;
|
|
|
|
case SDLK_RIGHT: rightColumn(code); break;
|
|
|
|
case SDLK_HOME: goHome(code); break;
|
|
|
|
case SDLK_END: goEnd(code); break;
|
|
|
|
case SDLK_PAGEUP: pageUp(code); break;
|
|
|
|
case SDLK_PAGEDOWN: pageDown(code); break;
|
|
|
|
case SDLK_DELETE: deleteChar(code); break;
|
|
|
|
case SDLK_BACKSPACE: backspaceChar(code); break;
|
|
|
|
case SDLK_RETURN: newLine(code); break;
|
|
|
|
case SDLK_TAB: doTab(code, keymod & KMOD_SHIFT); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateEditor(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void processGestures(Code* code)
|
|
|
|
{
|
|
|
|
SDL_Point point = {0, 0};
|
|
|
|
|
|
|
|
if(getGesturePos(&point))
|
|
|
|
{
|
|
|
|
if(code->scroll.gesture)
|
|
|
|
{
|
|
|
|
code->scroll.x = (code->scroll.start.x - point.x) / STUDIO_TEXT_WIDTH;
|
|
|
|
code->scroll.y = (code->scroll.start.y - point.y) / STUDIO_TEXT_HEIGHT;
|
|
|
|
|
|
|
|
normalizeScroll(code);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
code->scroll.start.x = point.x + code->scroll.x * STUDIO_TEXT_WIDTH;
|
|
|
|
code->scroll.start.y = point.y + code->scroll.y * STUDIO_TEXT_HEIGHT;
|
|
|
|
code->scroll.gesture = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else code->scroll.gesture = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void processMouse(Code* code)
|
|
|
|
{
|
|
|
|
if(checkMousePos(&code->rect))
|
|
|
|
{
|
|
|
|
setCursor(SDL_SYSTEM_CURSOR_IBEAM);
|
|
|
|
|
|
|
|
if(code->scroll.active)
|
|
|
|
{
|
|
|
|
if(checkMouseDown(&code->rect, SDL_BUTTON_RIGHT))
|
|
|
|
{
|
|
|
|
code->scroll.x = (code->scroll.start.x - getMouseX()) / STUDIO_TEXT_WIDTH;
|
|
|
|
code->scroll.y = (code->scroll.start.y - getMouseY()) / STUDIO_TEXT_HEIGHT;
|
|
|
|
|
|
|
|
normalizeScroll(code);
|
|
|
|
}
|
|
|
|
else code->scroll.active = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(checkMouseDown(&code->rect, SDL_BUTTON_LEFT))
|
|
|
|
{
|
|
|
|
s32 mx = getMouseX();
|
|
|
|
s32 my = getMouseY();
|
|
|
|
|
|
|
|
s32 x = (mx - code->rect.x) / STUDIO_TEXT_WIDTH;
|
|
|
|
s32 y = (my - code->rect.y) / STUDIO_TEXT_HEIGHT;
|
|
|
|
|
|
|
|
char* position = code->cursor.position;
|
|
|
|
setCursorPosition(code, x + code->scroll.x, y + code->scroll.y);
|
|
|
|
|
|
|
|
if(SDL_GetModState() & KMOD_SHIFT)
|
|
|
|
{
|
|
|
|
code->cursor.selection = code->cursor.position;
|
|
|
|
code->cursor.position = position;
|
|
|
|
}
|
|
|
|
else if(!code->cursor.mouseDownPosition)
|
2017-10-13 18:25:10 +02:00
|
|
|
{
|
2017-09-26 08:59:34 +02:00
|
|
|
code->cursor.selection = code->cursor.position;
|
|
|
|
code->cursor.mouseDownPosition = code->cursor.position;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(code->cursor.mouseDownPosition == code->cursor.position)
|
|
|
|
code->cursor.selection = NULL;
|
|
|
|
|
|
|
|
code->cursor.mouseDownPosition = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(checkMouseDown(&code->rect, SDL_BUTTON_RIGHT))
|
|
|
|
{
|
|
|
|
code->scroll.active = true;
|
|
|
|
|
|
|
|
code->scroll.start.x = getMouseX() + code->scroll.x * STUDIO_TEXT_WIDTH;
|
|
|
|
code->scroll.start.y = getMouseY() + code->scroll.y * STUDIO_TEXT_HEIGHT;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void textEditTick(Code* code)
|
|
|
|
{
|
|
|
|
SDL_Event* event = NULL;
|
|
|
|
while ((event = pollEvent()))
|
|
|
|
{
|
|
|
|
switch(event->type)
|
|
|
|
{
|
|
|
|
case SDL_MOUSEWHEEL:
|
2017-10-13 18:25:10 +02:00
|
|
|
{
|
2017-09-26 08:59:34 +02:00
|
|
|
enum{Scroll = 3};
|
|
|
|
s32 delta = event->wheel.y > 0 ? -Scroll : Scroll;
|
|
|
|
|
|
|
|
code->scroll.y += delta;
|
|
|
|
|
|
|
|
normalizeScroll(code);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SDL_KEYDOWN:
|
|
|
|
processKeydown(code, event->key.keysym.sym);
|
|
|
|
break;
|
|
|
|
case SDL_TEXTINPUT:
|
|
|
|
|
|
|
|
#if defined(__LINUX__)
|
|
|
|
if(!(SDL_GetModState() & KMOD_LALT))
|
2017-10-13 18:25:10 +02:00
|
|
|
#endif
|
|
|
|
|
2017-09-26 08:59:34 +02:00
|
|
|
if(strlen(event->text.text) == 1)
|
|
|
|
{
|
2017-10-13 18:25:10 +02:00
|
|
|
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
inputSymbol(code, *event->text.text);
|
|
|
|
updateEditor(code);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
processGestures(code);
|
|
|
|
processMouse(code);
|
|
|
|
|
|
|
|
code->tic->api.clear(code->tic, getConfig()->theme.code.bg);
|
|
|
|
|
|
|
|
drawCode(code, true);
|
|
|
|
drawStatus(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void drawPopupBar(Code* code, const char* title)
|
|
|
|
{
|
2017-10-20 09:32:31 +02:00
|
|
|
enum {TextY = TOOLBAR_SIZE + 1};
|
2017-09-26 08:59:34 +02:00
|
|
|
|
2017-10-20 09:37:30 +02:00
|
|
|
code->tic->api.rect(code->tic, 0, TOOLBAR_SIZE, TIC80_WIDTH, TIC_FONT_HEIGHT + 1, (tic_color_blue));
|
|
|
|
code->tic->api.fixed_text(code->tic, title, 0, TextY, (tic_color_white));
|
2017-09-26 08:59:34 +02:00
|
|
|
|
2017-10-20 09:37:30 +02:00
|
|
|
code->tic->api.fixed_text(code->tic, code->popup.text, (s32)strlen(title)*TIC_FONT_WIDTH, TextY, (tic_color_white));
|
2017-10-20 09:31:38 +02:00
|
|
|
|
|
|
|
drawCursor(code, (s32)(strlen(title) + strlen(code->popup.text)) * TIC_FONT_WIDTH, TextY, ' ');
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void updateFindCode(Code* code, char* pos)
|
|
|
|
{
|
|
|
|
if(pos)
|
|
|
|
{
|
|
|
|
code->cursor.position = pos;
|
|
|
|
code->cursor.selection = pos + strlen(code->popup.text);
|
|
|
|
|
|
|
|
centerScroll(code);
|
|
|
|
updateEditor(code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static char* upStrStr(const char* start, const char* from, const char* substr)
|
|
|
|
{
|
|
|
|
const char* ptr = from-1;
|
|
|
|
size_t len = strlen(substr);
|
|
|
|
|
|
|
|
if(len > 0)
|
|
|
|
{
|
|
|
|
while(ptr >= start)
|
|
|
|
{
|
|
|
|
if(memcmp(ptr, substr, len) == 0)
|
|
|
|
return (char*)ptr;
|
|
|
|
|
|
|
|
ptr--;
|
2017-10-13 18:25:10 +02:00
|
|
|
}
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char* downStrStr(const char* start, const char* from, const char* substr)
|
|
|
|
{
|
|
|
|
return strstr(from, substr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void textFindTick(Code* code)
|
|
|
|
{
|
|
|
|
SDL_Event* event = NULL;
|
|
|
|
while ((event = pollEvent()))
|
|
|
|
{
|
|
|
|
switch(event->type)
|
|
|
|
{
|
|
|
|
case SDL_KEYDOWN:
|
|
|
|
switch(event->key.keysym.sym)
|
|
|
|
{
|
2017-10-13 18:25:10 +02:00
|
|
|
case SDLK_RETURN:
|
2017-09-26 08:59:34 +02:00
|
|
|
setCodeMode(code, TEXT_EDIT_MODE);
|
2017-10-13 18:25:10 +02:00
|
|
|
break;
|
2017-09-26 08:59:34 +02:00
|
|
|
case SDLK_UP:
|
|
|
|
case SDLK_LEFT:
|
|
|
|
case SDLK_DOWN:
|
|
|
|
case SDLK_RIGHT:
|
|
|
|
if(*code->popup.text)
|
|
|
|
{
|
|
|
|
SDL_Keycode keycode = event->key.keysym.sym;
|
|
|
|
bool reverse = keycode == SDLK_UP || keycode == SDLK_LEFT;
|
|
|
|
char* (*func)(const char*, const char*, const char*) = reverse ? upStrStr : downStrStr;
|
|
|
|
char* from = reverse ? SDL_min(code->cursor.position, code->cursor.selection) : SDL_max(code->cursor.position, code->cursor.selection);
|
2017-12-14 15:08:04 +01:00
|
|
|
char* pos = func(code->src, from, code->popup.text);
|
2017-09-26 08:59:34 +02:00
|
|
|
updateFindCode(code, pos);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SDLK_BACKSPACE:
|
|
|
|
if(*code->popup.text)
|
|
|
|
{
|
|
|
|
code->popup.text[strlen(code->popup.text)-1] = '\0';
|
2017-12-14 15:08:04 +01:00
|
|
|
updateFindCode(code, strstr(code->src, code->popup.text));
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SDL_TEXTINPUT:
|
2017-10-13 18:25:10 +02:00
|
|
|
if(strlen(event->text.text) == 1)
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
|
|
|
if(strlen(code->popup.text) + 1 < sizeof code->popup.text)
|
|
|
|
{
|
|
|
|
strcat(code->popup.text, event->text.text);
|
2017-12-14 15:08:04 +01:00
|
|
|
updateFindCode(code, strstr(code->src, code->popup.text));
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
code->tic->api.clear(code->tic, getConfig()->theme.code.bg);
|
|
|
|
|
|
|
|
drawCode(code, false);
|
|
|
|
drawPopupBar(code, " FIND:");
|
|
|
|
drawStatus(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void updateGotoCode(Code* code)
|
|
|
|
{
|
|
|
|
s32 line = atoi(code->popup.text);
|
|
|
|
|
|
|
|
if(line) line--;
|
|
|
|
|
|
|
|
s32 count = getLinesCount(code);
|
|
|
|
|
|
|
|
if(line > count) line = count;
|
|
|
|
|
|
|
|
code->cursor.selection = NULL;
|
|
|
|
setCursorPosition(code, 0, line);
|
|
|
|
|
|
|
|
code->jump.line = line;
|
|
|
|
|
|
|
|
centerScroll(code);
|
|
|
|
updateEditor(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void textGoToTick(Code* code)
|
|
|
|
{
|
|
|
|
SDL_Event* event = NULL;
|
|
|
|
while ((event = pollEvent()))
|
|
|
|
{
|
|
|
|
switch(event->type)
|
|
|
|
{
|
|
|
|
case SDL_KEYDOWN:
|
|
|
|
switch(event->key.keysym.sym)
|
|
|
|
{
|
2017-10-13 18:25:10 +02:00
|
|
|
case SDLK_RETURN:
|
2017-09-26 08:59:34 +02:00
|
|
|
if(*code->popup.text)
|
|
|
|
updateGotoCode(code);
|
|
|
|
|
|
|
|
setCodeMode(code, TEXT_EDIT_MODE);
|
|
|
|
break;
|
|
|
|
case SDLK_BACKSPACE:
|
|
|
|
if(*code->popup.text)
|
|
|
|
{
|
|
|
|
code->popup.text[strlen(code->popup.text)-1] = '\0';
|
|
|
|
updateGotoCode(code);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SDL_TEXTINPUT:
|
2017-10-13 18:25:10 +02:00
|
|
|
if(strlen(event->text.text) == 1)
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
|
|
|
char sym = *event->text.text;
|
|
|
|
|
|
|
|
if(strlen(code->popup.text)+1 < sizeof code->popup.text && sym >= '0' && sym <= '9')
|
|
|
|
{
|
|
|
|
strcat(code->popup.text, event->text.text);
|
|
|
|
updateGotoCode(code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
code->tic->api.clear(code->tic, getConfig()->theme.code.bg);
|
|
|
|
|
|
|
|
if(code->jump.line >= 0)
|
2017-10-20 09:32:31 +02:00
|
|
|
code->tic->api.rect(code->tic, 0, (code->jump.line - code->scroll.y) * TIC_FONT_HEIGHT + TOOLBAR_SIZE + 1,
|
2017-09-26 08:59:34 +02:00
|
|
|
TIC80_WIDTH, TIC_FONT_HEIGHT+1, getConfig()->theme.code.select);
|
|
|
|
|
|
|
|
drawCode(code, false);
|
|
|
|
drawPopupBar(code, " GOTO:");
|
|
|
|
drawStatus(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void drawOutlineBar(Code* code, s32 x, s32 y)
|
|
|
|
{
|
|
|
|
SDL_Rect rect = {x, y, TIC80_WIDTH - x, TIC80_HEIGHT - y};
|
|
|
|
|
|
|
|
if(checkMousePos(&rect))
|
|
|
|
{
|
|
|
|
s32 mx = getMouseY() - rect.y;
|
|
|
|
mx /= STUDIO_TEXT_HEIGHT;
|
|
|
|
|
|
|
|
if(mx < OUTLINE_SIZE && code->outline.items[mx].pos)
|
|
|
|
{
|
|
|
|
setCursor(SDL_SYSTEM_CURSOR_HAND);
|
|
|
|
|
|
|
|
if(checkMouseDown(&rect, SDL_BUTTON_LEFT))
|
|
|
|
{
|
|
|
|
code->outline.index = mx;
|
|
|
|
updateOutlineCode(code);
|
2017-10-13 18:25:10 +02:00
|
|
|
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if(checkMouseClick(&rect, SDL_BUTTON_LEFT))
|
|
|
|
setCodeMode(code, TEXT_EDIT_MODE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-20 09:37:30 +02:00
|
|
|
code->tic->api.rect(code->tic, rect.x-1, rect.y, rect.w+1, rect.h, (tic_color_blue));
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
OutlineItem* ptr = code->outline.items;
|
|
|
|
|
|
|
|
y++;
|
|
|
|
|
|
|
|
if(ptr->pos)
|
|
|
|
{
|
2017-10-13 18:25:10 +02:00
|
|
|
code->tic->api.rect(code->tic, rect.x - 1, rect.y + code->outline.index*STUDIO_TEXT_HEIGHT,
|
2017-10-20 09:37:30 +02:00
|
|
|
rect.w + 1, TIC_FONT_HEIGHT + 1, (tic_color_red));
|
2017-09-26 08:59:34 +02:00
|
|
|
while(ptr->pos)
|
|
|
|
{
|
2017-10-20 09:37:30 +02:00
|
|
|
code->tic->api.fixed_text(code->tic, ptr->name, x, y, (tic_color_white));
|
2017-09-26 08:59:34 +02:00
|
|
|
ptr++;
|
|
|
|
y += STUDIO_TEXT_HEIGHT;
|
2017-10-13 18:25:10 +02:00
|
|
|
}
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
2017-10-20 09:37:30 +02:00
|
|
|
else code->tic->api.fixed_text(code->tic, "(empty)", x, y, (tic_color_white));
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void textOutlineTick(Code* code)
|
|
|
|
{
|
|
|
|
SDL_Event* event = NULL;
|
|
|
|
while ((event = pollEvent()))
|
|
|
|
{
|
|
|
|
switch(event->type)
|
|
|
|
{
|
|
|
|
case SDL_KEYDOWN:
|
|
|
|
switch(event->key.keysym.sym)
|
|
|
|
{
|
2017-10-13 18:25:10 +02:00
|
|
|
case SDLK_UP:
|
2017-09-26 08:59:34 +02:00
|
|
|
if(code->outline.index > 0)
|
|
|
|
{
|
|
|
|
code->outline.index--;
|
|
|
|
updateOutlineCode(code);
|
|
|
|
}
|
|
|
|
break;
|
2017-10-13 18:25:10 +02:00
|
|
|
case SDLK_DOWN:
|
2017-09-26 08:59:34 +02:00
|
|
|
if(code->outline.index < OUTLINE_SIZE - 1 && code->outline.items[code->outline.index + 1].pos)
|
|
|
|
{
|
|
|
|
code->outline.index++;
|
|
|
|
updateOutlineCode(code);
|
|
|
|
}
|
|
|
|
break;
|
2017-10-13 18:25:10 +02:00
|
|
|
case SDLK_RETURN:
|
2017-09-26 08:59:34 +02:00
|
|
|
updateOutlineCode(code);
|
|
|
|
setCodeMode(code, TEXT_EDIT_MODE);
|
|
|
|
break;
|
|
|
|
case SDLK_BACKSPACE:
|
|
|
|
if(*code->popup.text)
|
|
|
|
{
|
|
|
|
code->popup.text[strlen(code->popup.text)-1] = '\0';
|
|
|
|
setOutlineMode(code);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SDL_TEXTINPUT:
|
2017-10-13 18:25:10 +02:00
|
|
|
if(strlen(event->text.text) == 1)
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
|
|
|
if(strlen(code->popup.text) + 1 < sizeof code->popup.text)
|
|
|
|
{
|
|
|
|
strcat(code->popup.text, event->text.text);
|
|
|
|
setOutlineMode(code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
code->tic->api.clear(code->tic, getConfig()->theme.code.bg);
|
|
|
|
|
|
|
|
drawCode(code, false);
|
|
|
|
drawPopupBar(code, " FUNC:");
|
|
|
|
drawStatus(code);
|
|
|
|
drawOutlineBar(code, TIC80_WIDTH - 12 * TIC_FONT_WIDTH, 2*(TIC_FONT_HEIGHT+1));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void drawCodeToolbar(Code* code)
|
|
|
|
{
|
2017-10-20 09:37:30 +02:00
|
|
|
code->tic->api.rect(code->tic, 0, 0, TIC80_WIDTH, TOOLBAR_SIZE, (tic_color_white));
|
2017-09-26 08:59:34 +02:00
|
|
|
|
2017-10-13 18:25:10 +02:00
|
|
|
static const u8 Icons[] =
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
2017-10-08 20:28:51 +02:00
|
|
|
0b00000000,
|
2017-10-09 07:58:02 +02:00
|
|
|
0b00100000,
|
|
|
|
0b00110000,
|
|
|
|
0b00111000,
|
|
|
|
0b00110000,
|
|
|
|
0b00100000,
|
2017-10-08 20:28:51 +02:00
|
|
|
0b00000000,
|
|
|
|
0b00000000,
|
|
|
|
|
2017-09-26 08:59:34 +02:00
|
|
|
0b00000000,
|
|
|
|
0b00111000,
|
|
|
|
0b01000100,
|
|
|
|
0b00111000,
|
|
|
|
0b00010000,
|
|
|
|
0b00010000,
|
|
|
|
0b00000000,
|
|
|
|
0b00000000,
|
|
|
|
|
|
|
|
0b00000000,
|
|
|
|
0b00010000,
|
|
|
|
0b00011000,
|
|
|
|
0b01111100,
|
|
|
|
0b00011000,
|
|
|
|
0b00010000,
|
|
|
|
0b00000000,
|
|
|
|
0b00000000,
|
|
|
|
|
|
|
|
0b00000000,
|
|
|
|
0b01111100,
|
|
|
|
0b00000000,
|
|
|
|
0b01111100,
|
|
|
|
0b00000000,
|
|
|
|
0b01111100,
|
|
|
|
0b00000000,
|
|
|
|
0b00000000,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum {Count = sizeof Icons / BITS_IN_BYTE};
|
|
|
|
enum {Size = 7};
|
|
|
|
|
2017-10-09 07:58:02 +02:00
|
|
|
static const char* Tips[] = {"RUN [ctrl+r]","FIND [ctrl+f]", "GOTO [ctrl+g]", "OUTLINE [ctrl+o]"};
|
2017-09-26 08:59:34 +02:00
|
|
|
|
|
|
|
for(s32 i = 0; i < Count; i++)
|
|
|
|
{
|
|
|
|
SDL_Rect rect = {TIC80_WIDTH + (i - Count) * Size, 0, Size, Size};
|
|
|
|
|
|
|
|
bool over = false;
|
|
|
|
if(checkMousePos(&rect))
|
|
|
|
{
|
|
|
|
setCursor(SDL_SYSTEM_CURSOR_HAND);
|
|
|
|
|
|
|
|
showTooltip(Tips[i]);
|
|
|
|
|
|
|
|
over = true;
|
|
|
|
|
|
|
|
if(checkMouseClick(&rect, SDL_BUTTON_LEFT))
|
|
|
|
{
|
2017-10-08 20:28:51 +02:00
|
|
|
if (i == TEXT_RUN_CODE)
|
|
|
|
{
|
|
|
|
runProject();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
s32 mode = TEXT_EDIT_MODE + i;
|
2017-09-26 08:59:34 +02:00
|
|
|
|
2017-10-08 20:28:51 +02:00
|
|
|
if(code->mode == mode) code->escape(code);
|
|
|
|
else setCodeMode(code, mode);
|
|
|
|
}
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-08 20:28:51 +02:00
|
|
|
bool active = i == code->mode - TEXT_EDIT_MODE && i != 0;
|
2017-09-26 08:59:34 +02:00
|
|
|
if(active)
|
2017-10-20 09:37:30 +02:00
|
|
|
code->tic->api.rect(code->tic, rect.x, rect.y, Size, Size, (tic_color_blue));
|
2017-09-26 08:59:34 +02:00
|
|
|
|
2017-10-20 09:37:30 +02:00
|
|
|
drawBitIcon(rect.x, rect.y, Icons + i*BITS_IN_BYTE, active ? (tic_color_white) : (over ? (tic_color_dark_gray) : (tic_color_light_blue)));
|
2017-09-26 08:59:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
drawToolbar(code->tic, getConfig()->theme.code.bg, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tick(Code* code)
|
|
|
|
{
|
|
|
|
if(code->cursor.delay)
|
|
|
|
code->cursor.delay--;
|
|
|
|
|
|
|
|
code->cursor.tick++;
|
|
|
|
|
|
|
|
switch(code->mode)
|
|
|
|
{
|
2017-10-08 20:28:51 +02:00
|
|
|
case TEXT_RUN_CODE: runProject(); break;
|
2017-09-26 08:59:34 +02:00
|
|
|
case TEXT_EDIT_MODE: textEditTick(code); break;
|
|
|
|
case TEXT_FIND_MODE: textFindTick(code); break;
|
|
|
|
case TEXT_GOTO_MODE: textGoToTick(code); break;
|
|
|
|
case TEXT_OUTLINE_MODE: textOutlineTick(code); break;
|
|
|
|
}
|
|
|
|
|
|
|
|
drawCodeToolbar(code);
|
|
|
|
|
|
|
|
code->tickCounter++;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void escape(Code* code)
|
|
|
|
{
|
|
|
|
if(code->mode != TEXT_EDIT_MODE)
|
|
|
|
{
|
|
|
|
code->cursor.position = code->popup.prevPos;
|
|
|
|
code->cursor.selection = code->popup.prevSel;
|
|
|
|
code->popup.prevSel = code->popup.prevPos = NULL;
|
|
|
|
|
|
|
|
code->mode = TEXT_EDIT_MODE;
|
|
|
|
|
|
|
|
updateEditor(code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void onStudioEvent(Code* code, StudioEvent event)
|
|
|
|
{
|
|
|
|
switch(event)
|
|
|
|
{
|
|
|
|
case TIC_TOOLBAR_CUT: cutToClipboard(code); break;
|
|
|
|
case TIC_TOOLBAR_COPY: copyToClipboard(code); break;
|
|
|
|
case TIC_TOOLBAR_PASTE: copyFromClipboard(code); break;
|
|
|
|
case TIC_TOOLBAR_UNDO: undo(code); break;
|
|
|
|
case TIC_TOOLBAR_REDO: redo(code); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-14 12:38:06 +01:00
|
|
|
void initCode(Code* code, tic_mem* tic, tic_code* src)
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
|
|
|
if(code->outline.items == NULL)
|
|
|
|
code->outline.items = (OutlineItem*)SDL_malloc(OUTLINE_ITEMS_SIZE);
|
|
|
|
|
|
|
|
if(code->history) history_delete(code->history);
|
|
|
|
if(code->cursorHistory) history_delete(code->cursorHistory);
|
|
|
|
|
|
|
|
*code = (Code)
|
|
|
|
{
|
|
|
|
.tic = tic,
|
2017-12-14 15:08:04 +01:00
|
|
|
.src = src->data,
|
2017-09-26 08:59:34 +02:00
|
|
|
.tick = tick,
|
|
|
|
.escape = escape,
|
2017-12-14 12:38:06 +01:00
|
|
|
.cursor = {{src->data, NULL, 0, 0}, NULL, 0},
|
2017-10-20 09:32:31 +02:00
|
|
|
.rect = {0, TOOLBAR_SIZE + 1, TIC80_WIDTH, TIC80_HEIGHT - TOOLBAR_SIZE - TIC_FONT_HEIGHT - 1},
|
2017-09-26 08:59:34 +02:00
|
|
|
.scroll = {0, 0, {0, 0}, false},
|
|
|
|
.tickCounter = 0,
|
|
|
|
.history = NULL,
|
|
|
|
.cursorHistory = NULL,
|
|
|
|
.mode = TEXT_EDIT_MODE,
|
|
|
|
.jump = {.line = -1},
|
2017-10-13 18:25:10 +02:00
|
|
|
.popup =
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
|
|
|
.prevPos = NULL,
|
|
|
|
.prevSel = NULL,
|
|
|
|
},
|
2017-10-13 18:25:10 +02:00
|
|
|
.outline =
|
2017-09-26 08:59:34 +02:00
|
|
|
{
|
|
|
|
.items = code->outline.items,
|
|
|
|
.index = 0,
|
|
|
|
},
|
|
|
|
.event = onStudioEvent,
|
|
|
|
.update = update,
|
|
|
|
};
|
|
|
|
|
2017-12-14 15:08:04 +01:00
|
|
|
code->history = history_create(code->src, sizeof(tic_code));
|
2017-09-26 08:59:34 +02:00
|
|
|
code->cursorHistory = history_create(&code->cursor, sizeof code->cursor);
|
|
|
|
|
|
|
|
update(code);
|
|
|
|
}
|