////////////////////////////////
//~ sixten: Mutable String Functions

static mutable_string MutableStringAllocate(u64 Size)
{
    mutable_string Result = {};
    Result.Arena = ArenaAllocate(Size);
    ArenaSetAlign(Result.Arena, 1);
    Result.String = MakeString(PushArray(Result.Arena, u8, 1), 0LL);
    return(Result);
}

static void MutableStringRelease(mutable_string *String)
{
    ArenaRelease(String->Arena);
}

static void MutableStringReplaceRange(mutable_string *MutString, string ReplaceString, range1_s64 Range)
{
    Range = Intersection(Range, Range1S64(0, MutString->String.Count));
    
    s64 NewCount = MutString->String.Count+ReplaceString.Count-DimOfRange(Range);
    if(NewCount > MutString->String.Count)
    {
        s64 ToAllocate = NewCount-MutString->String.Count;
        PushArrayNoClear(MutString->Arena, u8, ToAllocate);
    }
    else if(NewCount < MutString->String.Count)
    {
        ArenaPopTo(MutString->Arena, sizeof(memory_arena)+NewCount+1);
    }
    
    Move(MutString->String.Data+Range.Min+ReplaceString.Count, MutString->String.Data+Range.Max, MutString->String.Count-Range.Min-DimOfRange(Range));
    Copy(MutString->String.Data+Range.Min, ReplaceString.Data, ReplaceString.Count);
    
    MutString->String.Count = NewCount;
    MutString->String.Data[NewCount] = 0;
}

////////////////////////////////
//~ sixten: History & Undo Functions

static history_entry HistoryEntry(memory_arena *Arena, string ReplaceString, range1_s64 Range)
{
    // sixten(TODO): proper memory management, right now we just keep appending to the arena, which works but never frees any memory
    history_entry Entry = {};
    Entry.ReplaceString = PushString(Arena, ReplaceString);
    Entry.Range = Range;
    return(Entry);
}

static void AppendToHistory(memory_arena *Arena, history_list *List, history_entry Forward, history_entry Backward)
{
    // sixten(TODO): proper memory management, right now we just keep appending to the arena, which works but never frees any memory
    history_node *Node = PushStructNoClear(Arena, history_node);
    Node->Forward = Forward;
    Node->Backward = Backward;
    
    SenDLLInsertLast(&List->Sentinel, Node);
}

////////////////////////////////
//~ sixten: Workspace Text Editing Functions

static workspace_text_data W_TextDataFromString(memory_arena *Arena, string Text)
{
    temporary_memory Scratch = GetScratch(&Arena, 1);
    
    //- sixten: clear the target arena
    ArenaClear(Arena);
    
    //- sixten: tokenize the text
    tokenize_result TokenizeResult = T_TokenizeFromText(Arena, Text);
    token_array Tokens = TokenizeResult.Tokens;
    
    //- sixten: gather all line ranges
    range1_s64_list Lines = {};
    {
        u8 *TextBegin = Text.Data;
        u8 *TextEnd = TextBegin + Text.Count;
        range1_s64 Range = Range1S64(0, 0);
        for(u8 *Char = TextBegin; Char <= TextEnd; Char += 1)
        {
            Range.Max += 1;
            
            //- sixten: push line range on newline and EOF
            if(Char == TextEnd || *Char == '\n')
            {
                Range1S64ListPush(Scratch.Arena, &Lines, Range);
                Range = Range1S64(Range.Max, Range.Max);
            }
        }
    }
    
    //- sixten: fill & return
    workspace_text_data Result = {};
    {
        Result.Tokens = Tokens;
        Result.Lines = Range1S64ArrayFromList(Arena, &Lines);;
    }
    ReleaseScratch(Scratch);
    return(Result);
}

static void W_TextEditorApplyChanges(workspace_view_text_editor *Editor)
{
    workspace_text_data TextData = W_TextDataFromString(Editor->ProcessingArena, Editor->Text.String);
    Editor->Tokens = TextData.Tokens;
    Editor->Lines = TextData.Lines;
    Editor->Compiled = S_ScriptFromText(Editor->ProcessingArena, Editor->Text.String);
    if(Editor->Compiled.IsValid)
    {
        SV_SetCurrentSource(&Editor->Compiled);
    }
}

static void W_SaveTextEditorToFile(workspace_view_text_editor *Editor)
{
    temporary_memory Scratch = GetScratch();
    
    if(Editor->SavePoint != Editor->History.At)
    {
        string Path = PushFormat(Scratch.Arena, "%S/%S", Editor->FilePath, Editor->FileName);
        platform_file_handle Handle = Platform.OpenFile(Path, PlatformAccess_Write);
        if(Handle.IsValid)
        {
            Platform.WriteFile(Handle, Editor->Text.String.Data, 0, Editor->Text.String.Count);
            Platform.CloseFile(Handle);
        }
        
        Editor->SavePoint = Editor->History.At;
    }
    ReleaseScratch(Scratch);
}

////////////////////////////////
//~ sixten: Workspace Text Editor Builder Functions

