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

static mutable_string MutableStringAllocate(u64 Size)
{
	mutable_string Result = {};
	Result.Arena = ArenaAlloc(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(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(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(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(arena *Arena, string Text)
{
	temp 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 = {};
	s64 MaxLineCount = 0;
	{
		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')
			{
				if(DimOfRange(Range) > MaxLineCount)
				{
					MaxLineCount = DimOfRange(Range);
				}
				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);;
		Result.MaxLineCount = MaxLineCount;
	}
	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->MaxLineCount = TextData.MaxLineCount;
	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)
{
	temp 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 b32 W_ProcessTextEditorEvent(workspace_view_text_editor *Editor, platform_event *Event)
{
	b32 CursorHasBeenModified = false;
	temp 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_BuildTextEditorErrorList(workspace_view_text_editor *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());
                    // sixten(TODO): ACTUALLY GO TO THE SOURCE LOCATION
                }
            }
            UI_Spacer(UI_Em(0.5, 1));
        }
    }
}

UI_CUSTOM_DRAW_CALLBACK(W_TextEditorLinesBarDrawCallback)
{
	workspace_view_text_editor *Editor = (workspace_view_text_editor *)Data;
    
    //- sixten: calculate dimensions
    ui_box *Parent = Box->Parent;
	v2_r32 ParentDim = DimOfRange(Parent->Rect);
    
    r32 FontSize = Editor->FontSize;
    r32 LineHeight = FontSize + 4.0f;
    
    s64 LineStart = Floor(-Parent->Offset.y/LineHeight);
    s64 LineEnd = Min(Editor->Lines.Count, (s64)Floor((ParentDim.y-Parent->Offset.y)/LineHeight)+1);
    
    s64 LineNumberCount = (s64)(Log(Editor->Lines.Count)/Log(10))+1;
    
    for(s64 Line = LineStart; Line < LineEnd; Line += 1)
    {
        PushTextF(Group, Atlas, Font_Monospace, Box->Rect.Min+V2R32(0, Line*LineHeight), Editor->FontSize, Color_Grey, " %*i", LineNumberCount, Line+1);
    }
}

UI_CUSTOM_DRAW_CALLBACK(W_TextEditorDrawCallback)
{
    workspace_view_text_editor *Editor = (workspace_view_text_editor *)Data;
    temp Scratch = GetScratch();
    
    //- sixten: calculate dimensions
    ui_box *Parent = Box->Parent;
	v2_r32 ParentDim = DimOfRange(Parent->Rect);
    
	//- 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);
	
    s64 LineStart = Floor(-Parent->Offset.y/LineHeight);
    s64 LineEnd = Min(Editor->Lines.Count, (s64)Floor((ParentDim.y-Parent->Offset.y)/LineHeight)+1);
    
	//- 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;
    v2_r32 StartTokenP = Box->Rect.Min;
	
	//- sixten: find the first visible token
	token *VisibleTokensBegin = TokensBegin;
	for(s64 LinesFound = 0; LinesFound < LineStart && VisibleTokensBegin < TokensEnd; VisibleTokensBegin += 1)
	{
		if(VisibleTokensBegin->Kind == TokenKind_Newline)
		{
			LinesFound += 1;
            StartTokenP.y += LineHeight;
		}
	}
	
	//- sixten: find the last visible token
	token *VisibleTokensEnd = VisibleTokensBegin;
	for(s64 LinesFound = 0; LinesFound < LineEnd-LineStart && VisibleTokensEnd < TokensEnd; VisibleTokensEnd += 1)
	{
		if(VisibleTokensEnd->Kind == TokenKind_Newline)
		{
			LinesFound += 1;
		}
	}
    
#if 0    
    //- sixten: render gradient on current line
    {
        v4_r32 ColorBegin = SetAlpha(Theme_HighlightBorderColor, 0.5);
        v4_r32 ColorEnd = SetAlpha(Theme_HighlightBorderColor, 0.0);
        range2_r32 LineDest = Range2R32(Box->Rect.Min+V2R32(0, (CursorTextP.Line-1)*LineHeight), Box->Rect.Min+V2R32(400, CursorTextP.Line*LineHeight));
        PushQuad(Group, LineDest, ColorBegin, ColorEnd, ColorBegin, ColorEnd, 0, 0, 0);
        PushQuad(Group, LineDest, Theme_BackgroundColor, Theme_BackgroundColor, Theme_BackgroundColor, Theme_BackgroundColor, 4.0f, 14, 0);
    }
#endif
    
	//- sixten: render tokens
    v2_r32 TokenP = StartTokenP;
    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_Cyan;
		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 = StartTokenP.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 = StartTokenP.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 the selection
	{
		text_range Selection = TextRange(CursorTextP, MarkTextP);
		range1_s64 LineRange = Range1S64(Selection.Min.Line, Selection.Max.Line);
		for(s64 LineIndex = LineStart; LineIndex < LineEnd; LineIndex += 1)
		{
			if(Contains(LineRange, LineIndex + 1))
			{
				range1_s64 ColumnRange = Editor->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(ColumnOffsetRange.Min*GlyphAdvance, LineY),
                                                        Box->Rect.Min+V2(ColumnOffsetRange.Max*GlyphAdvance, LineY+LineHeight));
				PushQuad(Group, LineHighlightBox, LineHighlightColor, LineHighlightColor, LineHighlightColor, LineHighlightColor, 4, 1.4, 0);
			}
		}
	}
    
	//- 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(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);
	}
}

