Files
imhex/plugins/ui/include/ui/text_editor.hpp
paxcut d8dd287cdf fix: Fixed ImHex crashing when using ctrl-backspace on empty file. (#2433)
Editor was attempting to delete non-existent chars which is UB. Fixed by
checking before deleting. Also fixed was a problem created by having to
press enter to change the search string which advanced the selection to
the first match. In the next step one would expect that pressing enter
on the replace field would replace the selected item but was replacing
the item found after he first.

This was fixed by always replacing the current selection first. If the
replacement is the same as the searched term then replacing won't
advance the cursor, but if they are different then the current match
will no longer exist so it would search fora new one.
2025-09-05 02:28:11 -07:00

711 lines
29 KiB
C++

#pragma once
#include <string>
#include <vector>
#include <array>
#include <memory>
#include <functional>
#include <unordered_set>
#include <unordered_map>
#include <map>
#include <regex>
#include <chrono>
#include <iostream>
#include "imgui.h"
#include "imgui_internal.h"
#include <hex/helpers/utils.hpp>
namespace hex::ui {
using strConstIter = std::string::const_iterator;
class TextEditor {
public:
// indices of the arrays that contain the lines (vector) and the columns (a string) of the
// text editor. Negative values indicate the distance to the last element of the array.
// When comparing coordinates ensure they have the same sign because coordinates don't have
// information about the size of the array. Currently positive coordinates are always bigger
// than negatives even if that gives a wrong result.
struct Coordinates {
i32 m_line, m_column;
Coordinates() : m_line(0), m_column(0) {}
Coordinates(i32 line, i32 column) : m_line(line), m_column(column) {}
bool operator==(const Coordinates &o) const;
bool operator!=(const Coordinates &o) const;
bool operator<(const Coordinates &o) const;
bool operator>(const Coordinates &o) const;
bool operator<=(const Coordinates &o) const;
bool operator>=(const Coordinates &o) const;
Coordinates operator+(const Coordinates &o) const;
Coordinates operator-(const Coordinates &o) const;
};
inline static const Coordinates Invalid = Coordinates(0x80000000, 0x80000000);
struct Selection {
Coordinates m_start;
Coordinates m_end;
Selection() = default;
Selection(Coordinates start, Coordinates end) : m_start(start), m_end(end) {
if (m_start > m_end) {
std::swap(m_start, m_end);
}
}
Coordinates getSelectedLines();
Coordinates getSelectedColumns();
bool isSingleLine();
bool contains(Coordinates coordinates, int8_t endsInclusive=1);
bool operator==(const Selection &o) const {
return m_start == o.m_start && m_end == o.m_end;
}
bool operator!=(const Selection &o) const {
return m_start != o.m_start || m_end != o.m_end;
}
};
struct EditorState {
Selection m_selection;
Coordinates m_cursorPosition;
};
class FindReplaceHandler {
public:
FindReplaceHandler();
typedef std::vector<EditorState> Matches;
Matches &getMatches() { return m_matches; }
bool findNext(TextEditor *editor);
u32 findMatch(TextEditor *editor, i32 index);
bool replace(TextEditor *editor, bool right);
bool replaceAll(TextEditor *editor);
std::string &getFindWord() { return m_findWord; }
void setFindWord(TextEditor *editor, const std::string &findWord) {
if (findWord != m_findWord) {
findAllMatches(editor, findWord);
m_findWord = findWord;
}
}
std::string &getReplaceWord() { return m_replaceWord; }
void setReplaceWord(const std::string &replaceWord) { m_replaceWord = replaceWord; }
void selectFound(TextEditor *editor, i32 found);
void findAllMatches(TextEditor *editor, std::string findWord);
u32 findPosition(TextEditor *editor, Coordinates pos, bool isNext);
bool getMatchCase() const { return m_matchCase; }
void setMatchCase(TextEditor *editor, bool matchCase) {
if (matchCase != m_matchCase) {
m_matchCase = matchCase;
m_optionsChanged = true;
findAllMatches(editor, m_findWord);
}
}
bool getWholeWord() const { return m_wholeWord; }
void setWholeWord(TextEditor *editor, bool wholeWord) {
if (wholeWord != m_wholeWord) {
m_wholeWord = wholeWord;
m_optionsChanged = true;
findAllMatches(editor, m_findWord);
}
}
bool getFindRegEx() const { return m_findRegEx; }
void setFindRegEx(TextEditor *editor, bool findRegEx) {
if (findRegEx != m_findRegEx) {
m_findRegEx = findRegEx;
m_optionsChanged = true;
findAllMatches(editor, m_findWord);
}
}
void resetMatches() {
m_matches.clear();
m_findWord = "";
}
private:
std::string m_findWord;
std::string m_replaceWord;
bool m_matchCase;
bool m_wholeWord;
bool m_findRegEx;
bool m_optionsChanged;
Matches m_matches;
};
enum class PaletteIndex {
Default,
Identifier,
Directive,
Operator,
Separator,
BuiltInType,
Keyword,
NumericLiteral,
StringLiteral,
CharLiteral,
Cursor,
Background,
LineNumber,
Selection,
Breakpoint,
ErrorMarker,
PreprocessorDeactivated,
CurrentLineFill,
CurrentLineFillInactive,
CurrentLineEdge,
ErrorText,
WarningText,
DebugText,
DefaultText,
Attribute,
PatternVariable,
LocalVariable,
CalculatedPointer,
TemplateArgument,
Function,
View,
FunctionVariable,
FunctionParameter,
UserDefinedType,
PlacedVariable,
GlobalVariable,
NameSpace,
TypeDef,
UnkIdentifier,
DocComment,
DocBlockComment,
BlockComment,
GlobalDocComment,
Comment,
PreprocIdentifier,
Max
};
typedef std::vector<std::pair<std::regex, PaletteIndex>> RegexList;
struct Identifier {
Coordinates m_location;
std::string m_declaration;
};
using String = std::string;
using Identifiers = std::unordered_map<std::string, Identifier>;
using Keywords = std::unordered_set<std::string>;
using ErrorMarkers = std::map<Coordinates, std::pair<u32, std::string>>;
using Breakpoints = std::unordered_set<u32>;
using Palette = std::array<ImU32, (u64) PaletteIndex::Max>;
using Glyph = uint8_t;
class ActionableBox {
ImRect m_box;
public:
ActionableBox() = default;
explicit ActionableBox(const ImRect &box) : m_box(box) {}
virtual bool trigger() {
return ImGui::IsMouseHoveringRect(m_box.Min, m_box.Max);
}
virtual void callback() {}
};
class CursorChangeBox : public ActionableBox {
public:
CursorChangeBox() = default;
explicit CursorChangeBox(const ImRect &box) : ActionableBox(box) {}
void callback() override {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
}
};
class ErrorGotoBox : public ActionableBox {
Coordinates m_pos;
public:
ErrorGotoBox() = default;
ErrorGotoBox(const ImRect &box, const Coordinates &pos, TextEditor *editor) : ActionableBox(box), m_pos(pos), m_editor(editor) {}
bool trigger() override {
return ActionableBox::trigger() && ImGui::IsMouseClicked(0);
}
void callback() override {
m_editor->jumpToCoords(m_pos);
}
private:
TextEditor *m_editor = nullptr;
};
using ErrorGotoBoxes = std::map<Coordinates, ErrorGotoBox>;
using CursorBoxes = std::map<Coordinates, CursorChangeBox>;
class ErrorHoverBox : public ActionableBox {
Coordinates m_pos;
std::string m_errorText;
public:
ErrorHoverBox() = default;
ErrorHoverBox(const ImRect &box, const Coordinates &pos, const char *errorText) : ActionableBox(box), m_pos(pos), m_errorText(errorText) {}
void callback() override {
ImGui::BeginTooltip();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f));
ImGui::Text("Error at line %d:", m_pos.m_line);
ImGui::PopStyleColor();
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.2f, 1.0f));
ImGui::TextUnformatted(m_errorText.c_str());
ImGui::PopStyleColor();
ImGui::EndTooltip();
}
};
using ErrorHoverBoxes = std::map<Coordinates, ErrorHoverBox>;
// A line of text in the pattern editor consists of three strings; the character encoding, the color encoding and the flags.
// The char encoding is utf-8, the color encoding are indices to the color palette and the flags are used to override the colors
// depending on priorities; e.g. comments, strings, etc.
class Line {
public:
struct FlagBits {
bool comment: 1;
bool blockComment: 1;
bool docComment: 1;
bool blockDocComment: 1;
bool globalDocComment: 1;
bool deactivated: 1;
bool preprocessor: 1;
bool matchedBracket: 1;
};
union Flags {
Flags(char value) : m_value(value) {}
Flags(FlagBits bits) : m_bits(bits) {}
FlagBits m_bits;
char m_value;
};
constexpr static char inComment = 31;
class LineIterator {
public:
strConstIter m_charsIter;
strConstIter m_colorsIter;
strConstIter m_flagsIter;
LineIterator(const LineIterator &other) : m_charsIter(other.m_charsIter), m_colorsIter(other.m_colorsIter), m_flagsIter(other.m_flagsIter) {}
LineIterator() = default;
char operator*();
LineIterator operator++();
LineIterator operator=(const LineIterator &other);
bool operator!=(const LineIterator &other) const;
bool operator==(const LineIterator &other) const;
LineIterator operator+(i32 n);
i32 operator-(LineIterator l);
};
enum class LinePart {
Chars,
Utf8,
Colors,
Flags
};
LineIterator begin() const;
LineIterator end() const;
std::string m_chars;
std::string m_colors;
std::string m_flags;
bool m_colorized = false;
i32 m_lineTextSize;
Line() : m_chars(), m_colors(), m_flags(), m_colorized(false), m_lineTextSize(-1) {}
explicit Line(const char *line) { Line(std::string(line)); }
explicit Line(const std::string &line) :
m_chars(line), m_colors(std::string(line.size(), 0x00)), m_flags(std::string(line.size(), 0x00)), m_colorized(false), m_lineTextSize(
getLineTextSize()) {}
Line(const Line &line) : m_chars(line.m_chars), m_colors(line.m_colors), m_flags(line.m_flags), m_colorized(line.m_colorized), m_lineTextSize(line.m_lineTextSize) {}
i32 getCharColumn(i32 stringIndex) const;
i32 getColumnIndex(i32 column) const;
i32 getLineTextSize();
i32 getStringTextSize(const std::string &str) const;
LineIterator begin();
LineIterator end();
Line &operator=(const Line &line);
Line &operator=(Line &&line) noexcept;
u64 size() const;
char front(LinePart part = LinePart::Chars) const;
std::string frontUtf8(LinePart part = LinePart::Chars) const;
void push_back(char c);
bool empty() const;
std::string substr(u64 start, u64 length = (u64) -1, LinePart part = LinePart::Chars) const;
char operator[](u64 index) const;
// C++ can't overload functions based on return type, so use any type other
// than u64 to avoid ambiguity.
std::string operator[](i64 column) const;
void setNeedsUpdate(bool needsUpdate);
void append(const char *text);
void append(const char text);
void append(const std::string &text);
void append(const Line &line);
void append(LineIterator begin, LineIterator end);
void insert(LineIterator iter, const std::string &text);
void insert(LineIterator iter, const char text);
void insert(LineIterator iter, strConstIter beginString, strConstIter endString);
void insert(LineIterator iter, const Line &line);
void insert(LineIterator iter, LineIterator beginLine, LineIterator endLine);
void erase(LineIterator begin);
void erase(LineIterator begin, u64 count);
void erase(u64 start, u64 length = (u64) -1);
void clear();
void setLine(const std::string &text);
void setLine(const Line &text);
bool needsUpdate() const;
};
using Lines = std::vector<Line>;
struct LanguageDefinition {
typedef std::pair<std::string, PaletteIndex> TokenRegexString;
typedef std::vector<TokenRegexString> TokenRegexStrings;
typedef bool(*TokenizeCallback)(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin,
strConstIter &out_end, PaletteIndex &paletteIndex);
std::string m_name;
Keywords m_keywords;
Identifiers m_identifiers;
Identifiers m_preprocIdentifiers;
std::string m_singleLineComment, m_commentEnd, m_commentStart, m_globalDocComment, m_docComment, m_blockDocComment;
char m_preprocChar;
bool m_autoIndentation;
TokenizeCallback m_tokenize;
TokenRegexStrings m_tokenRegexStrings;
bool m_caseSensitive;
LanguageDefinition() : m_name(""), m_keywords({}), m_identifiers({}), m_preprocIdentifiers({}),
m_singleLineComment(""), m_commentEnd(""),
m_commentStart(""), m_globalDocComment(""), m_docComment(""), m_blockDocComment(""),
m_preprocChar('#'), m_autoIndentation(true), m_tokenize(nullptr),
m_tokenRegexStrings({}), m_caseSensitive(true) {}
static const LanguageDefinition &CPlusPlus();
static const LanguageDefinition &HLSL();
static const LanguageDefinition &GLSL();
static const LanguageDefinition &C();
static const LanguageDefinition &SQL();
static const LanguageDefinition &AngelScript();
static const LanguageDefinition &Lua();
};
TextEditor();
~TextEditor();
private:
class UndoRecord {
public:
UndoRecord() {}
~UndoRecord() {}
UndoRecord( const std::string &added,
const TextEditor::Selection addedSelection,
const std::string &removed,
const TextEditor::Selection removedSelection,
TextEditor::EditorState &before,
TextEditor::EditorState &after);
void undo(TextEditor *editor);
void redo(TextEditor *editor);
std::string m_added;
Selection m_addedSelection;
std::string m_removed;
Selection m_removedSelection;
EditorState m_before;
EditorState m_after;
};
typedef std::vector<UndoRecord> UndoBuffer;
struct MatchedBracket {
bool m_active = false;
bool m_changed = false;
Coordinates m_nearCursor = {};
Coordinates m_matched = {};
static const std::string s_separators;
static const std::string s_operators;
MatchedBracket(const MatchedBracket &other) : m_active(other.m_active), m_changed(other.m_changed),
m_nearCursor(other.m_nearCursor),
m_matched(other.m_matched) {}
MatchedBracket() : m_active(false), m_changed(false), m_nearCursor(0, 0), m_matched(0, 0) {}
MatchedBracket(bool active, bool changed, const Coordinates &nearCursor, const Coordinates &matched)
: m_active(active), m_changed(changed), m_nearCursor(nearCursor), m_matched(matched) {}
bool checkPosition(TextEditor *editor, const Coordinates &from);
bool isNearABracket(TextEditor *editor, const Coordinates &from);
i32 detectDirection(TextEditor *editor, const Coordinates &from);
void findMatchingBracket(TextEditor *editor);
bool isActive() const { return m_active; }
bool hasChanged() const { return m_changed; }
};
public:
// Rendering
ImVec2 underwaves(ImVec2 pos, u32 nChars, ImColor color = ImGui::GetStyleColorVec4(ImGuiCol_Text), const ImVec2 &size_arg = ImVec2(0, 0));
void setTabSize(i32 value);
float getPageSize() const;
bool isEndOfLine(const Coordinates &coordinates) const;
bool isEndOfFile(const Coordinates &coordinates) const;
bool isEndOfLine() const;
bool isStartOfLine() const;
void setTopLine();
void render(const char *title, const ImVec2 &size = ImVec2(), bool border = false);
inline void setShowCursor(bool value) { m_showCursor = value; }
inline void setShowLineNumbers(bool value) { m_showLineNumbers = value; }
inline void setShowWhitespaces(bool value) { m_showWhitespaces = value; }
inline bool isShowingWhitespaces() const { return m_showWhitespaces; }
inline i32 getTabSize() const { return m_tabSize; }
ImVec2 &getCharAdvance() { return m_charAdvance; }
void clearGotoBoxes() { m_errorGotoBoxes.clear(); }
void clearCursorBoxes() { m_cursorBoxes.clear(); }
void addClickableText(std::string text) { m_clickableText.emplace_back(std::move(text)); }
void setErrorMarkers(const ErrorMarkers &markers) { m_errorMarkers = markers; }
Breakpoints &getBreakpoints() { return m_breakpoints; }
void setBreakpoints(const Breakpoints &markers) { m_breakpoints = markers; }
void setLongestLineLength(u64 line) { m_longestLineLength = line; }
u64 getLongestLineLength() const { return m_longestLineLength; }
void setTopMarginChanged(i32 newMargin);
void setFocusAtCoords(const Coordinates &coords, bool ensureVisible = false);
void clearErrorMarkers();
void clearActionables();
private:
void ensureCursorVisible();
void resetCursorBlinkTime();
void renderText(const char *title, const ImVec2 &lineNumbersStartPos, const ImVec2 &textEditorSize);
void setFocus();
void preRender();
void drawSelection(float lineNo);
void drawLineNumbers(ImVec2 position, float lineNo, const ImVec2 &contentSize, bool focused, ImDrawList *drawList);
void renderCursor(float lineNo, ImDrawList *drawList);
void renderGotoButtons(float lineNo);
void drawText(Coordinates &lineStart, u64 i, u32 tokenLength, char color);
void postRender(const char *title, ImVec2 position, float lineNo);
ImVec2 calculateCharAdvance() const;
float textDistanceToLineStart(const Coordinates &from);
// Highlighting
public:
void colorize();
void setLanguageDefinition(const LanguageDefinition &aLanguageDef);
static const Palette &getPalette();
static void setPalette(const Palette &value);
static const Palette &getDarkPalette();
static const Palette &getLightPalette();
static const Palette &getRetroBluePalette();
bool isColorizerEnabled() const { return m_colorizerEnabled; }
const LanguageDefinition &getLanguageDefinition() const { return m_languageDefinition; }
void setNeedsUpdate(u32 line, bool needsUpdate);
void setColorizedLine(u64 line, const std::string &tokens);
private:
void colorizeRange();
void colorizeInternal();
//Editing
public:
void deleteWordLeft();
void deleteWordRight();
void backspace();
bool canUndo();
bool canRedo() const;
void undo(i32 steps = 1);
void redo(i32 steps = 1);
void copy();
void cut();
void paste();
void doPaste(const char *clipText);
void deleteChar();
void insertText(const std::string &value);
void insertText(const char *value);
void appendLine(const std::string &value);
void setOverwrite(bool value) { m_overwrite = value; }
bool isOverwrite() const { return m_overwrite; }
void setText(const std::string &text, bool undo = false);
std::string getText();
std::vector<std::string> getTextLines() const;
std::string getSelectedText();
std::string getLineText(i32 line);
inline void setTextChanged(bool value) { m_textChanged = value; }
inline bool isTextChanged() { return m_textChanged; }
inline void setReadOnly(bool value) { m_readOnly = value; }
inline void setHandleMouseInputs(bool value) { m_handleMouseInputs = value; }
inline bool isHandleMouseInputsEnabled() const { return m_handleKeyboardInputs; }
inline void setHandleKeyboardInputs(bool value) { m_handleKeyboardInputs = value; }
inline bool isHandleKeyboardInputsEnabled() const { return m_handleKeyboardInputs; }
private:
std::string getText(const Selection &selection);
void deleteRange(const Selection &selection);
i32 insertTextAt(Coordinates &where, const std::string &value);
void removeLine(i32 start, i32 end);
void removeLine(i32 index);
Line &insertLine(i32 index);
void insertLine(i32 index, const std::string &text);
void enterCharacter(ImWchar character, bool shift);
void deleteSelection();
// Navigating
public:
void jumpToLine(i32 line = -1);
void jumpToCoords(const Coordinates &coords);
void moveUp(i32 amount = 1, bool select = false);
void moveDown(i32 amount = 1, bool select = false);
void moveLeft(i32 amount = 1, bool select = false, bool wordMode = false);
void moveRight(i32 amount = 1, bool select = false, bool wordMode = false);
void moveTop(bool select = false);
void moveBottom(bool select = false);
void moveHome(bool select = false);
void moveEnd(bool select = false);
void moveToMatchedBracket(bool select = false);
void setScrollY();
Coordinates getCursorPosition() { return setCoordinates(m_state.m_cursorPosition); }
void setCursorPosition(const Coordinates &position);
void setCursorPosition();
private:
Coordinates setCoordinates(const Coordinates &value);
Coordinates setCoordinates(i32 line, i32 column);
Selection setCoordinates(const Selection &value);
void advance(Coordinates &coordinates) const;
Coordinates findWordStart(const Coordinates &from);
Coordinates findWordEnd(const Coordinates &from);
Coordinates findPreviousWord(const Coordinates &from);
Coordinates findNextWord(const Coordinates &from);
u32 skipSpaces(const Coordinates &from);
//Support
public:
void setSelection(const Selection &selection);
Selection getSelection() const;
void selectWordUnderCursor();
void selectAll();
bool hasSelection() const;
void refreshSearchResults();
i32 getTotalLines() const { return (i32) m_lines.size(); }
FindReplaceHandler *getFindReplaceHandler() { return &m_findReplaceHandler; }
void setSourceCodeEditor(TextEditor *editor) { m_sourceCodeEditor = editor; }
inline void clearBreakpointsChanged() { m_breakPointsChanged = false; }
inline bool isBreakpointsChanged() { return m_breakPointsChanged; }
inline void setImGuiChildIgnored(bool value) { m_ignoreImGuiChild = value; }
inline bool isImGuiChildIgnored() const { return m_ignoreImGuiChild; }
bool raiseContextMenu() { return m_raiseContextMenu; }
void clearRaiseContextMenu() { m_raiseContextMenu = false; }
TextEditor *GetSourceCodeEditor();
bool isEmpty() const;
private:
void addUndo(UndoRecord &value);
TextEditor::PaletteIndex getColorIndexFromFlags(Line::Flags flags);
void handleKeyboardInputs();
void handleMouseInputs();
// utf8
public:
static i32 imTextCharToUtf8(char *buffer, i32 buf_size, u32 c);
static void imTextCharToUtf8(std::string &buffer, u32 c);
static i32 utf8CharLength(uint8_t c);
static i32 getStringCharacterCount(const std::string &str);
static TextEditor::Coordinates stringIndexToCoordinates(i32 strIndex, const std::string &input);
private:
Coordinates screenPosToCoordinates(const ImVec2 &position);
Coordinates lineCoordsToIndexCoords(const Coordinates &coordinates) const;
i32 lineCoordinatesToIndex(const Coordinates &coordinates) const;
Coordinates getCharacterCoordinates(i32 line, i32 index);
i32 getLineCharColumn(i32 lineIndex, i32 stringIndex);
u64 getLineByteCount(i32 line) const;
i32 getLineMaxCharColumn(i32 lineIndex);
public:
FindReplaceHandler m_findReplaceHandler;
private:
float m_lineSpacing = 1.0F;
Lines m_lines;
EditorState m_state = {};
UndoBuffer m_undoBuffer;
i32 m_undoIndex = 0;
bool m_scrollToBottom = false;
float m_topMargin = 0.0F;
float m_newTopMargin = 0.0F;
float m_oldTopMargin = 0.0F;
bool m_topMarginChanged = false;
i32 m_tabSize = 4;
bool m_overwrite = false;
bool m_readOnly = false;
bool m_withinRender = false;
bool m_scrollToCursor = false;
bool m_scrollToTop = false;
bool m_textChanged = false;
bool m_colorizerEnabled = true;
float m_lineNumberFieldWidth = 0.0F;
u64 m_longestLineLength = 0;
float m_leftMargin = 10.0;
float m_topLine = 0.0F;
bool m_setTopLine = false;
bool m_breakPointsChanged = false;
bool m_handleKeyboardInputs = true;
bool m_handleMouseInputs = true;
bool m_ignoreImGuiChild = false;
bool m_showWhitespaces = true;
MatchedBracket m_matchedBracket = {};
Palette m_palette = {};
LanguageDefinition m_languageDefinition = {};
RegexList m_regexList;
bool m_updateFlags = true;
Breakpoints m_breakpoints = {};
ErrorMarkers m_errorMarkers = {};
ErrorHoverBoxes m_errorHoverBoxes = {};
ErrorGotoBoxes m_errorGotoBoxes = {};
CursorBoxes m_cursorBoxes = {};
ImVec2 m_charAdvance = {};
Selection m_interactiveSelection = {};
u64 m_startTime = 0;
std::vector<std::string> m_defines;
TextEditor *m_sourceCodeEditor = nullptr;
float m_shiftedScrollY = 0;
float m_scrollYIncrement = 0.0F;
bool m_setScrollY = false;
float m_numberOfLinesDisplayed = 0;
float m_lastClick = -1.0F;
bool m_showCursor = true;
bool m_showLineNumbers = true;
bool m_raiseContextMenu = false;
Coordinates m_focusAtCoords = {};
bool m_updateFocus = false;
std::vector<std::string> m_clickableText;
static const i32 s_cursorBlinkInterval;
static const i32 s_cursorBlinkOnTime;
static ImVec2 s_cursorScreenPosition;
};
bool tokenizeCStyleString(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end);
bool tokenizeCStyleCharacterLiteral(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end);
bool tokenizeCStyleIdentifier(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end);
bool tokenizeCStyleNumber(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end);
bool tokenizeCStyleOperator(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end);
bool tokenizeCStyleSeparator(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end);
}