static UI_CUSTOM_DRAW_CALLBACK(W_TextEditorDrawCallback)
{
    temporary_memory Scratch = GetScratch();
    workspace_view_text_editor *Editor = (workspace_view_text_editor *)Data;
    
    //- sixten: get dimensions & scroll offset from container
    ui_box *ContainerBox = Editor->ContainerBox;
    range2_r32 ParentRect = ContainerBox->Rect;
    v2 ParentDim = DimOfRange(ParentRect);
    v2 Offset = Box->Parent->Offset;
    
    //- sixten: rendering properties
    r32 FontSize = Editor->FontSize;
    r32 LineHeight = FontSize + 4.0f;
    
    //- sixten: calculate the dimensions of the glyphs
    glyph *Glyph = GetGlyph(Atlas, Font_Monospace, 'A', FontSize, 0);
    r32 GlyphAdvance = Glyph->Advance;
    
    //- sixten: find the text point
    text_point CursorTextP = TextPointFromOffset(Editor->Text.String, Editor->EditState.Cursor);
    text_point MarkTextP = TextPointFromOffset(Editor->Text.String, Editor->EditState.Mark);
    
    //- sixten: get the line count
    range1_s64_array *Lines = &Editor->Lines;
    s64 LineCount = Lines->Count;
    
    //- sixten: calculate the text dim
    Editor->TextDim = V2(1900, LineCount*LineHeight);
    
    //- sixten: calculate the line margin dim
    s32 LineMarginDigitsRequired = 6;
    v2_r32 LineMarginDim = V2((LineMarginDigitsRequired)*GlyphAdvance, ParentRect.Max.y - ParentRect.Min.y);
    
    //- sixten: tokenize text
    tokenize_result TokenizeResult = T_TokenizeFromText(Scratch.Arena, Editor->Text.String);
    token_array Tokens = TokenizeResult.Tokens;
    token *TokensBegin = Tokens.Tokens;
    token *TokensEnd = TokensBegin + Tokens.Count;
    
    //- sixten: find the first visible token
    token *VisibleTokensBegin = TokensBegin;
    s64 TopMostLine = Min((s64)Floor(-Offset.y / LineHeight), LineCount);
    for(s64 LinesFound = 0; LinesFound < TopMostLine && VisibleTokensBegin < TokensEnd; VisibleTokensBegin += 1)
    {
        if(VisibleTokensBegin->Kind == TokenKind_Newline)
        {
            LinesFound += 1;
        }
    }
    
    //- sixten: find the last visible token
    token *VisibleTokensEnd = VisibleTokensBegin;
    s64 LinesOnScreen = Min((s64)Floor(ParentDim.y / LineHeight)+1, LineCount-TopMostLine);
    for(s64 LinesFound = 0; LinesFound < LinesOnScreen && VisibleTokensEnd < TokensEnd; VisibleTokensEnd += 1)
    {
        if(VisibleTokensEnd->Kind == TokenKind_Newline)
        {
            LinesFound += 1;
        }
    }
    
    //- sixten: draw line numbers & line highlights
    {
        //- sixten: draw the background
        v4 LineMarginColor = ColorFromHex(0x10203080);
        range2_r32 LineMarginBox = Range2R32(ParentRect.Min, ParentRect.Min+LineMarginDim);
        PushQuad(Group, LineMarginBox, LineMarginColor, LineMarginColor, LineMarginColor, LineMarginColor, 0, 0, 0);
        
        //- sixten: draw the numbers
        v2_r32 LineOffset = Box->Rect.Min;
        for(s64 LineIndex = TopMostLine; LineIndex < TopMostLine + LinesOnScreen; LineIndex += 1)
        {
            r32 LineY = LineOffset.y + LineIndex*LineHeight;
            PushTextF(Group, Atlas, Font_Monospace, V2(0, LineY), FontSize, Color_Grey, "%*.i", LineMarginDigitsRequired, LineIndex+1);
            
            if(LineIndex + 1 == CursorTextP.Line)
            {
                v4_r32 LineHighlightColor = ColorFromHex(0x10204080);
                range2_r32 LineHighlightBox = Range2R32(V2(LineMarginBox.Max.x, LineY), V2(Box->Rect.Max.x, LineY+LineHeight));
                PushQuad(Group, LineHighlightBox, LineHighlightColor, LineHighlightColor, LineHighlightColor, LineHighlightColor, 0, 0, 0);
            }
        }
    }
    
    //- sixten: render tokens
    v2 BaseTokenP = Box->Rect.Min+V2(LineMarginDim.x, TopMostLine*LineHeight);
    v2 TokenP = BaseTokenP;
    for(token *Token = VisibleTokensBegin; Token < VisibleTokensEnd; Token += 1)
    {
        string TokenString = T_StringFromToken(Editor->Text.String, *Token);
        
        //- sixten: get color & font from token
        font_id Font = Font_Monospace;
        v4 Color = Color_Magenta;
        if(Token->Kind == TokenKind_Comment) { Color = Color_Grey; Font = Font_MonospaceOblique; }
        else if(Token->Kind > TokenKind_SymbolsBegin && Token->Kind < TokenKind_SymbolsEnd) { Color = Color_Grey; }
        else if(Token->Kind == TokenKind_StringLiteral) { Color = ColorFromHex(0xffa900ff); }
        else if(Token->Kind == TokenKind_Numeric) { Color = ColorFromHex(0xffa900ff); }
        else if(Token->Kind > TokenKind_KeywordsBegin && Token->Kind < TokenKind_KeywordsEnd)
        {
            if(Token->Kind == TokenKind_True || Token->Kind == TokenKind_False)
            {
                Color = ColorFromHex(0xffa900ff);
            }
            else
            {
                Color = ColorFromHex(0xf0c674ff);
            }
        }
        else if(Token->Kind == TokenKind_Identifier)
        {
            Color = Theme_TextColor;
        }
        
        //- sixten: check for errors
        b32 ConsideredError = false;
        for(scene_compile_error *Error = Editor->Compiled.Errors.First; Error != 0; Error = Error->Next)
        {
            if(Error->Token.Range.Min == Token->Range.Min &&
               Error->Token.Range.Max == Token->Range.Max)
            {
                ConsideredError = true;
                break;
            }
        }
        
        //- sixten: render & advance by token
        if(!(T_IsWhitespace(Token->Kind)))
        {
            if(Token->Kind == TokenKind_Comment)
            {
                //- sixten: advance to newline and push text
                // sixten(TODO): proper multiline comment rendering.
                u8 *TextBegin = TokenString.Data;
                u8 *TextEnd = TextBegin+TokenString.Count;
                u8 *Marker = TextBegin;
                for(u8 *Byte = TextBegin; Byte <= TextEnd; Byte += 1)
                {
                    if(*Byte == '\n' || Byte == TextEnd)
                    {
                        PushText(Group, Atlas, Font, TokenP, FontSize, Color, MakeString(Marker, Byte-Marker));
                        Marker = Byte+1;
                        
                        if(*Byte == '\n' && Byte != TextEnd)
                        {
                            TokenP.x = BaseTokenP.x;
                            TokenP.y += LineHeight;
                        }
                    }
                }
            }
            else
            {
                r32 TokenWidth = PushText(Group, Atlas, Font, TokenP, FontSize, Color, TokenString);
                
                //- sixten: render error highlight
                if(ConsideredError)
                {
                    range2_r32 Dest = Range2R32(TokenP+V2R32(0, LineHeight-3), TokenP+V2R32(TokenWidth, LineHeight));
                    v4_r32 ErrorColor = V4R32(0.9f, 0.3f, 0.3f, 0.8f);
                    PushQuad(Group, Dest, ErrorColor, ErrorColor, ErrorColor, ErrorColor, 3, 0.4, 0);
                }
                TokenP.x += TokenWidth;
            }
        }
        else
        {
            if(Token->Kind == TokenKind_Newline)
            {
                TokenP.x = BaseTokenP.x;
                TokenP.y += LineHeight;
            }
            else
            {
                u8 *StringBegin = TokenString.Data;
                u8 *StringEnd = StringBegin + TokenString.Count;
                for(u8 *Char = StringBegin; Char < StringEnd; Char += 1)
                {
                    if(*Char == ' ' || *Char == '\t')
                    {
                        TokenP.x += GlyphAdvance;
                    }
                }
            }
        }
    }
    
    //- sixten: render cursor
    {
        s64 LineIndex = CursorTextP.Line-1;
        string Line = Substring(Editor->Text.String, Editor->Lines.Ranges[LineIndex]);
        s64 ColumnIndex = CursorTextP.Column-1;
        s64 ColumnOffset = UTF8OffsetFromIndex(Line, ColumnIndex);
        
        v2 TargetCursorP = Box->Rect.Min+V2(LineMarginDim.x+ColumnOffset*GlyphAdvance, LineIndex*LineHeight);
        v2 CursorP = V2(AC_AnimateValueF(TargetCursorP.x, TargetCursorP.x, 0.1, "Workspace Text Editor Cursor X %p", Editor),
                        AC_AnimateValueF(TargetCursorP.y, TargetCursorP.y, 0.1, "Workspace Text Editor Cursor Y %p", Editor));
        v2 CursorDim = V2(2, LineHeight);
        range2_r32 CursorRect = Range2R32(CursorP, CursorP+CursorDim);
        v4 CursorColor = ColorFromHex(0x10FF20FF);
        PushQuad(Group, CursorRect, CursorColor, CursorColor, CursorColor, CursorColor, 2, 0.4, 0);
    }
    
    //- sixten: render the selection
    {
        text_range Selection = TextRange(CursorTextP, MarkTextP);
        range1_s64 LineRange = Range1S64(Selection.Min.Line, Selection.Max.Line);
        for(s64 LineIndex = TopMostLine; LineIndex < TopMostLine + LinesOnScreen; LineIndex += 1)
        {
            if(Contains(LineRange, LineIndex + 1))
            {
                range1_s64 ColumnRange = Lines->Ranges[LineIndex];
                range1_s64 NormalizedColumnRange = Range1S64(0, DimOfRange(ColumnRange));
                if(LineIndex+1 == LineRange.Min && LineIndex+1 == LineRange.Max)
                {
                    NormalizedColumnRange = Range1S64(Editor->EditState.Cursor - ColumnRange.Min, Editor->EditState.Mark - ColumnRange.Min);
                }
                else if(LineIndex+1 == LineRange.Min)
                {
                    NormalizedColumnRange = Range1S64(Min(Editor->EditState.Mark, Editor->EditState.Cursor) - ColumnRange.Min, DimOfRange(ColumnRange));
                }
                else if(LineIndex+1 == LineRange.Max)
                {
                    NormalizedColumnRange = Range1S64(0, Max(Editor->EditState.Mark, Editor->EditState.Cursor) - ColumnRange.Min);
                }
                
                string Line = Substring(Editor->Text.String, ColumnRange);
                range1_s64 ColumnOffsetRange = Range1S64(UTF8OffsetFromIndex(Line, NormalizedColumnRange.Min),
                                                         UTF8OffsetFromIndex(Line, NormalizedColumnRange.Max));
                
                r32 LineY = LineIndex*LineHeight;
                v4_r32 LineHighlightColor = ColorFromHex(0x66B3CC4C);
                range2_r32 LineHighlightBox = Range2R32(Box->Rect.Min+V2(LineMarginDim.x+ColumnOffsetRange.Min*GlyphAdvance, LineY),
                                                        Box->Rect.Min+V2(LineMarginDim.x+ColumnOffsetRange.Max*GlyphAdvance, LineY+LineHeight));
                PushQuad(Group, LineHighlightBox, LineHighlightColor, LineHighlightColor, LineHighlightColor, LineHighlightColor, 4, 1.4, 0);
            }
        }
    }
    ReleaseScratch(Scratch);
}
UI_CUSTOM_DRAW_CALLBACK(W_TextEditorListerInputCallback)
{
    workspace_view_text_editor *Editor = (workspace_view_text_editor *)Data;
    s64 ClampedCursor = Clamp(0, Box->String.Count, Editor->ListerInputEditState.Cursor);
    s64 ClampedMark = Clamp(0, Box->String.Count, Editor->ListerInputEditState.Mark);
    string ToCursor = MakeString(Box->String.Data, ClampedCursor);
    string ToMark = MakeString(Box->String.Data, ClampedMark);
    
    r32 TargetCursorX = CalculateRasterizedTextWidth(Atlas, Box->Font, Box->FontSize, ToCursor);
    r32 TargetMarkerX = CalculateRasterizedTextWidth(Atlas, Box->Font, Box->FontSize, ToMark);
    
    r32 CursorX = AC_AnimateValueF(TargetCursorX, 0, 0.175, "Workspace View Input Cursor %p", Box);
    r32 MarkerX = AC_AnimateValueF(TargetMarkerX, 0, 0.175, "Workspace View Input Mark %p", Box);
    
    v2 BoxDim = DimOfRange(Box->Rect);
    
    // sixten: Draw selection
    {
        v2 Offset = V2(7.5, (BoxDim.y - Box->FontSize) / 2);
        v2 Dim = V2(0, Box->FontSize);
        if(CursorX > MarkerX)
        {
            Offset.x += MarkerX;
            Dim.x = CursorX - MarkerX;
        }
        else
        {
            Offset.x += CursorX;
            Dim.x = MarkerX - CursorX;
        }
        
        v2 P = Box->Rect.Min + Offset;
        v4 Color = V4(0.4, 0.7, 0.8, 0.3);
        PushQuad(Group, Range2R32(P, P+Dim), Color, 0, 0, 0);
    }
    
    // sixten: Draw cursor
    {
        range1_r32 CursorSpan = Range1R32(CursorX, TargetCursorX);
        r32 Height = Box->FontSize + 4;
        v2 Offset = V2(7.5F + CursorSpan.Min, (BoxDim.y - Height) / 2);
        v2 Dim = V2(1.25F + CursorSpan.Max - CursorSpan.Min, Height);
        
        v2 P = Box->Rect.Min + Offset;
        v4 Color = V4(0.3, 1, 0.3, 0.7);
        PushQuad(Group, Range2R32(P, P+Dim), Color, 0, 0, 0);
    }
}