static void W_BuildTextEditor(workspace_view *View)
{
    workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data;
    
    //- sixten: calculate dimensions
    r32 FontSize = Editor->FontSize;
    r32 LineHeight = FontSize + 4.0f;
    
    glyph *Glyph = GetGlyph(UI_GlyphAtlas(), Font_Monospace, 'A', FontSize, 0);
    r32 GlyphAdvance = Glyph->Advance;
    
    s64 LineCount = Editor->Lines.Count;
    s64 MaxLineCount = Editor->MaxLineCount;
    
    s64 LineNumberCount = (s64)(Log(Editor->Lines.Count)/Log(10))+1;
    v2_r32 LinesBarDim = V2R32((LineNumberCount + 2)*GlyphAdvance, LineCount*LineHeight);
    
    v2_r32 TextEditorDim = V2R32((MaxLineCount+LineNumberCount+2)*GlyphAdvance, (LineCount+1)*LineHeight);
    v2_r32 VisibleRegionDim; // sixten(NOTE): This is set further down
    
    b32 CursorHasBeenModified = false;
    
    //- sixten: layout editor
    UI_WidthFill UI_HeightFill UI_Column()
    {
        //- sixten: line numbers, editor & scrollbar
        UI_HeightFill UI_Row(UI_BoxFlag_DrawBorder)
        {
            UI_WidthFill UI_Column(0, StrLit("Scrollable & Scroll X"))
            {
                UI_Row(0, StrLit("Scroll Region")) // contains lines bar & text editor
                {
                    v2_r32 ScrollRegionDim = DimOfRange(UI_TopParent()->Rect);
                    
                    v2_r32 AnimatedOffset = V2R32(AC_AnimateValueF(Editor->Offset.x, Editor->Offset.x, 0.3f, "%p Offset X", View),
                                                  AC_AnimateValueF(Editor->Offset.y, Editor->Offset.y, 0.3f, "%p Offset Y", View));
                    
                    // sixten(NOTE): We put the lines bar & text editor in containers to be able to easily offset them.
                    UI_SetNextBackgroundColor(ColorFromHex(0x10203080));
                    UI_SetNextOffsetY(-AnimatedOffset.y);
                    UI_Width(UI_ChildrenSum(1, 1)) UI_Parent(UI_MakeBox(UI_BoxFlag_Clip|UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawDropShadow, StrLit("Lines Bar Container")))
                    {
                        UI_SetNextSize(UI_Pixels(LinesBarDim.x, 1), UI_Pixels(LinesBarDim.y, 1));
                        ui_box *LinesBarBox = UI_MakeBoxF(0, "Workspace View Text Editor Lines Bar");
                        UI_EquipBoxCustomDrawCallback(LinesBarBox, W_TextEditorLinesBarDrawCallback, Editor);
                    }
                    
                    UI_SetNextOffset(-AnimatedOffset.x, -AnimatedOffset.y);
                    UI_WidthFill UI_Parent(UI_MakeBox(UI_BoxFlag_Clip|UI_BoxFlag_Clickable, StrLit("Text Editor Container")))
                    {
                        ui_box *EditorContainerBox = UI_TopParent();
                        VisibleRegionDim = DimOfRange(EditorContainerBox->Rect);
                        ui_signal EditorContainerSignal = UI_SignalFromBox(EditorContainerBox);
                        //- sixten: text editor scrolling
                        if(AreEqual(UI_HotKey(), EditorContainerBox->Key))
                        {
                            for(platform_event *Event = UI_EventList()->First;
                                Event != 0;
                                Event = Event->Next)
                            {
                                if(Event->Type == PlatformEvent_MouseScroll && (Event->Modifiers != PlatformModifier_Ctrl) && Event->Scroll.y != 0)
                                {
                                    Editor->Offset.y -= Event->Scroll.y*LineHeight*4;
                                    Platform_ConsumeEvent(UI_EventList(), Event);
                                }
                            }
                        }
                        UI_SetNextSize(UI_Pixels(TextEditorDim.x, 1), UI_Pixels(TextEditorDim.y, 1));
                        ui_box *TextEditorBox = UI_MakeBoxF(0, "Workspace View Text Editor");
                        UI_EquipBoxCustomDrawCallback(TextEditorBox, W_TextEditorDrawCallback, Editor);
                        
                        if(EditorContainerSignal.Dragging)
                        {
                            //- sixten: translate mouse position to text point
                            v2 MouseOffset = EditorContainerSignal.MouseP - TextEditorBox->Rect.Min;
                            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);
                            
                            CursorHasBeenModified = true;
                            
                            text_point Point = {LineIndex + 1, ColumnIndex + 1};
                            Editor->EditState.Cursor = OffsetFromTextPoint(Editor->Text.String, Editor->Lines, Point);
                            
                            if(EditorContainerSignal.Pressed)
                            {
                                Editor->EditState.Mark = Editor->EditState.Cursor;
                            }
                        }
                    }
                }
                
                //- sixten: build scrollbar x
                {
                    ui_box *ParentBox = UI_TopParent();
                    v2_r32 ParentDim = DimOfRange(ParentBox->Rect);
                    r32 MaxScrollWidth = ParentDim.x-UI_TopFontSize()*2;
                    r32 ScrollScaleX = ParentDim.x/TextEditorDim.x;
                    if(ScrollScaleX > 1)
                    {
                        ScrollScaleX = 0;
                    }
                    ui_signal ScrollX = UI_Scrollbar(Axis2_X, StrLit("Workspace View Text Editor Scrollbar X"), MaxScrollWidth*ScrollScaleX, Editor->Offset.x/TextEditorDim.x*MaxScrollWidth);
                    if(ScrollX.Dragging)
                    {
                        if(ScrollX.Pressed)
                        {
                            UI_StoreDragR32(Editor->Offset.x);
                        }
                        Editor->Offset.x = UI_GetDragR32() + ScrollX.DragDelta.x/ScrollScaleX;
                        
                    }
                    Editor->Offset.x = Clamp(Editor->Offset.x, 0, Max(0.0f, TextEditorDim.x-ParentDim.x));
                }
            }
            
            UI_Width(UI_ChildrenSum(1, 1)) UI_Column(0, StrLit("Scrollable & Scroll Y"))
            {
                //- sixten: build scrollbar y
                {
                    ui_box *ParentBox = UI_TopParent();
                    v2_r32 ParentDim = DimOfRange(ParentBox->Rect);
                    r32 MaxScrollHeight = ParentDim.y-UI_TopFontSize()*3;
                    r32 ScrollScaleY = ParentDim.y/TextEditorDim.y;
                    if(ScrollScaleY > 1)
                    {
                        MaxScrollHeight = 0;
                    }
                    ui_signal ScrollY = UI_Scrollbar(Axis2_Y, StrLit("Workspace View Text Editor Scrollbar Y"), MaxScrollHeight*ScrollScaleY, Editor->Offset.y/TextEditorDim.y*MaxScrollHeight);
                    if(ScrollY.Dragging)
                    {
                        if(ScrollY.Pressed)
                        {
                            UI_StoreDragR32(Editor->Offset.y);
                        }
                        Editor->Offset.y = UI_GetDragR32() + ScrollY.DragDelta.y/ScrollScaleY;
                    }
                    Editor->Offset.y = Clamp(Editor->Offset.y, 0, Max(0.0f, TextEditorDim.y-ParentDim.y));
                }
                UI_Width(UI_Em(1, 1)) UI_Height(UI_Em(1, 1)) UI_MakeBox(UI_BoxFlag_DrawBorder, StrLit(""));
            }
        }
        
        //- sixten: info bar
        W_BuildTextEditorInfoBar(Editor);
        W_BuildTextEditorErrorList(Editor);
    }
    
    //- sixten: process inputs
    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)
        {
            //- sixten: check for scaling
            if(Event->Type == PlatformEvent_Press && Event->Modifiers == PlatformModifier_Ctrl && (Event->Key == Key_Plus || Event->Key == Key_Minus))
            {
                Editor->FontSize += (Event->Key == Key_Plus ? 1 : -1);
                continue;
            }
            if(Event->Type == PlatformEvent_MouseScroll && Event->Modifiers == PlatformModifier_Ctrl && (Event->Scroll.y != 0))
            {
                Editor->FontSize += (Event->Scroll.y);
                continue;
            }
            
            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);
                }
                
                //- sixten: default event
                if(W_ProcessTextEditorEvent(Editor, Event))
                {
                    CursorHasBeenModified = true;
                    Platform_ConsumeEvent(UI_EventList(), 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: change last text point & adapt view to cursor
        if(CursorHasBeenModified)
        {
            text_point Point = TextPointFromOffset(Editor->Text.String, Editor->EditState.Cursor);
            s64 LineIndex = Point.Line-1;
            string Line = Substring(Editor->Text.String, Editor->Lines.Ranges[LineIndex]);
            s64 ColumnIndex = Point.Column-1;
            s64 ColumnOffset = UTF8OffsetFromIndex(Line, ColumnIndex);
            
            v2 CursorP = V2(ColumnOffset*GlyphAdvance, LineIndex*LineHeight);
            
            if(CursorP.y < Editor->Offset.y)
            {
                Editor->Offset.y = CursorP.y;
            }
            if(CursorP.y > Editor->Offset.y + VisibleRegionDim.y - LineHeight)
            {
                Editor->Offset.y = CursorP.y - VisibleRegionDim.y + LineHeight;
            }
            
            if(CursorP.x < Editor->Offset.x)
            {
                Editor->Offset.x = CursorP.x;
            }
            if(CursorP.x > Editor->Offset.x + VisibleRegionDim.x - LineHeight)
            {
                Editor->Offset.x = CursorP.x - VisibleRegionDim.x + LineHeight;
            }
            
            if(Editor->LastTextPoint.Line == Point.Line)
            {
                Editor->LastTextPoint = Point;
            }
            else
            {
                Editor->LastTextPoint.Line = Point.Line;
                Editor->LastTextPoint.Column = Max(Editor->LastTextPoint.Column, Point.Column);
            }
        }
    }
}