//////////////////////////////// // NOTE(allen): Buffer Structure internal C_Token E_MakeToken(C_TokenKind kind, STR_Index string){ C_Token result = {kind, string}; return(result); } internal E_TokenBuffer E_InitTokenBuffer(void){ E_TokenBuffer buffer = {0}; buffer.arena = M_ArenaInitializeWithAlign(1); buffer.max = 1024; buffer.tokens = PushArray(&buffer.arena, C_Token, buffer.max); buffer.count = 0; return(buffer); } internal E_TokenBuffer E_InitTextFieldTokenBuffer(C_Token *text_field_token){ E_TokenBuffer buffer = {0}; buffer.tokens = text_field_token; buffer.count = 0; buffer.max = 1; buffer.text_field_mode = 1; return(buffer); } internal void E_ReleaseTokenBuffer(E_TokenBuffer *buffer){ if (!buffer->text_field_mode){ M_ArenaRelease(&buffer->arena); } MemoryZeroStruct(buffer); } internal void E_TokenBufferNotifyChange(E_TokenBuffer *buffer){ buffer->dirty = 1; } internal b32 E_TokenBufferHasChange(E_TokenBuffer *buffer){ return(buffer->dirty); } internal void E_TokenBufferChangeHandled(E_TokenBuffer *buffer){ buffer->dirty = 0; } internal u64 E_TokenBufferGetCount(E_TokenBuffer *buffer){ return(buffer->count); } internal C_Token* E_TokenBufferReadRange(E_TokenBuffer *buffer, Rangeu range){ C_Token *result = 0; if (range.min <= range.max && range.max <= buffer->count){ result = buffer->tokens + range.min; } return(result); } internal b32 E_TokenBufferReplaceRange(E_TokenBuffer *buffer, Rangeu range, C_Token *new_tokens, u64 new_token_count){ b32 result = 0; if (range.min <= range.max && range.max <= buffer->count){ result = 1; i64 shift = new_token_count - (range.max - range.min); AssertImplies(shift < 0, (-shift) <= buffer->count); u64 new_count = buffer->count + shift; if (new_count > buffer->max){ if (buffer->text_field_mode){ result = 0; } else{ PushArray(&buffer->arena, C_Token, new_count - buffer->max); buffer->max = new_count; } } if (result && buffer->text_field_mode){ Assert(new_token_count <= 1); if (new_token_count == 1 && new_tokens[0].kind != C_TokenKind_Label){ result = 0; } } if (result){ u64 size_of_tail = buffer->count - range.max; if (size_of_tail > 0){ u64 old_start_of_tail = range.max; u64 new_start_of_tail = old_start_of_tail + shift; MemoryCopy(buffer->tokens + new_start_of_tail, buffer->tokens + old_start_of_tail, sizeof(*buffer->tokens)*size_of_tail); } MemoryCopy(buffer->tokens + range.min, new_tokens, sizeof(*buffer->tokens)*new_token_count); buffer->count = new_count; E_TokenBufferNotifyChange(buffer); } } return(result); } internal b32 E_TokenBufferInsert(E_TokenBuffer *buffer, u64 pos, C_Token new_token){ b32 result = E_TokenBufferReplaceRange(buffer, MakeRangeu(pos, pos), &new_token, 1); return(result); } internal b32 E_TokenBufferModify(E_TokenBuffer *buffer, u64 pos, C_Token new_token){ b32 result = 0; if (pos < buffer->count){ buffer->tokens[pos] = new_token; E_TokenBufferNotifyChange(buffer); } return(result); } internal b32 E_TokenBufferModifyString(E_TokenBuffer *buffer, u64 pos, String8 new_string){ b32 result = 0; if (pos < buffer->count){ STR_Hash *string_hash = APP_GetStringHash(); buffer->tokens[pos].string = STR_Save(string_hash, new_string); E_TokenBufferNotifyChange(buffer); } return(result); } internal b32 E_TokenBufferDelete(E_TokenBuffer *buffer, u64 pos){ b32 result = E_TokenBufferReplaceRange(buffer, MakeRangeu(pos, pos + 1), 0, 0); return(result); } internal void E_TokenBufferScrub(E_TokenBuffer *buffer, u64 *cursor_pos, u64 cursor_count){ M_Arena *scratch = OS_GetScratch(); STR_Hash *string_hash = APP_GetStringHash(); u64 shift = 0; u64 i = 0; C_Token *r = buffer->tokens; C_Token *w = buffer->tokens; C_Token *opl = r + buffer->count; b32 next_can_be_space = 1; b32 merge_back_label = 0; b32 merge_back_comment = 0; for (;r < opl; r += 1, i += 1){ // NOTE(allen): Correct cursors if (shift != 0){ for (u64 j = 0; j < cursor_count; j += 1){ if (cursor_pos[j] == i){ cursor_pos[j] -= shift; } } } // NOTE(allen): Transfer logic b32 write = ((r->kind != C_TokenKind_Space) || next_can_be_space); if (write){ b32 merge_back = (((r->kind == C_TokenKind_Label) && merge_back_label) || ((r->kind == C_TokenKind_Comment) && merge_back_comment)); if (merge_back){ C_Token *pw = w - 1; String8 pre = STR_Read(string_hash, pw->string); String8 post = STR_Read(string_hash, r->string); String8 str = PushStringCat(scratch, pre, post); pw->string = STR_Save(string_hash, str); shift += 1; } else{ if (w < r){ MemoryCopyStruct(w, r); } w += 1; } } else{ shift += 1; } switch (r->kind){ default: { next_can_be_space = 1; merge_back_label = 0; merge_back_comment = 0; }break; case C_TokenKind_Comment: { next_can_be_space = 1; merge_back_label = 0; merge_back_comment = 1; }break; case C_TokenKind_Label: { next_can_be_space = 1; merge_back_label = 1; merge_back_comment = 0; }break; case C_TokenKind_Space: case C_TokenKind_Newline: case C_TokenKind_OpenParen: { next_can_be_space = 1; merge_back_label = 0; merge_back_comment = 0; }break; } } // NOTE(allen): Correct cursors if (shift != 0){ for (u64 j = 0; j < cursor_count; j += 1){ if (cursor_pos[j] == i){ cursor_pos[j] -= shift; } } } buffer->count = w - buffer->tokens; OS_ReleaseScratch(scratch); } //////////////////////////////// // NOTE(allen): Layout internal E_LayoutCtx _E_InitLayoutCtx(M_Arena *out_arena, M_Arena *temp_arena, E_RuneLayout *layout){ Assert(out_arena != temp_arena); E_LayoutCtx ctx = {0}; ctx.arena = out_arena; ctx.temp_arena = temp_arena; ctx.layout = layout; ctx.layout->vals = PushArray(out_arena, E_Rune, 0); return(ctx); } internal void _E_PushIndent(E_LayoutCtx *ctx, f32 pre, f32 post){ E_LayoutIndent *indent = ctx->free_indent; if (indent != 0){ SLLStackPop(ctx->free_indent); } else{ indent = PushArray(ctx->temp_arena, E_LayoutIndent, 1); } SLLStackPush(ctx->indent, indent); indent->pre_indent = pre; indent->post_indent = post; } internal void _E_PopIndent(E_LayoutCtx *ctx){ if (ctx->indent != 0){ E_LayoutIndent *indent = ctx->indent; SLLStackPop(ctx->indent); SLLStackPush(ctx->free_indent, indent); } } internal f32 _E_ReadIndent(E_LayoutCtx *ctx, b32 pre){ f32 result = 0.f; if (ctx->indent != 0){ if (pre){ result = ctx->indent->pre_indent; } else{ result = ctx->indent->post_indent; } } return(result); } internal E_Rune* _E_PushRuneLineStart(E_LayoutCtx *ctx, b32 pre_indented){ E_RuneLayout *layout = ctx->layout; if (layout->first_line != 0){ ctx->p.y += ctx->space_dim.y; } E_Rune *result = PushArrayZero(ctx->arena, E_Rune, 1); result->cursor_pos = ctx->cursor_pos; f32 x = _E_ReadIndent(ctx, pre_indented) + ctx->initial_padding; f32 y = ctx->p.y; result->rect = MakeRect(0.f, y, x, y + ctx->space_dim.y); result->color = ctx->cl_whitespace; result->kind = E_RuneKind_LineStart; ctx->cursor_pos += 1; ctx->p.x = x; DLLPushBack(layout->first_line, layout->last_line, result); layout->dim.x = Max(layout->dim.x, result->rect.x1); layout->dim.y = Max(layout->dim.y, result->rect.y1); return(result); } internal E_Rune* _E_PushRune(E_LayoutCtx *ctx, v4 color, String8 string, E_RuneKind kind){ v2 dim = R_StringDimWithFont(ctx->font, ctx->text_scale, string); E_Rune *result = PushArrayZero(ctx->arena, E_Rune, 1); result->cursor_pos = ctx->cursor_pos; v2 p = ctx->p; result->string = string; result->rect = MakeRect(V2Expand(p), p.x + dim.x, p.y + ctx->space_dim.y); result->color = color; result->kind = kind; result->text_scale = ctx->text_scale; ctx->cursor_pos += 1; ctx->p.x += dim.x; E_RuneLayout *layout = ctx->layout; layout->dim.x = Max(layout->dim.x, result->rect.x1); layout->dim.y = Max(layout->dim.y, result->rect.y1); return(result); } internal E_Rune* _E_PushRuneError(E_LayoutCtx *ctx){ E_Rune *result = _E_PushRune(ctx, ctx->cl_error, S8Lit("ERROR"), E_RuneKind_Error); E_RuneLayout *layout = ctx->layout; DLLPushBack(layout->first_error, layout->last_error, result); return(result); } internal E_Rune* _E_PushRuneSpace(E_LayoutCtx *ctx){ E_Rune *result = _E_PushRune(ctx, ctx->cl_whitespace, S8Lit(" "), E_RuneKind_Space); return(result); } internal E_RuneLayout E_LayoutTokenBuffer(M_Arena *arena, R_Font *font, E_TokenBuffer *buffer){ STR_Hash *string_hash = APP_GetStringHash(); M_Arena *scratch = OS_GetScratch1(arena); E_RuneLayout result = {0, 0}; E_LayoutCtx ctx = _E_InitLayoutCtx(arena, scratch, &result); ctx.font = font; ctx.text_scale = 1.f; ctx.initial_padding = 4.f; ctx.space_dim = R_StringDimWithFont(font, ctx.text_scale, S8Lit(" ")); ctx.cl_whitespace = v4(V3Expand(cl_white), 1.f); ctx.cl_error = v4(V3Expand(cl_error), 1.f); _E_PushRuneLineStart(&ctx, 0); u64 index = 0; C_Token *token = buffer->tokens; C_Token *opl = token + buffer->count; for (; token < opl; token += 1, index += 1){ switch (token->kind){ default: { _E_PushRuneError(&ctx); }break; case C_TokenKind_Space: { _E_PushRuneSpace(&ctx); }break; case C_TokenKind_Newline: { b32 pre_indented_line = 0; if (token + 1 < opl && (token + 1)->kind == C_TokenKind_CloseParen){ pre_indented_line = 1; } _E_PushRuneLineStart(&ctx, pre_indented_line); }break; case C_TokenKind_Comment: { String8 string = STR_Read(string_hash, token->string); _E_PushRune(&ctx, v4(V3Expand(cl_comment), 1.f), string, E_RuneKind_Comment); }break; case C_TokenKind_OpenParen: { f32 before = ctx.p.x; _E_PushRune(&ctx, v4(V3Expand(cl_text), 1.f), S8Lit("("), E_RuneKind_Symbol); _E_PushIndent(&ctx, before - ctx.initial_padding, ctx.p.x - ctx.initial_padding); }break; case C_TokenKind_CloseParen: { _E_PushRune(&ctx, v4(V3Expand(cl_text), 1.f), S8Lit(")"), E_RuneKind_Symbol); _E_PopIndent(&ctx); }break; case C_TokenKind_Quote: { _E_PushRune(&ctx, v4(V3Expand(cl_text), 1.f), S8Lit("'"), E_RuneKind_Symbol); }break; case C_TokenKind_Label: { v3 color = cl_text; STR_Index token_string = token->string; STR_Index *keyword = vars->keyword_table; for (u64 i = 0; i < ArrayCount(vars->keyword_table); i += 1){ if (keyword[i] == token_string){ color = cl_keyword; break; } } String8 string = STR_Read(string_hash, token_string); _E_PushRune(&ctx, v4(V3Expand(color), 1.f), string, E_RuneKind_Label); }break; } } result.count = PushArray(arena, E_Rune, 0) - result.vals; OS_ReleaseScratch(scratch); return(result); } //////////////////////////////// // NOTE(allen): Editor State internal String8 E_LexemeOrDummy(E_EditorState *state, u64 pos){ Assert(pos <= state->buffer->count); String8 result = S8Lit(" "); if (pos > 0){ C_Token *token = state->buffer->tokens + pos - 1; if (token->kind == C_TokenKind_Comment || token->kind == C_TokenKind_Label){ result = STR_Read(APP_GetStringHash(), token->string); } } return(result); } #if 1 internal void _E_AssertEditorInvariants(E_EditorState *state){ E_Cursor *check[2]; check[0] = &state->cursor; check[1] = &state->mark; for (u64 i = 0; i < ArrayCount(check); i += 1){ Assert(check[i]->pos <= state->buffer->count); Assert(check[i]->sub_pos > 0); String8 lexeme = E_LexemeOrDummy(state, check[i]->pos); Assert(check[i]->sub_pos <= lexeme.size); } } #else #define _E_AssertEditorInvariants(s) #endif internal E_Cursor E_InitCursor(void){ E_Cursor result = {0, 1}; return(result); } internal b32 E_CursorEq(E_Cursor a, E_Cursor b){ return((a.pos == b.pos) && (a.sub_pos == b.sub_pos)); } internal void E_CursorSetToEndOfToken(E_EditorState *state, E_Cursor *cursor){ if (cursor->pos == 0){ cursor->sub_pos = 1; } else{ String8 lexeme = E_LexemeOrDummy(state, cursor->pos); cursor->sub_pos = lexeme.size; } } internal E_EditorState E_InitEditorState(E_TokenBuffer *buffer, R_Font *font){ E_EditorState result = {0}; result.buffer = buffer; result.font = font; result.cursor = result.mark = E_InitCursor(); if (buffer->text_field_mode){ result.text_field_mode = E_EditorFieldMode_AnyString; } _E_AssertEditorInvariants(&result); return(result); } internal void E_EditorSetFieldMode(E_EditorState *state, E_EditorFieldMode mode){ if (state->buffer->text_field_mode){ state->text_field_mode = mode; if (mode == E_EditorFieldMode_None){ state->text_field_mode = E_EditorFieldMode_AnyString; } } else{ state->text_field_mode = E_EditorFieldMode_None; } } internal E_Rune* E_GetRuneForPosition(E_EditorState *state, u64 pos){ E_Rune *result = 0; if (pos < state->runes.count){ result = state->runes.vals + pos; } return(result); } internal E_Rune* E_GetLineStartRune(E_EditorState *state, u64 p){ E_Rune *node = state->runes.first_line->next; for (; node != 0; node = node->next){ if (p < node->cursor_pos){ node = node->prev; break; } } if (node == 0){ node = state->runes.last_line; } return(node); } internal E_Rune* E_GetNearestRuneOnLine(E_EditorState *state, E_Rune *rune, f32 x){ Assert(rune->kind == E_RuneKind_LineStart); E_Rune *opl = rune->next; if (opl == 0){ opl = state->runes.vals + state->runes.count; } f32 best_dist = Inf32(); E_Rune *result = 0; for (;rune < opl; rune += 1){ f32 dist = 0.f; if (rune->kind == E_RuneKind_Label || rune->kind == E_RuneKind_Comment){ if (x < rune->rect.x0){ dist = rune->rect.x0 - x; } else if (x > rune->rect.x1){ dist = x - rune->rect.x1; } } else{ dist = AbsoluteValue(rune->rect.x1 - x); } if (dist < best_dist){ result = rune; best_dist = dist; } } return(result); } internal E_Cursor E_GetCursor(E_EditorState *state){ return(state->cursor); } internal E_Cursor E_GetMark(E_EditorState *state){ return(state->mark); } internal b32 E_HasRange(E_EditorState *state){ return(!E_CursorEq(state->cursor, state->mark)); } internal E_Range E_GetClosedRange(E_EditorState *state){ E_Range range = {0}; if (state->cursor.pos == state->mark.pos){ range.kind = E_RangeKind_SingleTokenTextRange; range.token = state->cursor.pos; range.range = MakeRangeu(state->cursor.sub_pos, state->mark.sub_pos); } else{ E_Cursor *min = 0; E_Cursor *max = 0; if (state->cursor.pos < state->mark.pos){ min = &state->cursor; max = &state->mark; } else{ min = &state->mark; max = &state->cursor; } String8 min_lexeme = E_LexemeOrDummy(state, min->pos); b32 min_is_right_side = (min->sub_pos >= min_lexeme.size); String8 max_lexeme = E_LexemeOrDummy(state, max->pos); b32 max_is_right_side = (max->sub_pos >= max_lexeme.size); if (min->pos + 1 == max->pos && min_is_right_side && !max_is_right_side){ range.kind = E_RangeKind_SingleTokenTextRange; range.token = max->pos; range.range = MakeRangeu(0, max->sub_pos); } else{ range.kind = E_RangeKind_MultiTokenRange; range.range.min = min->pos; if (range.range.min > 0 && !min_is_right_side){ range.range.min -= 1; } range.range.max = max->pos; } } return(range); } internal b32 E_HasRangeWithTokenGranularity(E_EditorState *state){ E_Range range = E_GetClosedRange(state); return(range.kind == E_RangeKind_MultiTokenRange); } internal E_Rune* E_GetRuneAtXY(E_EditorState *state, v2 p){ E_Rune *result = 0; E_RuneLayout *layout = &state->runes; for (E_Rune *node = layout->first_line; node != 0; node = node->next){ if (node == layout->last_line){ result = E_GetNearestRuneOnLine(state, node, p.x); break; } Range y_range = RectGetRange(node->rect, Dimension_Y); if (RangeContains(y_range, p.y)){ result = E_GetNearestRuneOnLine(state, node, p.x); break; } } return(result); } internal u64 E_GetNearestSubPosOnRune(E_EditorState *state, E_Rune *rune, f32 x){ u64 pos = 0; switch (rune->kind){ default: { pos = 1; }break; case E_RuneKind_Label: case E_RuneKind_Comment: { R_Font *font = state->font; String8 string = rune->string; f32 text_scale = rune->text_scale; f32 best_dist = Inf32(); f32 x0 = rune->rect.x0; for (u64 i = 0; i < string.size; i += 1){ v2 dim = R_StringDimWithFont(font, text_scale, S8(&string.str[i], 1)); x0 += dim.x; f32 dist = AbsoluteValue(x0 - x); if (dist < best_dist){ best_dist = dist; pos = i + 1; } } }break; } pos = ClampBot(1, pos); return(pos); } internal f32 E_GetCursorX(E_EditorState *state){ f32 x = 0.f; E_Rune *rune = E_GetRuneForPosition(state, state->cursor.pos); if (rune != 0){ switch (rune->kind){ default: { String8 string = StringPrefix(rune->string, state->cursor.sub_pos); v2 dim = R_StringDimWithFont(state->font, rune->text_scale, string); x = rune->rect.x0 + dim.x; }break; case E_RuneKind_Error: case E_RuneKind_Space: case E_RuneKind_LineStart: { x = rune->rect.x1; }break; } } return(x); } internal b32 E_OnTokenKind(E_EditorState *state, C_TokenKind kind){ b32 result = 0; if (state->cursor.pos > 0){ C_Token *token = state->buffer->tokens + state->cursor.pos - 1; if (token->kind == kind){ result = 1; } } return(result); } internal void E_EditorSetMark(E_EditorState *state, E_Cursor mark){ u64 token_count = state->buffer->count; mark.pos = ClampTop(mark.pos, token_count); u64 sub_pos = mark.sub_pos; E_CursorSetToEndOfToken(state, &mark); state->mark.pos = mark.pos; state->mark.sub_pos = Clamp(1, sub_pos, mark.sub_pos); } internal void E_EditorUpdatePreferredXToCursor(E_EditorState *state){ state->preferred_x = E_GetCursorX(state); } internal void E_EditorInsertCharacter(E_EditorState *state, u64 character){ b32 pass = 0; switch (state->text_field_mode){ case E_EditorFieldMode_None: case E_EditorFieldMode_AnyString: { pass = 1; }break; case E_EditorFieldMode_CodeIdentifier: { if (character != ' ' && character != ';' && character != '(' && character != ')' && character != '\''){ pass = 1; } }break; } if (!pass){ return; } M_Arena *scratch = OS_GetScratch(); STR_Hash *string_hash = APP_GetStringHash(); E_TokenBuffer *buffer = state->buffer; b32 modify_prev_token = 0; if (state->cursor.pos > 0){ C_Token *current_token = buffer->tokens + state->cursor.pos - 1; if (current_token != 0 && (current_token->kind == C_TokenKind_Comment || current_token->kind == C_TokenKind_Label)){ String8 string = STR_Read(string_hash, current_token->string); String8 pre_str = StringPrefix(string, state->cursor.sub_pos); String8 post_str = StringSkip(string, state->cursor.sub_pos); String8 new_string = PushStringF(scratch, "%.*s%c%.*s", StringExpand(pre_str), (u8)character, StringExpand(post_str)); C_Token new_token = E_MakeToken(current_token->kind, STR_Save(string_hash, new_string)); E_TokenBufferModify(buffer, state->cursor.pos - 1, new_token); modify_prev_token = 1; state->cursor.sub_pos += 1; } } b32 modify_next_token = 0; if (state->cursor.pos < buffer->count){ String8 lexeme = E_LexemeOrDummy(state, state->cursor.pos); if (state->cursor.sub_pos == lexeme.size){ C_Token *current_token = buffer->tokens + state->cursor.pos; if (current_token != 0 && (current_token->kind == C_TokenKind_Comment || current_token->kind == C_TokenKind_Label)){ String8 string = STR_Read(string_hash, current_token->string); String8 new_string = PushStringF(scratch, "%c%.*s", (u8)character, StringExpand(string)); C_Token new_token = E_MakeToken(current_token->kind, STR_Save(string_hash, new_string)); E_TokenBufferModify(buffer, state->cursor.pos, new_token); modify_prev_token = 1; state->cursor.pos += 1; state->cursor.sub_pos = 1; } } } if (!modify_prev_token && !modify_next_token){ String8 new_string = PushStringF(scratch, "%c", (u8)character); C_Token new_token = E_MakeToken(C_TokenKind_Label, STR_Save(string_hash, new_string)); E_TokenBufferInsert(buffer, state->cursor.pos, new_token); state->cursor.pos += 1; state->cursor.sub_pos = 1; } state->mark = state->cursor; OS_ReleaseScratch(scratch); _E_AssertEditorInvariants(state); } internal b32 E_EditorInsertTokenString(E_EditorState *state, C_TokenKind kind, String8 token_string){ b32 result = 0; STR_Hash *string_hash = APP_GetStringHash(); String8 lexeme = E_LexemeOrDummy(state, state->cursor.pos); u64 sub_pos = state->cursor.sub_pos; if (sub_pos < lexeme.size){ C_Token *token = state->buffer->tokens + state->cursor.pos - 1; String8 string = STR_Read(string_hash, token->string); C_Token split[3]; split[0].kind = token->kind; split[0].string = STR_Save(string_hash, StringPrefix(string, sub_pos)); split[1].kind = kind; split[1].string = STR_Save(string_hash, token_string); split[2].kind = token->kind; split[2].string = STR_Save(string_hash, StringSkip(string, sub_pos)); result = E_TokenBufferReplaceRange(state->buffer, MakeRangeu(state->cursor.pos - 1, state->cursor.pos), split, ArrayCount(split)); } else{ STR_Index string_indx = 0; if (token_string.size > 0){ string_indx = STR_Save(string_hash, token_string); } result = E_TokenBufferInsert(state->buffer, state->cursor.pos, E_MakeToken(kind, string_indx)); } state->cursor.pos += 1; state->cursor.sub_pos = 1; state->mark = state->cursor; _E_AssertEditorInvariants(state); return(result); } internal void E_EditorInsertSpace(E_EditorState *state){ b32 insert_into_comment = 0; if (state->cursor.pos > 0){ E_TokenBuffer *buffer = state->buffer; C_Token *current_token = buffer->tokens + state->cursor.pos - 1; if (current_token->kind == C_TokenKind_Comment){ E_EditorInsertCharacter(state, ' '); insert_into_comment = 1; } } if (!insert_into_comment){ E_EditorInsertTokenString(state, C_TokenKind_Space, S8Zero()); } } internal void E_EditorInsertComment(E_EditorState *state){ b32 insert_into_comment = 0; if (state->cursor.pos > 0){ E_TokenBuffer *buffer = state->buffer; C_Token *current_token = buffer->tokens + state->cursor.pos - 1; if (current_token->kind == C_TokenKind_Comment){ E_EditorInsertCharacter(state, ';'); insert_into_comment = 1; } } if (!insert_into_comment){ E_EditorInsertTokenString(state, C_TokenKind_Comment, S8Lit(";")); } } internal void E_EditorDelete(E_EditorState *state, Side side){ if (side == Side_Min && state->cursor.pos > 0){ E_TokenBufferDelete(state->buffer, state->cursor.pos - 1); state->cursor.pos -= 1; E_CursorSetToEndOfToken(state, &state->cursor); } else if (side == Side_Max && state->cursor.pos < state->buffer->count){ E_TokenBufferDelete(state->buffer, state->cursor.pos); } state->mark = state->cursor; _E_AssertEditorInvariants(state); } internal void E_EditorDeleteSmall(E_EditorState *state, Side side){ M_Arena *scratch = OS_GetScratch(); STR_Hash *string_hash = APP_GetStringHash(); E_TokenBuffer *buffer = state->buffer; C_Token *current_token = 0; String8 new_string = {0}; if (side == Side_Min && state->cursor.pos > 0){ current_token = buffer->tokens + state->cursor.pos - 1; if (current_token->kind == C_TokenKind_Label || current_token->kind == C_TokenKind_Comment){ String8 string = STR_Read(string_hash, current_token->string); if (string.size > 0){ String8 pre_str = StringPrefix(string, state->cursor.sub_pos); String8 post_str = StringSkip(string, state->cursor.sub_pos); pre_str = StringChop(pre_str, 1); new_string = PushStringF(scratch, "%.*s%.*s", StringExpand(pre_str), StringExpand(post_str)); state->cursor.sub_pos -= 1; } } } else if (side == Side_Max && state->cursor.pos <= buffer->count){ b32 delete_in_current_token = 0; if (state->cursor.pos > 0){ current_token = buffer->tokens + state->cursor.pos - 1; if (current_token->kind == C_TokenKind_Label || current_token->kind == C_TokenKind_Comment){ String8 string = STR_Read(string_hash, current_token->string); if (state->cursor.sub_pos < string.size){ String8 pre_str = StringPrefix(string, state->cursor.sub_pos); String8 post_str = StringSkip(string, state->cursor.sub_pos); post_str = StringSkip(post_str, 1); new_string = PushStringF(scratch, "%.*s%.*s", StringExpand(pre_str), StringExpand(post_str)); delete_in_current_token = 1; } } } if (!delete_in_current_token){ current_token = buffer->tokens + state->cursor.pos; if (current_token->kind == C_TokenKind_Label || current_token->kind == C_TokenKind_Comment){ new_string = STR_Read(string_hash, current_token->string); if (new_string.size > 0){ new_string = StringPostfix(new_string, new_string.size - 1); } } } } if (new_string.size != 0){ STR_Index new_string_indx = STR_Save(string_hash, new_string); current_token->string = new_string_indx; E_TokenBufferNotifyChange(buffer); } else{ E_EditorDelete(state, side); } if (state->cursor.sub_pos == 0){ if (state->cursor.pos > 0){ state->cursor.pos -= 1; E_CursorSetToEndOfToken(state, &state->cursor); } else{ state->cursor.pos = 1; } } state->mark = state->cursor; OS_ReleaseScratch(scratch); _E_AssertEditorInvariants(state); } internal void E_EditorDeleteRange(E_EditorState *state){ E_Range range = E_GetClosedRange(state); switch (range.kind){ case E_RangeKind_SingleTokenTextRange: { if (range.token > 0 && RangeSize(range.range) > 0){ M_Arena *scratch = OS_GetScratch(); STR_Hash *string_hash = APP_GetStringHash(); E_TokenBuffer *buffer = state->buffer; C_Token *current_token = buffer->tokens + range.token - 1; String8 string = STR_Read(string_hash, current_token->string); String8 pre = StringPrefix(string, range.range.min); String8 post = StringSkip(string, range.range.max); String8 new_string = PushStringCat(scratch, pre, post); STR_Index new_string_indx = STR_Save(string_hash, new_string); current_token->string = new_string_indx; E_TokenBufferNotifyChange(buffer); OS_ReleaseScratch(scratch); state->cursor.pos = range.token; state->cursor.sub_pos = range.range.min; if (state->cursor.sub_pos == 0){ if (state->cursor.pos > 0){ state->cursor.pos -= 1; } E_CursorSetToEndOfToken(state, &state->cursor); } state->mark = state->cursor; } }break; case E_RangeKind_MultiTokenRange: { E_TokenBufferReplaceRange(state->buffer, range.range, 0, 0); state->cursor.pos = range.range.min; E_CursorSetToEndOfToken(state, &state->cursor); state->mark = state->cursor; }break; } _E_AssertEditorInvariants(state); } internal b32 _E_EditorMove(E_EditorState *state, Side side){ b32 result = 0; if (side == Side_Min && state->cursor.pos > 0){ state->cursor.pos -= 1; E_CursorSetToEndOfToken(state, &state->cursor); result = 1; } else if (side == Side_Max && state->cursor.pos < state->buffer->count){ state->cursor.pos += 1; state->cursor.sub_pos = 1; result = 1; } _E_AssertEditorInvariants(state); return(result); } internal b32 E_EditorMoveSmall(E_EditorState *state, Side side){ E_Cursor original = state->cursor; if (side == Side_Min){ if (state->cursor.sub_pos > 1){ state->cursor.sub_pos -= 1; } else{ _E_EditorMove(state, Side_Min); } } else if (side == Side_Max && state->cursor.pos <= state->buffer->count){ b32 small_step = 0; if (state->cursor.pos > 0){ C_Token *token = state->buffer->tokens + state->cursor.pos - 1; String8 string = STR_Read(APP_GetStringHash(), token->string); if (state->cursor.sub_pos < string.size){ state->cursor.sub_pos += 1; small_step = 1; } } if (!small_step){ _E_EditorMove(state, Side_Max); } } state->mark = state->cursor; E_EditorUpdatePreferredXToCursor(state); _E_AssertEditorInvariants(state); return(!E_CursorEq(original, state->cursor)); } internal b32 E_EditorMove(E_EditorState *state, Side side){ E_Cursor original = state->cursor; _E_EditorMove(state, side); E_CursorSetToEndOfToken(state, &state->cursor); state->mark = state->cursor; E_EditorUpdatePreferredXToCursor(state); return(!E_CursorEq(original, state->cursor)); } internal b32 E_EditorMoveSkipWhitespace(E_EditorState *state, Side side){ E_Cursor original = state->cursor; for (;;){ if (!_E_EditorMove(state, side)){ break; } E_Rune *rune = E_GetRuneForPosition(state, state->cursor.pos); if (!(rune->kind == E_RuneKind_Space || rune->kind == E_RuneKind_LineStart)){ break; } } state->mark = state->cursor; E_EditorUpdatePreferredXToCursor(state); _E_AssertEditorInvariants(state); return(!E_CursorEq(original, state->cursor)); } internal b32 E_EditorMoveWholeLine(E_EditorState *state, Side side){ E_Cursor original = state->cursor; E_Rune *line = E_GetLineStartRune(state, state->cursor.pos); if (side == Side_Min){ state->cursor.pos = line->cursor_pos; state->cursor.sub_pos = 1; } else{ if (line->next != 0){ state->cursor.pos = line->next->cursor_pos - 1; } else{ state->cursor.pos = state->runes.count - 1; } E_CursorSetToEndOfToken(state, &state->cursor); } state->mark = state->cursor; E_EditorUpdatePreferredXToCursor(state); _E_AssertEditorInvariants(state); return(!E_CursorEq(original, state->cursor)); } internal b32 E_EditorMoveVertical(E_EditorState *state, Side side){ E_Cursor original = state->cursor; E_Rune *nearest = 0; E_Rune *line = E_GetLineStartRune(state, state->cursor.pos); if (side == Side_Min && line->prev != 0){ nearest = E_GetNearestRuneOnLine(state, line->prev, state->preferred_x); } else if (side == Side_Max && line->next != 0){ nearest = E_GetNearestRuneOnLine(state, line->next, state->preferred_x); } if (nearest != 0){ state->cursor.pos = nearest->cursor_pos; state->cursor.sub_pos = E_GetNearestSubPosOnRune(state, nearest, state->preferred_x); } state->mark = state->cursor; _E_AssertEditorInvariants(state); return(!E_CursorEq(original, state->cursor)); } internal b32 E_EditorMoveToXY(E_EditorState *state, v2 p){ b32 result = 0; E_Rune *rune = E_GetRuneAtXY(state, p); if (rune != 0){ result = 1; state->cursor.pos = rune->cursor_pos; state->cursor.sub_pos = E_GetNearestSubPosOnRune(state, rune, p.x); } state->preferred_x = p.x; state->mark = state->cursor; _E_AssertEditorInvariants(state); return(result); } internal void E_EditorEnterFrom(E_EditorState *state, Dimension dimension, Side side, f32 v){ if (dimension == Dimension_Y){ E_Rune *line = 0; if (side == Side_Min){ line = state->runes.first_line; } else if (side == Side_Max){ line = state->runes.last_line; } if (line != 0){ E_Rune *nearest = E_GetNearestRuneOnLine(state, line, v); if (nearest != 0){ state->cursor.pos = nearest->cursor_pos; state->cursor.sub_pos = E_GetNearestSubPosOnRune(state, nearest, v); } } state->mark = state->cursor; state->preferred_x = v; } else{ NotImplemented; } _E_AssertEditorInvariants(state); } internal void E_EditorUpdateLayout(E_EditorState *state, R_Font *font){ state->font = font; state->runes = E_LayoutTokenBuffer(APP_GetFrameArena(), font, state->buffer); _E_AssertEditorInvariants(state); } //////////////////////////////// // NOTE(allen): Input internal b32 E_EditorHandleCharacterEvent(E_EditorState *state, OS_Event *event){ b32 result = 0; if (E_HasRange(state)){ E_EditorDeleteRange(state); } u64 character = event->character; if (state->text_field_mode != E_EditorFieldMode_None){ E_EditorInsertCharacter(state, character); } else{ switch (character){ case ' ': { E_EditorInsertSpace(state); }break; case ';': { E_EditorInsertComment(state); }break; case '(': { E_EditorInsertTokenString(state, C_TokenKind_OpenParen, S8Zero()); }break; case ')': { E_EditorInsertTokenString(state, C_TokenKind_CloseParen, S8Zero()); }break; case '\'': { E_EditorInsertTokenString(state, C_TokenKind_Quote, S8Zero()); }break; default: { E_EditorInsertCharacter(state, character); }break; } } OS_EatEvent(event); result = 1; return(result); } internal b32 E_EditorHandleKeyPressEvent(E_EditorState *state, OS_Event *event){ i32 move_size = 0; if ((event->modifiers & KeyModifier_Ctrl) != 0){ move_size = 1; } if (E_HasRangeWithTokenGranularity(state)){ move_size = 1; } b32 selection = 0; if ((event->modifiers & KeyModifier_Shift) != 0){ selection = 1; } b32 used_event = 0; switch (event->key){ case Key_Enter: { if (state->text_field_mode == E_EditorFieldMode_None){ used_event = E_EditorInsertTokenString(state, C_TokenKind_Newline, S8Zero()); } }break; case Key_Backspace: case Key_Delete: { if (E_HasRange(state)){ E_EditorDeleteRange(state); } else{ Side side = Side_Min; if (event->key == Key_Delete){ side = Side_Max; } if (event->modifiers & KeyModifier_Alt){ E_EditorDelete(state, side); } else if (move_size == 0){ E_EditorDeleteSmall(state, side); } else if (move_size == 1){ E_Cursor mark = E_GetMark(state); E_Rune *rune = E_GetRuneForPosition(state, state->cursor.pos); if (!(rune->kind == E_RuneKind_Space || rune->kind == E_RuneKind_LineStart)){ if (side == Side_Max){ E_CursorSetToEndOfToken(state, &state->cursor); } else{ E_EditorMove(state, side); } } else{ E_EditorMoveSkipWhitespace(state, side); } E_EditorSetMark(state, mark); E_EditorDeleteRange(state); } used_event = 1; } }break; case Key_Left: case Key_Right: { Side side = Side_Min; if (event->key == Key_Right){ side = Side_Max; } E_Cursor mark = E_GetMark(state); if (move_size == 0){ used_event = E_EditorMoveSmall(state, side); } else if (move_size == 1){ used_event = E_EditorMove(state, side); } if (selection){ E_EditorSetMark(state, mark); } }break; case Key_Home: case Key_End: { Side side = Side_Min; if (event->key == Key_End){ side = Side_Max; } E_Cursor mark = E_GetMark(state); used_event = E_EditorMoveWholeLine(state, side); if (selection){ E_EditorSetMark(state, mark); } }break; case Key_Up: case Key_Down: { Side side = Side_Min; if (event->key == Key_Down){ side = Side_Max; } E_Cursor mark = E_GetMark(state); used_event = E_EditorMoveVertical(state, side); if (selection){ E_EditorSetMark(state, mark); } }break; } return(used_event); } internal b32 E_EditorHandleMousePressEvent(E_EditorState *state, OS_Event *event, v2 editor_to_screen){ b32 result = 0; b32 selection = 0; if ((event->modifiers & KeyModifier_Shift) != 0){ selection = 1; } v2 p_screen = event->position; v2 p_editor = V2Sub(p_screen, editor_to_screen); E_Cursor mark = E_GetMark(state); E_EditorMoveToXY(state, p_editor); if (selection){ E_EditorSetMark(state, mark); } OS_EatEvent(event); result = 1; return(result); } //////////////////////////////// // NOTE(allen): Render internal void E_EditorRender(E_EditorState *state, R_Font *font, v2 base_p){ #define EDITOR_DEBUG_VISOR 0 #if EDITOR_DEBUG_VISOR // NOTE(allen): Debug visor for editor { R_Rect(MakeRect(base_p.x + state->preferred_x, 0.f, base_p.x + state->preferred_x + 1.f, 10000.f), cl_awful, 1.f); } #endif b32 show_cursor = (APP_GetActiveEditor() == state); b32 show_range = (show_cursor && E_HasRange(state)); E_Range range = E_GetClosedRange(state); R_SelectFont(font); u64 rune_count = state->runes.count; E_Rune *rune = state->runes.vals; for (u64 i = 0; i < rune_count; i += 1, rune += 1){ v2 p0 = V2Add(rune->rect.p0, base_p); v2 p1 = V2Add(rune->rect.p1, base_p); v3 color = v3(V3Expand(rune->color)); f32 a = rune->color.a; if (show_range){ f32 y_highlight_top = Lerp(p0.y, 0.775f, p1.y); Range y_range = MakeRange(y_highlight_top, p1.y); switch (range.kind) { case E_RangeKind_SingleTokenTextRange: { if (rune->cursor_pos == range.token){ Range x_range; v2 dim = R_StringDim(rune->text_scale, StringPrefix(rune->string, range.range.min)); x_range.min = p0.x + dim.x; dim = R_StringDim(rune->text_scale, StringSubstring(rune->string, range.range)); x_range.max = x_range.min + dim.x; R_Rect(MakeRectRanges(x_range, y_range), color, a*0.5f); } }break; case E_RangeKind_MultiTokenRange: { if (RangeuContains(range.range, rune->cursor_pos - 1)){ Range x_range = MakeRange(p0.x, p1.x); R_Rect(MakeRectRanges(x_range, y_range), color, a*0.5f); } }break; } } R_String(p0, rune->text_scale, rune->string, color, a); if (show_cursor){ if (rune->cursor_pos == state->cursor.pos){ switch (rune->kind){ default: { R_Rect(MakeRect(p1.x, p0.y, p1.x + 1.f, p1.y), color, a); if (!show_range){ R_Rect(MakeRectVec(p0, p1), color, a*0.2f); } }break; case E_RuneKind_Label: case E_RuneKind_Comment: { if (state->cursor.sub_pos >= rune->string.size){ R_Rect(MakeRect(p1.x, p0.y, p1.x + 1.f, p1.y), color, a); } else{ String8 pre_string = StringPrefix(rune->string, state->cursor.sub_pos); f32 x = p0.x + R_StringDim(rune->text_scale, pre_string).x; R_Rect(MakeRect(x, p0.y, x + 1.f, p1.y), color, a); } if (!show_range){ R_Rect(MakeRectVec(p0, p1), color, a*0.2f); } }break; case E_RuneKind_Space: { R_Rect(MakeRect(p1.x, p0.y, p1.x + 1.f, p1.y), color, a); }break; } } } #if EDITOR_DEBUG_VISOR { if (rune->cursor_pos == state->mark.pos){ switch (rune->kind){ default: { R_Rect(MakeRect(p1.x, p0.y, p1.x + 1.f, p1.y), cl_green, 1.f); }break; case E_RuneKind_Label: case E_RuneKind_Comment: { if (state->mark.sub_pos >= rune->string.size){ R_Rect(MakeRect(p1.x, p0.y, p1.x + 1.f, p1.y), cl_green, 1.f); } else{ String8 pre_string = StringPrefix(rune->string, state->mark.sub_pos); f32 x = p0.x + R_StringDim(rune->text_scale, pre_string).x; R_Rect(MakeRect(x, p0.y, x + 1.f, p1.y), cl_green, a); } }break; } } } #endif } } //////////////////////////////// // NOTE(allen): Test internal void _E_TokenBuffer_Test(void){ E_TokenBuffer buffer = E_InitTokenBuffer(); C_Token t1[] = { {1, 2}, {2, 4}, {3, 6}, }; C_Token t2[] = { {100, 2}, {200, 8}, {300, 32}, {400, 64}, {800, 128}, }; for (u64 i = 0; i < ArrayCount(t1); i += 1){ u64 end = E_TokenBufferGetCount(&buffer); E_TokenBufferInsert(&buffer, end, t1[i]); } for (u64 i = 0; i < ArrayCount(t2); i += 1){ u64 end = E_TokenBufferGetCount(&buffer); E_TokenBufferInsert(&buffer, end, t2[i]); } C_Token *r1 = E_TokenBufferReadRange(&buffer, MakeRangeu(0, 3)); Assert(MemoryMatch(r1, t1, sizeof(t1))); C_Token *r2 = E_TokenBufferReadRange(&buffer, MakeRangeu(3, 8)); Assert(MemoryMatch(r2, t2, sizeof(t2))); for (u64 i = 0; i < ArrayCount(t1); i += 1){ E_TokenBufferDelete(&buffer, 0); } r2 = E_TokenBufferReadRange(&buffer, MakeRangeu(0, 5)); Assert(MemoryMatch(r2, t2, sizeof(t2))); for (u64 i = 0; i < ArrayCount(t1); i += 1){ E_TokenBufferModify(&buffer, i, t1[i]); } r1 = E_TokenBufferReadRange(&buffer, MakeRangeu(0, 3)); Assert(MemoryMatch(r1, t1, sizeof(t1))); r2 = E_TokenBufferReadRange(&buffer, MakeRangeu(3, 5)); Assert(MemoryMatch(r2, t2 + 3, 2*sizeof(*r2))); }