static b32 W_BuildTextEditorListerItem(string Text, u32 Icon)
{
    b32 Result = false;
    UI_SetNextLayoutAxis(Axis2_X);
    UI_SetNextHoverCursor(PlatformCursor_Hand);
    ui_box *Container = UI_MakeBoxF(UI_BoxFlag_DrawBorder     |
                                    UI_BoxFlag_DrawBackground |
                                    UI_BoxFlag_Clickable      |
                                    UI_BoxFlag_HotAnimation   |
                                    UI_BoxFlag_ActiveAnimation|
                                    UI_BoxFlag_DrawDropShadow,
                                    "File Lister %S", Text);
    UI_Parent(Container)
    {
        UI_Width(UI_Em(2, 1)) UI_Font(Font_Icons) UI_LabelF("%U", Icon);
        UI_Width(UI_TextContent(0, 1)) UI_Label(Text);
    }
    
    ui_signal Signal = UI_SignalFromBox(Container);
    if(Signal.Clicked)
    {
        Result = true;
    }
    return(Result);
}

static workspace_text_editor_lister_action W_BuildTextEditorLister(workspace_view *View, workspace_view_text_editor *Editor)
{
    workspace_text_editor_lister_action ListerAction = {};
    temporary_memory Scratch = GetScratch();
    UI_Size(UI_Percent(1, 1), UI_Percent(1, 1)) UI_Scroll(0, &Editor->ListerScroll)
    {
        UI_Height(UI_Em(2, 1))
        {
            //- sixten: filename input field
            if(W_ViewIsCurrent(View))
            {
                for(platform_event *Event = UI_EventList()->First; Event != 0; Event = Event->Next)
                {
                    if((Event->Type == PlatformEvent_Press || Event->Type == PlatformEvent_Text) && (Event->Codepoint != '/' && Event->Codepoint != '\\'))
                    {
                        text_action Action = SingleLineTextActionFromEvent(Event);
                        if(IsValid(&Action))
                        {
                            text_op Op = TextOpFromAction(Scratch.Arena, MakeString(Editor->ListerInput, Editor->ListerInputUsed), &Editor->ListerInputEditState, &Action);
                            
                            string Left = MakeString(Editor->ListerInput, Op.Range.Min);
                            string Right = MakeString(Editor->ListerInput + Op.Range.Max, Editor->ListerInputUsed - Op.Range.Max);
                            
                            u64 NewStringSize = Left.Count + Right.Count + Op.ReplaceString.Count;
                            char *NewString = PushArray(Scratch.Arena, char, NewStringSize);
                            Copy(NewString, Left.Data, Left.Count);
                            Copy(NewString + Left.Count, Op.ReplaceString.Data, Op.ReplaceString.Count);
                            Copy(NewString + Left.Count + Op.ReplaceString.Count, Right.Data, Right.Count);
                            
                            Editor->ListerInputUsed = Minimum(ArrayCount(Editor->ListerInput), NewStringSize);
                            Copy(Editor->ListerInput, NewString, Editor->ListerInputUsed);
                            
                            Editor->ListerInputEditState.Cursor = Minimum(Op.NewCursor, Editor->ListerInputUsed);
                            Editor->ListerInputEditState.Mark = Minimum(Op.NewMark, Editor->ListerInputUsed);
                        }
                    }
                }
            }
            
            //- sixten: build navbar
            UI_Row(UI_BoxFlag_DrawBorder)
            {
                UI_SetNextBackgroundColor(ColorFromHex(0x2D5790FF));
                UI_SetNextWidth(UI_TextContent(20, 1));
                UI_MakeBox(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawText, Editor->Path);
                
                UI_SetNextWidth(UI_TextContent(15, 1));
                ui_box *InputBox = UI_MakeBox(UI_BoxFlag_DrawText, StrLit("Text Editor Lister Input"));
                UI_EquipBoxText(InputBox, MakeString(Editor->ListerInput, Editor->ListerInputUsed));
                UI_EquipBoxCustomDrawCallback(InputBox, W_TextEditorListerInputCallback, Editor);
                
                UI_Padding(UI_Percent(1, 0));
                
                UI_SetNextWidth(UI_TextContent(20, 1));
                if(UI_ButtonF("Open/Create").Clicked || Platform_KeyPress(UI_EventList(), Key_Return))
                {
                    ListerAction.HasRequestedFile = true;
                    ListerAction.Name = MakeString(Editor->ListerInput, Editor->ListerInputUsed);
                    ListerAction.Path = Editor->Path;
                }
            }
            
            //- sixten: display "parent directory button" 
            s64 LastSlash = LastIndexOf(Editor->Path, '/');
            if(LastSlash != -1)
            {
                if(W_BuildTextEditorListerItem(StrLit("Parent Directory"), FontIcon_Reply))
                {
                    Editor->Path = Prefix(Editor->Path, LastSlash);
                    Editor->ListerInputUsed = 0;
                }
            }
            platform_file_info FileInfo;
            platform_file_iter *FileIter;
            
            string Name = MakeString(Editor->ListerInput, Editor->ListerInputUsed);
            string FullPath = PushFormat(Scratch.Arena, "%S/%S", Editor->Path, Name);
            
            //- sixten: display directories
            {
                FileIter = Platform.BeginFileIter(Scratch.Arena, FullPath);
                for(;Platform.AdvanceFileIter(Scratch.Arena, FileIter, &FileInfo);)
                {
                    if(FileInfo.IsDirectory)
                    {
                        if(W_BuildTextEditorListerItem(FileInfo.Name, FontIcon_Folder))
                        {
                            Editor->Path = PushFormat(View->Arena, "%S/%S", Editor->Path, FileInfo.Name);
                            Editor->ListerInputUsed = 0;
                        }
                    }
                }
            }
            Platform.EndFileIter(FileIter);
            
            //- sixten: display files
            {
                FileIter = Platform.BeginFileIter(Scratch.Arena, FullPath);
                for(;Platform.AdvanceFileIter(Scratch.Arena, FileIter, &FileInfo);)
                {
                    if(!FileInfo.IsDirectory)
                    {
                        if(W_BuildTextEditorListerItem(FileInfo.Name, FontIcon_Document))
                        {
                            ListerAction.HasRequestedFile = true;
                            ListerAction.Name = PushString(View->Arena, FileInfo.Name);
                            ListerAction.Path = Editor->Path;
                        }
                    }
                }
            }
            Platform.EndFileIter(FileIter);
        }
    }
    ReleaseScratch(Scratch);
    return(ListerAction);
}

