TIC-80-guile/src/code.c

1504 lines
32 KiB
C
Raw Normal View History

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;
2018-02-06 20:15:56 +01:00
struct { char* start; char* end; } selection = {MIN(code->cursor.selection, code->cursor.position),
MAX(code->cursor.selection, code->cursor.position)};
2017-09-26 08:59:34 +02:00
struct { s32 x; s32 y; char symbol; } cursor = {-1, -1, 0};
while(*pointer)
{
char symbol = *pointer;
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;
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;
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-12-14 15:08:04 +01:00
memset(code->src + codeLen, '\0', TIC_CODE_SIZE - codeLen);
memcpy(code->status + sizeof code->status - strlen(status) - 1, status, strlen(status));
2017-09-26 08:59:34 +02:00
}
}
2017-12-20 21:14:41 +01:00
static inline bool isalnum_(char c) {return isalnum(c) || c == '_';}
2017-12-20 21:39:21 +01:00
static void parseSyntaxColor(Code* code)
{
2017-12-23 16:19:51 +01:00
memset(code->colorBuffer, getConfig()->theme.code.syntax.var, sizeof(code->colorBuffer));
2017-12-20 18:53:19 +01:00
2017-12-21 08:28:14 +01:00
tic_mem* tic = code->tic;
2017-12-23 16:19:51 +01:00
const tic_script_config* config = tic->api.get_script_config(tic);
if(config->parse)
config->parse(config, code->src, code->colorBuffer, &getConfig()->theme.code.syntax);
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);
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-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);
}
static void goCodeHome(Code *code)
{
2017-12-14 15:08:04 +01:00
code->cursor.position = code->src;
updateColumn(code);
}
static void goCodeEnd(Code *code)
{
2017-12-14 15:08:04 +01:00
code->cursor.position = code->src + strlen(code->src);
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)
{
2018-02-06 20:15:56 +01:00
char* start = MIN(sel, pos);
char* end = MAX(sel, pos);
2017-09-26 08:59:34 +02:00
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)
{
2018-02-06 20:15:56 +01:00
start = MIN(sel, pos);
size = MAX(sel, pos) - start;
2017-09-26 08:59:34 +02:00
}
else
{
start = getLine(code);
size = getNextLine(code) - start;
}
2018-02-06 20:15:56 +01:00
char* clipboard = (char*)malloc(size+1);
2017-09-26 08:59:34 +02:00
if(clipboard)
{
memcpy(clipboard, start, size);
clipboard[size] = '\0';
2018-02-06 20:15:56 +01:00
setClipboardText(clipboard);
free(clipboard);
2017-09-26 08:59:34 +02:00
}
}
static void cutToClipboard(Code* code)
{
copyToClipboard(code);
replaceSelection(code);
history(code);
}
static void copyFromClipboard(Code* code)
{
2018-02-06 20:15:56 +01:00
if(hasClipboardText())
2017-09-26 08:59:34 +02:00
{
2018-02-06 20:15:56 +01:00
char* clipboard = getClipboardText();
2017-09-26 08:59:34 +02:00
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);
}
2018-02-06 20:15:56 +01:00
free(clipboard);
2017-09-26 08:59:34 +02:00
}
}
}
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, bool crtl)
{
char* cursor_position = code->cursor.position;
char* cursor_selection = code->cursor.selection;
bool has_selection = cursor_selection && cursor_selection != cursor_position;
bool modifier_key_pressed = shift || crtl;
if(has_selection || modifier_key_pressed)
{
char* start;
char* end;
2017-09-26 08:59:34 +02:00
bool changed = false;
if(cursor_selection) {
2018-02-06 20:15:56 +01:00
start = MIN(cursor_selection, cursor_position);
end = MAX(cursor_selection, cursor_position);
} else {
start = end = cursor_position;
}
2017-09-26 08:59:34 +02:00
char* line = start = getLineByPos(code, start);
while(line)
2017-09-26 08:59:34 +02:00
{
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;
}
2017-09-26 08:59:34 +02:00
line = getNextLineByPos(code, line);
if(line >= end) break;
2017-09-26 08:59:34 +02:00
}
if(changed) {
if(has_selection) {
code->cursor.position = start;
code->cursor.selection = end;
}
else if (start <= end) code->cursor.position = end;
history(code);
parseSyntaxColor(code);
}
2017-09-26 08:59:34 +02:00
}
else inputSymbolBase(code, '\t');
}
static void setFindMode(Code* code)
{
if(code->cursor.selection)
{
2018-02-06 20:15:56 +01:00
const char* end = MAX(code->cursor.position, code->cursor.selection);
const char* start = MIN(code->cursor.position, code->cursor.selection);
2017-09-26 08:59:34 +02:00
size_t len = end - start;
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;
2018-02-06 20:15:56 +01:00
// return SDL_strcasecmp(item1->name, item2->name);
return strcmp(item1->name, item2->name);
2017-09-26 08:59:34 +02:00
}
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);
}
2017-12-22 12:20:15 +01:00
static void initOutlineMode(Code* code)
2017-09-26 08:59:34 +02:00
{
OutlineItem* out = code->outline.items;
OutlineItem* end = out + OUTLINE_SIZE;
2017-12-22 12:20:15 +01:00
tic_mem* tic = code->tic;
2017-09-26 08:59:34 +02:00
2017-12-22 12:20:15 +01:00
char buffer[STUDIO_TEXT_BUFFER_WIDTH] = {0};
char filter[STUDIO_TEXT_BUFFER_WIDTH] = {0};
2018-02-06 20:15:56 +01:00
strncpy(filter, code->popup.text, sizeof(filter));
2018-02-12 17:15:05 +01:00
_strlwr(filter);
2017-12-22 12:20:15 +01:00
const tic_script_config* config = tic->api.get_script_config(tic);
2017-09-26 08:59:34 +02:00
2017-12-22 12:20:15 +01:00
if(config->getOutline)
2017-09-26 08:59:34 +02:00
{
2017-12-22 12:20:15 +01:00
s32 size = 0;
const tic_outline_item* items = config->getOutline(code->src, &size);
2017-09-26 08:59:34 +02:00
2017-12-22 12:20:15 +01:00
for(s32 i = 0; i < size; i++)
2017-09-26 08:59:34 +02:00
{
2017-12-22 12:20:15 +01:00
const tic_outline_item* item = items + i;
2017-09-26 08:59:34 +02:00
2017-12-22 12:20:15 +01:00
if(out < end)
2017-09-26 08:59:34 +02:00
{
2017-12-22 12:20:15 +01:00
out->pos = code->src + item->pos;
memset(out->name, 0, STUDIO_TEXT_BUFFER_WIDTH);
2018-02-06 20:15:56 +01:00
memcpy(out->name, out->pos, MIN(item->size, STUDIO_TEXT_BUFFER_WIDTH-1));
2017-09-26 08:59:34 +02:00
if(*filter)
{
2018-02-06 20:15:56 +01:00
strncpy(buffer, out->name, sizeof(buffer));
2018-02-12 17:15:05 +01:00
_strlwr(buffer);
2017-12-22 12:20:15 +01:00
if(strstr(buffer, filter)) out++;
else out->pos = NULL;
2017-09-26 08:59:34 +02:00
}
2017-12-22 12:20:15 +01:00
else out++;
2017-09-26 08:59:34 +02:00
}
2017-12-22 12:20:15 +01:00
else break;
2017-09-26 08:59:34 +02:00
}
}
}
static void setOutlineMode(Code* code)
{
code->outline.index = 0;
memset(code->outline.items, 0, OUTLINE_ITEMS_SIZE);
2017-12-22 12:20:15 +01:00
initOutlineMode(code);
2017-09-26 08:59:34 +02:00
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)
{
const char* comment = code->tic->api.get_script_config(code->tic)->singleComment;
size_t size = strlen(comment);
2017-09-26 08:59:34 +02:00
char* line = getLine(code);
const char* end = line + getLineSize(line);
while((*line == ' ' || *line == '\t') && line < end) line++;
if(memcmp(line, comment, size))
2017-09-26 08:59:34 +02: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);
2017-09-26 08:59:34 +02:00
if(code->cursor.position > line)
code->cursor.position += size;
2017-09-26 08:59:34 +02:00
}
else
{
memmove(line, line + size, strlen(line + size)+1);
2017-09-26 08:59:34 +02:00
if(code->cursor.position > line + size)
code->cursor.position -= size;
2017-09-26 08:59:34 +02:00
}
code->cursor.selection = NULL;
2017-09-26 08:59:34 +02:00
history(code);
parseSyntaxColor(code);
}
2018-02-12 17:15:05 +01:00
static void processKeyboard(Code* code)
{
tic_mem* tic = code->tic;
switch(getClipboardEvent(0))
{
case TIC_CLIPBOARD_CUT: cutToClipboard(code); break;
case TIC_CLIPBOARD_COPY: copyToClipboard(code); break;
case TIC_CLIPBOARD_PASTE: copyFromClipboard(code); break;
default: break;
}
bool shift = tic->api.key(tic, tic_key_shift);
bool ctrl = tic->api.key(tic, tic_key_ctrl);
bool alt = tic->api.key(tic, tic_key_alt);
2018-02-12 17:16:37 +01:00
if(isKeyBeenPressed(tic_key_up)
|| isKeyBeenPressed(tic_key_down)
|| isKeyBeenPressed(tic_key_left)
|| isKeyBeenPressed(tic_key_right)
|| isKeyBeenPressed(tic_key_home)
|| isKeyBeenPressed(tic_key_end)
|| isKeyBeenPressed(tic_key_pageup)
|| isKeyBeenPressed(tic_key_pagedown))
2018-02-12 17:15:05 +01:00
{
if(!shift) code->cursor.selection = NULL;
else if(code->cursor.selection == NULL) code->cursor.selection = code->cursor.position;
}
if(ctrl)
{
if(ctrl)
{
2018-02-12 17:16:37 +01:00
if(isKeyBeenPressed(tic_key_left)) leftWord(code);
else if(isKeyBeenPressed(tic_key_right)) rightWord(code);
else if(isKeyBeenPressed(tic_key_tab)) doTab(code, shift, ctrl);
2018-02-12 17:15:05 +01:00
}
// else if(keymod & KMOD_GUI)
// {
// switch(keycode)
// {
// case SDLK_LEFT: goHome(code); break;
// case SDLK_RIGHT: goEnd(code); break;
// }
// }
2018-02-12 17:16:37 +01:00
if(isKeyBeenPressed(tic_key_a)) selectAll(code);
else if(isKeyBeenPressed(tic_key_z)) undo(code);
else if(isKeyBeenPressed(tic_key_y)) redo(code);
else if(isKeyBeenPressed(tic_key_f)) setCodeMode(code, TEXT_FIND_MODE);
else if(isKeyBeenPressed(tic_key_g)) setCodeMode(code, TEXT_GOTO_MODE);
else if(isKeyBeenPressed(tic_key_o)) setCodeMode(code, TEXT_OUTLINE_MODE);
else if(isKeyBeenPressed(tic_key_slash)) commentLine(code);
else if(isKeyBeenPressed(tic_key_home)) goCodeHome(code);
else if(isKeyBeenPressed(tic_key_end)) goCodeEnd(code);
2018-02-12 17:15:05 +01:00
}
else if(alt)
{
2018-02-12 17:16:37 +01:00
if(isKeyBeenPressed(tic_key_left)) leftWord(code);
else if(isKeyBeenPressed(tic_key_right)) rightWord(code);
2018-02-12 17:15:05 +01:00
}
else
{
2018-02-12 17:16:37 +01:00
if(isKeyBeenPressed(tic_key_up)) upLine(code);
else if(isKeyBeenPressed(tic_key_down)) downLine(code);
else if(isKeyBeenPressed(tic_key_left)) leftColumn(code);
else if(isKeyBeenPressed(tic_key_right)) rightColumn(code);
else if(isKeyBeenPressed(tic_key_home)) goHome(code);
else if(isKeyBeenPressed(tic_key_end)) goEnd(code);
else if(isKeyBeenPressed(tic_key_pageup)) pageUp(code);
else if(isKeyBeenPressed(tic_key_pagedown)) pageDown(code);
else if(isKeyBeenPressed(tic_key_delete)) deleteChar(code);
else if(isKeyBeenPressed(tic_key_backspace)) backspaceChar(code);
else if(isKeyBeenPressed(tic_key_return)) newLine(code);
else if(isKeyBeenPressed(tic_key_tab)) doTab(code, shift, ctrl);
2018-02-12 17:15:05 +01:00
}
updateEditor(code);
}
2017-09-26 08:59:34 +02:00
static void processGestures(Code* code)
{
2018-02-01 17:34:42 +01:00
tic_point point = {0, 0};
2017-09-26 08:59:34 +02:00
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)
{
2018-02-12 12:59:14 +01:00
tic_mem* tic = code->tic;
2017-09-26 08:59:34 +02:00
if(checkMousePos(&code->rect))
{
2018-02-06 20:15:56 +01:00
setCursor(tic_cursor_ibeam);
2017-09-26 08:59:34 +02:00
if(code->scroll.active)
{
2018-02-01 16:32:31 +01:00
if(checkMouseDown(&code->rect, tic_mouse_right))
2017-09-26 08:59:34 +02:00
{
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
{
2018-02-01 16:32:31 +01:00
if(checkMouseDown(&code->rect, tic_mouse_left))
2017-09-26 08:59:34 +02:00
{
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);
2018-02-12 12:59:14 +01:00
if(tic->api.key(tic, tic_key_shift))
{
code->cursor.selection = code->cursor.position;
code->cursor.position = position;
}
else if(!code->cursor.mouseDownPosition)
{
code->cursor.selection = code->cursor.position;
code->cursor.mouseDownPosition = code->cursor.position;
}
2017-09-26 08:59:34 +02:00
}
else
{
if(code->cursor.mouseDownPosition == code->cursor.position)
code->cursor.selection = NULL;
code->cursor.mouseDownPosition = NULL;
}
2018-02-01 16:32:31 +01:00
if(checkMouseDown(&code->rect, tic_mouse_right))
2017-09-26 08:59:34 +02:00
{
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)
{
2018-02-12 17:15:05 +01:00
tic_mem* tic = code->tic;
2018-02-12 11:31:52 +01:00
// process scroll
{
tic80_input* input = &code->tic->ram.input;
if(input->mouse.scrolly)
{
enum{Scroll = 3};
s32 delta = input->mouse.scrolly > 0 ? -Scroll : Scroll;
code->scroll.y += delta;
normalizeScroll(code);
}
}
2018-02-12 17:15:05 +01:00
processKeyboard(code);
2018-01-24 10:20:05 +01:00
2018-02-12 17:15:05 +01:00
if(!tic->api.key(tic, tic_key_ctrl) && !tic->api.key(tic, tic_key_alt))
{
char sym = getKeyboardText();
2018-01-24 10:20:05 +01:00
2018-02-12 17:15:05 +01:00
if(sym)
{
inputSymbol(code, sym);
updateEditor(code);
}
}
2017-09-26 08:59:34 +02:00
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-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)
{
2018-02-12 17:16:37 +01:00
if(isKeyBeenPressed(tic_key_return)) setCodeMode(code, TEXT_EDIT_MODE);
else if(isKeyBeenPressed(tic_key_up)
|| isKeyBeenPressed(tic_key_down)
|| isKeyBeenPressed(tic_key_left)
|| isKeyBeenPressed(tic_key_right))
2018-02-12 17:15:05 +01:00
{
if(*code->popup.text)
{
2018-02-12 17:16:37 +01:00
bool reverse = isKeyBeenPressed(tic_key_up) || isKeyBeenPressed(tic_key_left);
2018-02-12 17:15:05 +01:00
char* (*func)(const char*, const char*, const char*) = reverse ? upStrStr : downStrStr;
char* from = reverse ? MIN(code->cursor.position, code->cursor.selection) : MAX(code->cursor.position, code->cursor.selection);
char* pos = func(code->src, from, code->popup.text);
updateFindCode(code, pos);
}
}
2018-02-12 17:16:37 +01:00
else if(isKeyBeenPressed(tic_key_backspace))
2018-02-12 17:15:05 +01:00
{
if(*code->popup.text)
{
code->popup.text[strlen(code->popup.text)-1] = '\0';
updateFindCode(code, strstr(code->src, code->popup.text));
}
}
char sym = getKeyboardText();
if(sym)
{
if(strlen(code->popup.text) + 1 < sizeof code->popup.text)
{
char str[] = {sym , 0};
strcat(code->popup.text, str);
updateFindCode(code, strstr(code->src, code->popup.text));
}
}
2017-09-26 08:59:34 +02:00
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)
{
2018-02-12 17:15:05 +01:00
tic_mem* tic = code->tic;
2017-09-26 08:59:34 +02:00
2018-02-12 17:16:37 +01:00
if(isKeyBeenPressed(tic_key_return))
2018-02-12 17:15:05 +01:00
{
if(*code->popup.text)
updateGotoCode(code);
setCodeMode(code, TEXT_EDIT_MODE);
}
2018-02-12 17:16:37 +01:00
else if(isKeyBeenPressed(tic_key_backspace))
2018-02-12 17:15:05 +01:00
{
if(*code->popup.text)
{
code->popup.text[strlen(code->popup.text)-1] = '\0';
updateGotoCode(code);
}
}
char sym = getKeyboardText();
if(sym)
{
if(strlen(code->popup.text)+1 < sizeof code->popup.text && sym >= '0' && sym <= '9')
{
char str[] = {sym, 0};
strcat(code->popup.text, str);
updateGotoCode(code);
}
}
tic->api.clear(tic, getConfig()->theme.code.bg);
2017-09-26 08:59:34 +02:00
if(code->jump.line >= 0)
2018-02-12 17:15:05 +01:00
tic->api.rect(tic, 0, (code->jump.line - code->scroll.y) * (TIC_FONT_HEIGHT+1) + TOOLBAR_SIZE,
TIC80_WIDTH, TIC_FONT_HEIGHT+2, getConfig()->theme.code.select);
2017-09-26 08:59:34 +02:00
drawCode(code, false);
drawPopupBar(code, " GOTO:");
drawStatus(code);
}
static void drawOutlineBar(Code* code, s32 x, s32 y)
{
2018-02-01 17:34:42 +01:00
tic_rect rect = {x, y, TIC80_WIDTH - x, TIC80_HEIGHT - y};
2017-09-26 08:59:34 +02:00
if(checkMousePos(&rect))
{
s32 mx = getMouseY() - rect.y;
mx /= STUDIO_TEXT_HEIGHT;
if(mx < OUTLINE_SIZE && code->outline.items[mx].pos)
{
2018-02-06 20:15:56 +01:00
setCursor(tic_cursor_hand);
2017-09-26 08:59:34 +02:00
2018-02-01 16:32:31 +01:00
if(checkMouseDown(&rect, tic_mouse_left))
2017-09-26 08:59:34 +02:00
{
code->outline.index = mx;
updateOutlineCode(code);
2017-09-26 08:59:34 +02:00
}
2018-02-01 16:32:31 +01:00
if(checkMouseClick(&rect, tic_mouse_left))
2017-09-26 08:59:34 +02:00
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)
{
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-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)
{
2018-02-12 17:16:37 +01:00
if(isKeyBeenPressed(tic_key_up))
2018-02-12 17:15:05 +01:00
{
if(code->outline.index > 0)
{
code->outline.index--;
updateOutlineCode(code);
}
}
2018-02-12 17:16:37 +01:00
else if(isKeyBeenPressed(tic_key_down))
2018-02-12 17:15:05 +01:00
{
if(code->outline.index < OUTLINE_SIZE - 1 && code->outline.items[code->outline.index + 1].pos)
{
code->outline.index++;
updateOutlineCode(code);
}
}
2018-02-12 17:16:37 +01:00
else if(isKeyBeenPressed(tic_key_return))
2018-02-12 17:15:05 +01:00
{
updateOutlineCode(code);
setCodeMode(code, TEXT_EDIT_MODE);
}
2018-02-12 17:16:37 +01:00
else if(isKeyBeenPressed(tic_key_backspace))
2018-02-12 17:15:05 +01:00
{
if(*code->popup.text)
{
code->popup.text[strlen(code->popup.text)-1] = '\0';
setOutlineMode(code);
}
}
char sym = getKeyboardText();
if(sym)
{
if(strlen(code->popup.text) + 1 < sizeof code->popup.text)
{
char str[] = {sym, 0};
strcat(code->popup.text, str);
setOutlineMode(code);
}
}
2017-09-26 08:59:34 +02:00
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
static const u8 Icons[] =
2017-09-26 08:59:34 +02:00
{
2017-10-08 20:28:51 +02:00
0b00000000,
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};
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++)
{
2018-02-01 17:34:42 +01:00
tic_rect rect = {TIC80_WIDTH + (i - Count) * Size, 0, Size, Size};
2017-09-26 08:59:34 +02:00
bool over = false;
if(checkMousePos(&rect))
{
2018-02-06 20:15:56 +01:00
setCursor(tic_cursor_hand);
2017-09-26 08:59:34 +02:00
showTooltip(Tips[i]);
over = true;
2018-02-01 16:32:31 +01:00
if(checkMouseClick(&rect, tic_mouse_left))
2017-09-26 08:59:34 +02:00
{
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)
2018-02-06 20:15:56 +01:00
code->outline.items = (OutlineItem*)malloc(OUTLINE_ITEMS_SIZE);
2017-09-26 08:59:34 +02:00
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},
.popup =
2017-09-26 08:59:34 +02:00
{
.prevPos = NULL,
.prevSel = NULL,
},
.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);
}