/* date = May 7th 2023 7:16 pm */ #ifndef VN_TEXT_OP_H #define VN_TEXT_OP_H struct text_op { range_s64 Range; string ReplaceString; string CopyString; s64 NewCursor; s64 NewMark; }; struct text_edit_state { s64 Cursor; s64 Mark; }; typedef u32 text_action_flags; enum { TextActionFlag_WordScan = (1<<0), TextActionFlag_KeepMark = (1<<1), TextActionFlag_Delete = (1<<2), TextActionFlag_ZeroDeltaWithSelection = (1<<3), TextActionFlag_DeltaPicksSelectionSide = (1<<4), }; struct text_action { text_action_flags Flags; s64 Delta; u32 Codepoint; }; inline b32 IsValid(text_action *Action) { b32 Result = !(Action->Flags == 0 && Action->Delta == 0 && Action->Codepoint == 0); return(Result); } static text_action SingleLineTextActionFromEvent(platform_event *Event) { text_action Action = {}; if(Event->Type == PlatformEvent_Text) { if(Event->Codepoint != '\n') { Action.Codepoint = Event->Codepoint; } } else if(Event->Type == PlatformEvent_Press) { if(Event->Modifiers & PlatformModifier_Ctrl) { Action.Flags |= TextActionFlag_WordScan; } if(Event->Modifiers & PlatformModifier_Shift) { Action.Flags |= TextActionFlag_KeepMark; } switch(Event->Key) { case Key_Right: { Action.Delta = +1; Action.Flags |= TextActionFlag_DeltaPicksSelectionSide; } break; case Key_Left: { Action.Delta = -1; Action.Flags |= TextActionFlag_DeltaPicksSelectionSide; } break; case Key_Home: { Action.Delta = S64_Min; } break; case Key_End: { Action.Delta = S64_Max; } break; case Key_Backspace: { Action.Delta = -1; Action.Flags |= TextActionFlag_Delete|TextActionFlag_ZeroDeltaWithSelection; } break; case Key_Delete: { Action.Delta = +1; Action.Flags |= TextActionFlag_Delete|TextActionFlag_ZeroDeltaWithSelection; } break; default: {} break; } } return(Action); } inline s64 CodepointScan(string String, s64 Index, s64 Delta) { s64 Result = 0; if(Delta > 0) { while(Index < String.Count && Delta) { u8 Base = String.Data[Index]; s64 ToMove = 0; if((Base & 0x80) == 0x00) { ToMove = 1; } else if((Base & 0xE0) == 0xC0) { ToMove = 2; } else if((Base & 0xF0) == 0xE0) { ToMove = 3; } else if((Base & 0xF8) == 0xF0) { ToMove = 4; } Result += ToMove; Index += ToMove; --Delta; } } else { Index -= 1; while(Index >= 0 && (Delta != 0)) { u8 Base = String.Data[Index]; if(((Base & 0x80) == 0) || !((Base & 0xC0) == 0x80)) { ++Delta; } --Result; --Index; } } return(Result); } inline b32 IsWordBoundary(string String, s64 Index) { b32 Result; if(Index > 0) { Result = IsWhitespace(String.Data[Index - 1]) && !(IsWhitespace(String.Data[Index])); } else { Result = true; } return(Result); } static s64 WordScan(string String, s64 Index, s64 Delta) { s64 Result = 0; while(Delta) { if(Delta > 0) { ++Index; ++Result; while(Index < String.Count && !IsWordBoundary(String, Index)) { ++Index; ++Result; } if(Index > String.Count) { Result -= Index - String.Count; Index = String.Count; goto End; } --Delta; } else { --Index; --Result; while(Index >= 0 && !IsWordBoundary(String, Index)) { --Index; --Result; } if(Index < 0) { Result -= Index; Index = 0; goto End; } ++Delta; } } End: return(Result); } static text_op TextOpFromAction(memory_arena *Arena, string String, text_edit_state *State, text_action *Action) { text_op Op = {}; Op.NewCursor = State->Cursor; Op.NewMark = State->Mark; Op.Range = RangeS64(0, 0); Op.ReplaceString = StrLit(""); s64 Delta = 0; if(Action->Flags & TextActionFlag_WordScan) { Delta = WordScan(String, State->Cursor, Action->Delta); } else { Delta = CodepointScan(String, State->Cursor, Action->Delta); } if(State->Cursor != State->Mark && Action->Flags & TextActionFlag_ZeroDeltaWithSelection) { Delta = 0; } if(State->Cursor != State->Mark && Action->Flags & TextActionFlag_DeltaPicksSelectionSide && !(Action->Flags & TextActionFlag_KeepMark)) { Delta = 0; if(Action->Delta > 0) { Op.NewCursor = Maximum(State->Cursor, State->Mark); } else if(Action->Delta < 0) { Op.NewCursor = Minimum(State->Cursor, State->Mark); } } else { Op.NewCursor = State->Cursor + Delta; } if(Action->Flags & TextActionFlag_Delete) { Op.Range = RangeS64(Op.NewCursor, Op.NewMark); Op.NewCursor = Op.NewMark = Op.Range.Min; } if(Action->Codepoint != 0) { Op.ReplaceString = StringFromCodepoint(Arena, Action->Codepoint); if(State->Cursor == State->Mark) { Op.NewCursor += Op.ReplaceString.Count; Op.Range = RangeS64(State->Cursor, State->Cursor); } else { Op.NewCursor += Op.ReplaceString.Count; Op.Range = RangeS64(State->Cursor, State->Mark); } } if(!(Action->Flags & TextActionFlag_KeepMark)) { Op.NewMark = Op.NewCursor; } return(Op); } #endif //VN_TEXT_OP_H