static b32 W_ProcessTextEditorEvent(workspace_view_text_editor *Editor, platform_event *Event)
{
    b32 CursorHasBeenModified = false;
    temporary_memory Scratch = GetScratch();
    text_action Action = MultiLineTextActionFromEvent(Event);
    if(IsValid(&Action))
    {
        text_op Op = TextOpFromAction(Scratch.Arena, Editor->Text.String, &Editor->EditState, &Action, &Editor->Lines, Editor->LastTextPoint.Column - 1);
        
        if(DimOfRange(Op.Range) != 0 || !AreEqual(StrLit(""), Op.ReplaceString))
        {
            //- sixten: append to the history
            {
                history_list *List = &Editor->History;
                
                //- sixten: remove the pre-existing history if needed
                if(List->Sentinel.Prev != List->At)
                {
                    // sixten(TODO): instead of just removing the links to the old memory, find some way to manage it.
                    List->Sentinel.Prev->Next = List->At;
                    List->Sentinel.Prev = List->At;
                }
                
                range1_s64 Selection = Range1S64(Editor->EditState.Cursor, Editor->EditState.Mark);
                AppendToHistory(Editor->HistoryArena, List,
                                HistoryEntry(Editor->HistoryArena, Op.ReplaceString, Op.Range),
                                HistoryEntry(Editor->HistoryArena, Substring(Editor->Text.String, Op.Range), Range1S64(Op.Range.Min, Op.Range.Min+Op.ReplaceString.Count)));
                
                List->At = List->Sentinel.Prev;
            }
            //- sixten: apply the text action
            string ReplaceString = RemoveAll(Scratch.Arena, Op.ReplaceString, '\r');
            MutableStringReplaceRange(&Editor->Text, ReplaceString, Op.Range);
            W_TextEditorApplyChanges(Editor);
        }
        
        CursorHasBeenModified = true;
        Editor->EditState.Cursor = Op.NewCursor;
        Editor->EditState.Mark = Op.NewMark;
    }
    ReleaseScratch(Scratch);
    return(CursorHasBeenModified);
}

