vn/code/vn_text_op.h

296 lines
6.6 KiB
C

/* 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