static void W_BuildTextEditorInfoBar(workspace_view_text_editor *Editor)
{
    UI_SetNextLayoutAxis(Axis2_X);
    UI_WidthFill UI_Height(UI_Em(1.75f, 1)) UI_Parent(UI_MakeBoxF(UI_BoxFlag_DrawDropShadow, "")) UI_Padding(UI_Em(1, 1))
    {
        UI_Width(UI_TextContent(0, 1))
        {
            UI_Font(Font_Icons) UI_LabelF("%U", FontIcon_Attention);
            UI_Spacer(UI_Em(0.5f, 1));
            UI_LabelF("%i", Editor->Compiled.Errors.Count);
        }
    }
}

static void W_BuildTextEditor(workspace_view *View)
{
    workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data;
    
    temporary_memory Scratch = GetScratch();
    
    //- sixten: rendering properties
    r32 FontSize = Editor->FontSize = 13.0f;
    r32 LineHeight = FontSize + 4.0f;
    
    //- sixten: calculate the dimensions of the glyphs
    glyph *Glyph = GetGlyph(UI_GlyphAtlas(), Font_Monospace, 'A', FontSize, 0);
    r32 GlyphAdvance = Glyph->Advance;
    
    //- sixten: calculate the line margin dim
    s32 LineMarginDigitsRequired = 6;
    r32 LineMarginWidth = (LineMarginDigitsRequired)*GlyphAdvance;
    
    
    b32 InFileListMode = AreEqual(Editor->FileName, StrLit(""));
    if(InFileListMode)
    {
        //- sixten: build & handle file lister
        workspace_text_editor_lister_action Action = W_BuildTextEditorLister(View, Editor);
        if(Action.HasRequestedFile)
        {
            //- sixten: try to load file
            string FullPath = PushFormat(Scratch.Arena, "%S/%S", Action.Path, Action.Name);
            platform_file_handle File = Platform.OpenFile(FullPath, PlatformAccess_Read);
            
            if(File.IsValid)
            {
                s64 FileSize = Platform.GetFileSize(File);
                string ReplaceString = MakeString(PushArray(Scratch.Arena, u8, FileSize+1), FileSize);
                Platform.ReadFile(File, ReplaceString.Data, 0, ReplaceString.Count);
                
                ReplaceString = RemoveAll(Scratch.Arena, ReplaceString, '\r');
                
                MutableStringReplaceRange(&Editor->Text, ReplaceString, Range1S64(0, 0));
                W_TextEditorApplyChanges(Editor);
                Editor->FileName = Action.Name;
                Editor->FilePath = Action.Path;
                
                Platform.CloseFile(File);
            }
            else
            {
                Editor->FileName = Action.Name;
                Editor->FilePath = Action.Path;
            }
        }
    }
    else
    {
        //- sixten: build & handle the text editor
        ui_box *EditorBox = 0;
        UI_SetNextSize(UI_Percent(1, 1), UI_Percent(1, 0));
        UI_Scroll(0, &Editor->Offset.y)
        {
            //- sixten: find the container box for the scrollable region
            Editor->ContainerBox = UI_TopParent()->Parent->Parent;
            
            UI_SetNextSize(UI_Pixels(Editor->TextDim.x, 1), UI_Pixels(Editor->TextDim.y, 1));
            EditorBox = UI_MakeBoxF(UI_BoxFlag_DrawBackground|UI_BoxFlag_Clickable, "Workspace Text Editor %p", View);
            UI_EquipBoxCustomDrawCallback(EditorBox, W_TextEditorDrawCallback, Editor);
        }
        
        //- sixten: build footer
        W_BuildTextEditorInfoBar(Editor);
        r32 TargetFooterHeightEm = 2.25f*Min(Editor->Compiled.Errors.Count, 10LL);
        UI_Size(UI_Percent(1, 0), UI_Em(AC_AnimateValueF(TargetFooterHeightEm, TargetFooterHeightEm, 0.3, "Error Lister %p", Editor), 1)) UI_Column() UI_Height(UI_TextContent(0, 1))
        {
            s64 Index = 0;
            for(scene_compile_error *Error = Editor->Compiled.Errors.First; Error != 0; Error = Error->Next, Index += 1)
            {
                UI_SetNextHeight(UI_ChildrenSum(1, 1));
                UI_SetNextLayoutAxis(Axis2_X);
                UI_Parent(UI_MakeBoxF(0, "Editor Error Lister Container %p", Error)) UI_Padding(UI_Em(1, 1)) UI_Height(UI_Em(1.75f, 1))
                {
                    UI_SetNextBackgroundColor(SetAlpha(Theme_BorderColor, 0.8f));
                    UI_SetNextCornerRadius(4);
                    UI_SetNextLayoutAxis(Axis2_X);
                    UI_SetNextHoverCursor(PlatformCursor_Hand);
                    ui_box *ContainerBox = UI_MakeBoxF(UI_BoxFlag_Clickable|UI_BoxFlag_DrawDropShadow|UI_BoxFlag_DrawBorder, "Container Box");
                    
                    UI_Parent(ContainerBox) UI_Padding(UI_Em(1, 1)) UI_Width(UI_TextContent(0, 1))
                    {
                        UI_Font(Font_Icons) UI_LabelF("%U", FontIcon_Attention);
                        UI_Spacer(UI_Em(0.5f, 1));
                        // sixten(TODO): this is dumb, slow and downright stupid.
                        text_point Point = TextPointFromOffset(Editor->Text.String, Error->Token.Range.Min);
                        UI_LabelF("%i:%i", Point.Line, Point.Column);
                        UI_Spacer(UI_Em(0.5f, 1));
                        UI_Label(Error->Message);
                        UI_Spacer(UI_Percent(1, 0));
                    }
                    
                    ui_signal Signal = UI_SignalFromBox(ContainerBox);
                    if(Signal.Hovering)
                    {
                        UI_TooltipLabel(StrLit("Goto in source"), UI_MouseP());
                    }
                }
                UI_Spacer(UI_Em(0.5, 1));
            }
        }
        
        b32 CursorHasBeenModified = false;
        
        if(W_ViewIsCurrent(View))
        {
            //- sixten: handle history
            {
                history_list *List = &Editor->History;
                
                //- sixten: undo
                if(Platform_KeyPress(UI_EventList(), Key_Z, PlatformModifier_Ctrl))
                {
                    history_node *Node = List->At;
                    if(Node != &List->Sentinel)
                    {
                        //- sixten: get entry & apply
                        history_entry Entry = Node->Backward;
                        MutableStringReplaceRange(&Editor->Text, Entry.ReplaceString, Entry.Range);
                        W_TextEditorApplyChanges(Editor);
                        Editor->EditState.Cursor = Editor->EditState.Mark = Entry.Range.Min+Entry.ReplaceString.Count;
                        CursorHasBeenModified = true;
                        
                        List->At = Node->Prev;
                    }
                }
                
                //- sixten: redo
                if(Platform_KeyPress(UI_EventList(), Key_Y, PlatformModifier_Ctrl))
                {
                    history_node *Node = List->At->Next;
                    if(Node != &List->Sentinel)
                    {
                        //- sixten: get entry & apply
                        history_entry Entry = Node->Forward;
                        MutableStringReplaceRange(&Editor->Text, Entry.ReplaceString, Entry.Range);
                        W_TextEditorApplyChanges(Editor);
                        Editor->EditState.Cursor = Editor->EditState.Mark = Entry.Range.Min+Entry.ReplaceString.Count;
                        CursorHasBeenModified = true;
                        
                        List->At = Node;
                    }
                }
            }
            
            //- sixten: save
            if(Platform_KeyPress(UI_EventList(), Key_S, PlatformModifier_Ctrl))
            {
                W_SaveTextEditorToFile(Editor);
            }
            
            //- sixten: select all
            if(Platform_KeyPress(UI_EventList(), Key_A, PlatformModifier_Ctrl))
            {
                Editor->EditState.Mark = 0;
                Editor->EditState.Cursor = Editor->Text.String.Count;
            }
            
            //- sixten: keyboard input -> text op
            for(platform_event *Event = UI_EventList()->First;
                Event != 0;
                Event = Event->Next)
            {
                if(Event->Type == PlatformEvent_Press || Event->Type == PlatformEvent_Text)
                {
                    //- sixten: auto-indent
                    s64 Indent = 0;
                    if(Event->Codepoint == '\n')
                    {
                        string Line = Substring(Editor->Text.String, Editor->Lines.Ranges[Editor->LastTextPoint.Line-1]);
                        if(Line.Data[Line.Count-1] == '\n')
                        {
                            Line.Count -= 1;
                        }
                        
                        for(u8 *Data = Line.Data; *Data == '\t' && Data < Line.Data+Line.Count; Data += 1)
                        {
                            Indent += 1;
                        }
                    }
                    
                    //- sixten: auto close bracket
                    if(Event->Codepoint == '{')
                    {
                        platform_event FakeEvent = {};
                        FakeEvent.Codepoint = '}';
                        FakeEvent.Type = PlatformEvent_Text;
                        CursorHasBeenModified |= W_ProcessTextEditorEvent(Editor, &FakeEvent);
                        FakeEvent.Key = Key_Left;;
                        FakeEvent.Type = PlatformEvent_Press;
                        CursorHasBeenModified |= W_ProcessTextEditorEvent(Editor, &FakeEvent);
                    }
                    
                    //- sixten: auto close string literal
                    if(Event->Codepoint == '"')
                    {
                        platform_event FakeEvent = {};
                        FakeEvent.Codepoint = '"';
                        FakeEvent.Type = PlatformEvent_Text;
                        CursorHasBeenModified |= W_ProcessTextEditorEvent(Editor, &FakeEvent);
                        FakeEvent.Key = Key_Left;;
                        FakeEvent.Type = PlatformEvent_Press;
                        CursorHasBeenModified |= W_ProcessTextEditorEvent(Editor, &FakeEvent);
                    }
                    
                    CursorHasBeenModified |= W_ProcessTextEditorEvent(Editor, Event);
                    
                    //- sixten: apply indent
                    {
                        platform_event FakeTab = {};
                        FakeTab.Codepoint = '\t';
                        FakeTab.Type = PlatformEvent_Text;
                        
                        for(s64 IndentIndex = 0; IndentIndex < Indent; IndentIndex += 1)
                        {
                            CursorHasBeenModified |= W_ProcessTextEditorEvent(Editor, &FakeTab);
                        }
                    }
                }
            }
        }
        
        //- sixten: right-click dropdown
        {
            if(Editor->DropdownActive)
            {
                UI_Tooltip
                {
                    UI_SetNextFixedP(Editor->DropdownP);
                    UI_SetNextWidth(UI_Em(20, 1));
                    UI_SetNextHeight(UI_ChildrenSum(AC_AnimateValueDirect(1, 0.2, &Editor->DropdownTransition), 1));
                    UI_SetNextCornerRadius(4);
                    UI_Parent(UI_MakeBox(UI_BoxFlag_DrawBackground |
                                         UI_BoxFlag_DrawDropShadow |
                                         UI_BoxFlag_Clip           |
                                         UI_BoxFlag_FloatingX      |
                                         UI_BoxFlag_FloatingY, StrLit("Text Editor Dropdown")))
                        UI_Width(UI_Percent(1, 1)) UI_Height(UI_Em(1.66, 1))
                        UI_BackgroundColor(V4(0.25, 0.25, 0.25, 1))
                        UI_BorderColor(V4(0.45, 0.45, 0.45, 1))
                        UI_CornerRadius(2)
                    {
                        if(W_BuildMenuItem(FontIcon_Gamepad, "Run in scene view", "").Clicked)
                        {
                            SV_SetCurrentSource(&Editor->Compiled);
                            Editor->DropdownActive = false;
                        }
                        if(Editor->EditState.Cursor != Editor->EditState.Mark)
                        {
                            if(W_BuildMenuItem(FontIcon_Document, "Copy", "Ctrl+C").Clicked)
                            {
                                Editor->DropdownActive = false;
                            }
                            if(W_BuildMenuItem(FontIcon_Cut, "Cut", "Ctrl+X").Clicked)
                            {
                                Editor->DropdownActive = false;
                            }
                        }
                        if(W_BuildMenuItem(FontIcon_Paste, "Paste", "Ctrl+V").Clicked)
                        {
                            Editor->DropdownActive = false;
                        }
                        if(W_BuildMenuItem(FontIcon_Floppy, "Save", "Ctrl+S").Clicked)
                        {
                            W_SaveTextEditorToFile(Editor);
                            Editor->DropdownActive = false;
                        }
                    }
                }
            }
        }
        
        ui_signal Signal = UI_SignalFromBox(EditorBox);
        
        if(Signal.Pressed || (Signal.PressedRight && (Editor->EditState.Cursor == Editor->EditState.Mark)))
        {
            //- sixten: translate mouse position to text point
            v2 MouseOffset = Signal.MouseP - EditorBox->Rect.Min - V2(LineMarginWidth, 0);
            s64 LineIndex = Clamp((s64)(MouseOffset.y / LineHeight), 0, Editor->Lines.Count);
            
            string Line = Substring(Editor->Text.String, Editor->Lines.Ranges[LineIndex]);
            s64 ColumnOffset = (s64)(MouseOffset.x / GlyphAdvance);
            s64 ColumnIndex = UTF8IndexFromOffset(Line, ColumnOffset);
            
            text_point Point = {LineIndex + 1, ColumnIndex + 1};
            Editor->EditState.Cursor = Editor->EditState.Mark = OffsetFromTextPoint(Editor->Text.String, Editor->Lines, Point);
        }
        
        if(Signal.Dragging)
        {
            //- sixten: translate mouse position to text point
            v2 MouseOffset = Signal.MouseP - EditorBox->Rect.Min - V2(LineMarginWidth, 0);
            s64 LineIndex = Clamp((s64)(MouseOffset.y / LineHeight), 0, Editor->Lines.Count);
            string Line = Substring(Editor->Text.String, Editor->Lines.Ranges[LineIndex]);
            s64 ColumnOffset = (s64)(MouseOffset.x / GlyphAdvance);
            s64 ColumnIndex = UTF8IndexFromOffset(Line, ColumnOffset);
            
            text_point Point = {LineIndex + 1, ColumnIndex + 1};
            Editor->EditState.Cursor = OffsetFromTextPoint(Editor->Text.String, Editor->Lines, Point);
            
            CursorHasBeenModified = true;
            
            Editor->DropdownActive = false;
        }
        
        if(Signal.PressedRight)
        {
            Editor->DropdownActive = true;
            Editor->DropdownP = UI_MouseP();
            Editor->DropdownTransition = 0;
        }
        
        //- sixten: update eventual text point extents
        if(CursorHasBeenModified)
        {
            text_point Point = TextPointFromOffset(Editor->Text.String, Editor->EditState.Cursor);
            if(Editor->LastTextPoint.Line == Point.Line)
            {
                Editor->LastTextPoint = Point;
            }
            else
            {
                Editor->LastTextPoint.Line = Point.Line;
                Editor->LastTextPoint.Column = Max(Editor->LastTextPoint.Column, Point.Column);
            }
        }
    }
    ReleaseScratch(Scratch);
}