| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- #pragma once
- #include <cassert>
- #include <iostream>
- #include <string>
- #include <vector>
- #include <array>
- #include <memory>
- #include <unordered_set>
- #include <unordered_map>
- #include <map>
- #include "boost/regex.hpp"
- #include "imgui.h"
- class IMGUI_API TextEditor
- {
- public:
- // ------------- Exposed API ------------- //
- TextEditor();
- ~TextEditor();
- enum class PaletteId
- {
- Dark, Light, Mariana, RetroBlue
- };
- enum class LanguageDefinitionId
- {
- None, Cpp, C, Cs, Python, Lua, Json, Sql, AngelScript, Glsl, Hlsl
- };
- enum class SetViewAtLineMode
- {
- FirstVisibleLine, Centered, LastVisibleLine
- };
- inline void SetReadOnlyEnabled(bool aValue) { mReadOnly = aValue; }
- inline bool IsReadOnlyEnabled() const { return mReadOnly; }
- inline void SetAutoIndentEnabled(bool aValue) { mAutoIndent = aValue; }
- inline bool IsAutoIndentEnabled() const { return mAutoIndent; }
- inline void SetShowWhitespacesEnabled(bool aValue) { mShowWhitespaces = aValue; }
- inline bool IsShowWhitespacesEnabled() const { return mShowWhitespaces; }
- inline void SetShowLineNumbersEnabled(bool aValue) { mShowLineNumbers = aValue; }
- inline bool IsShowLineNumbersEnabled() const { return mShowLineNumbers; }
- inline void SetShortTabsEnabled(bool aValue) { mShortTabs = aValue; }
- inline bool IsShortTabsEnabled() const { return mShortTabs; }
- inline int GetLineCount() const { return (int)mLines.size(); }
- inline bool IsOverwriteEnabled() const { return mOverwrite; }
- void SetPalette(PaletteId aValue);
- PaletteId GetPalette() const { return mPaletteId; }
- void SetLanguageDefinition(LanguageDefinitionId aValue);
- LanguageDefinitionId GetLanguageDefinition() const { return mLanguageDefinitionId; };
- const char* GetLanguageDefinitionName() const;
- void SetTabSize(int aValue);
- inline int GetTabSize() const { return mTabSize; }
- void SetLineSpacing(float aValue);
- inline float GetLineSpacing() const { return mLineSpacing; }
- inline bool HasTextChanged() const { return mTextChanged; }
- inline void ResetTextChanged() { mTextChanged = false; }
- inline static void SetDefaultPalette(PaletteId aValue) { defaultPalette = aValue; }
- inline static PaletteId GetDefaultPalette() { return defaultPalette; }
- void SelectAll();
- void SelectLine(int aLine);
- void SelectRegion(int aStartLine, int aStartChar, int aEndLine, int aEndChar);
- void SelectNextOccurrenceOf(const char* aText, int aTextSize, bool aCaseSensitive = true);
- void SelectAllOccurrencesOf(const char* aText, int aTextSize, bool aCaseSensitive = true);
- bool AnyCursorHasSelection() const;
- bool AllCursorsHaveSelection() const;
- void ClearExtraCursors();
- void ClearSelections();
- void SetCursorPosition(int aLine, int aCharIndex);
- inline void GetCursorPosition(int& outLine, int& outColumn) const
- {
- auto coords = GetActualCursorCoordinates();
- outLine = coords.mLine;
- outColumn = coords.mColumn;
- }
- int GetFirstVisibleLine();
- int GetLastVisibleLine();
- void SetViewAtLine(int aLine, SetViewAtLineMode aMode);
- void Copy();
- void Cut();
- void Paste();
- void Undo(int aSteps = 1);
- void Redo(int aSteps = 1);
- inline bool CanUndo() const { return !mReadOnly && mUndoIndex > 0; };
- inline bool CanRedo() const { return !mReadOnly && mUndoIndex < (int)mUndoBuffer.size(); };
- inline int GetUndoIndex() const { return mUndoIndex; };
- void SetText(const std::string& aText);
- std::string GetText() const;
- void SetTextLines(const std::vector<std::string>& aLines);
- std::vector<std::string> GetTextLines() const;
- bool Render(const char* aTitle, bool aParentIsFocused = false, const ImVec2& aSize = ImVec2(), bool aBorder = false);
- void ImGuiDebugPanel(const std::string& panelName = "Debug");
- void UnitTests();
- private:
- // ------------- Generic utils ------------- //
- static inline ImVec4 U32ColorToVec4(ImU32 in)
- {
- float s = 1.0f / 255.0f;
- return ImVec4(
- ((in >> IM_COL32_A_SHIFT) & 0xFF) * s,
- ((in >> IM_COL32_B_SHIFT) & 0xFF) * s,
- ((in >> IM_COL32_G_SHIFT) & 0xFF) * s,
- ((in >> IM_COL32_R_SHIFT) & 0xFF) * s);
- }
- static inline bool IsUTFSequence(char c)
- {
- return (c & 0xC0) == 0x80;
- }
- static inline float Distance(const ImVec2& a, const ImVec2& b)
- {
- float x = a.x - b.x;
- float y = a.y - b.y;
- return sqrt(x * x + y * y);
- }
- template<typename T>
- static inline T Max(T a, T b) { return a > b ? a : b; }
- template<typename T>
- static inline T Min(T a, T b) { return a < b ? a : b; }
- // ------------- Internal ------------- //
- enum class PaletteIndex
- {
- Default,
- Keyword,
- Number,
- String,
- CharLiteral,
- Punctuation,
- Preprocessor,
- Identifier,
- KnownIdentifier,
- PreprocIdentifier,
- Comment,
- MultiLineComment,
- Background,
- Cursor,
- Selection,
- ErrorMarker,
- ControlCharacter,
- Breakpoint,
- LineNumber,
- CurrentLineFill,
- CurrentLineFillInactive,
- CurrentLineEdge,
- Max
- };
- // Represents a character coordinate from the user's point of view,
- // i. e. consider an uniform grid (assuming fixed-width font) on the
- // screen as it is rendered, and each cell has its own coordinate, starting from 0.
- // Tabs are counted as [1..mTabSize] count empty spaces, depending on
- // how many space is necessary to reach the next tab stop.
- // For example, coordinate (1, 5) represents the character 'B' in a line "\tABC", when mTabSize = 4,
- // because it is rendered as " ABC" on the screen.
- struct Coordinates
- {
- int mLine, mColumn;
- Coordinates() : mLine(0), mColumn(0) {}
- Coordinates(int aLine, int aColumn) : mLine(aLine), mColumn(aColumn)
- {
- assert(aLine >= 0);
- assert(aColumn >= 0);
- }
- static Coordinates Invalid() { static Coordinates invalid(-1, -1); return invalid; }
- bool operator ==(const Coordinates& o) const
- {
- return
- mLine == o.mLine &&
- mColumn == o.mColumn;
- }
- bool operator !=(const Coordinates& o) const
- {
- return
- mLine != o.mLine ||
- mColumn != o.mColumn;
- }
- bool operator <(const Coordinates& o) const
- {
- if (mLine != o.mLine)
- return mLine < o.mLine;
- return mColumn < o.mColumn;
- }
- bool operator >(const Coordinates& o) const
- {
- if (mLine != o.mLine)
- return mLine > o.mLine;
- return mColumn > o.mColumn;
- }
- bool operator <=(const Coordinates& o) const
- {
- if (mLine != o.mLine)
- return mLine < o.mLine;
- return mColumn <= o.mColumn;
- }
- bool operator >=(const Coordinates& o) const
- {
- if (mLine != o.mLine)
- return mLine > o.mLine;
- return mColumn >= o.mColumn;
- }
- Coordinates operator -(const Coordinates& o)
- {
- return Coordinates(mLine - o.mLine, mColumn - o.mColumn);
- }
- Coordinates operator +(const Coordinates& o)
- {
- return Coordinates(mLine + o.mLine, mColumn + o.mColumn);
- }
- };
- struct Cursor
- {
- Coordinates mInteractiveStart = { 0, 0 };
- Coordinates mInteractiveEnd = { 0, 0 };
- inline Coordinates GetSelectionStart() const { return mInteractiveStart < mInteractiveEnd ? mInteractiveStart : mInteractiveEnd; }
- inline Coordinates GetSelectionEnd() const { return mInteractiveStart > mInteractiveEnd ? mInteractiveStart : mInteractiveEnd; }
- inline bool HasSelection() const { return mInteractiveStart != mInteractiveEnd; }
- };
- struct EditorState // state to be restored with undo/redo
- {
- int mCurrentCursor = 0;
- int mLastAddedCursor = 0;
- std::vector<Cursor> mCursors = { {{0,0}} };
- void AddCursor();
- int GetLastAddedCursorIndex();
- void SortCursorsFromTopToBottom();
- };
- struct Identifier
- {
- Coordinates mLocation;
- std::string mDeclaration;
- };
- typedef std::unordered_map<std::string, Identifier> Identifiers;
- typedef std::array<ImU32, (unsigned)PaletteIndex::Max> Palette;
- struct Glyph
- {
- char mChar;
- PaletteIndex mColorIndex = PaletteIndex::Default;
- bool mComment : 1;
- bool mMultiLineComment : 1;
- bool mPreprocessor : 1;
- Glyph(char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex),
- mComment(false), mMultiLineComment(false), mPreprocessor(false) {}
- };
- typedef std::vector<Glyph> Line;
- struct LanguageDefinition
- {
- typedef std::pair<std::string, PaletteIndex> TokenRegexString;
- typedef bool(*TokenizeCallback)(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex);
- std::string mName;
- std::unordered_set<std::string> mKeywords;
- Identifiers mIdentifiers;
- Identifiers mPreprocIdentifiers;
- std::string mCommentStart, mCommentEnd, mSingleLineComment;
- char mPreprocChar = '#';
- TokenizeCallback mTokenize = nullptr;
- std::vector<TokenRegexString> mTokenRegexStrings;
- bool mCaseSensitive = true;
- static const LanguageDefinition& Cpp();
- static const LanguageDefinition& Hlsl();
- static const LanguageDefinition& Glsl();
- static const LanguageDefinition& Python();
- static const LanguageDefinition& C();
- static const LanguageDefinition& Sql();
- static const LanguageDefinition& AngelScript();
- static const LanguageDefinition& Lua();
- static const LanguageDefinition& Cs();
- static const LanguageDefinition& Json();
- };
- enum class UndoOperationType { Add, Delete };
- struct UndoOperation
- {
- std::string mText;
- TextEditor::Coordinates mStart;
- TextEditor::Coordinates mEnd;
- UndoOperationType mType;
- };
- typedef std::vector<std::pair<boost::regex, PaletteIndex>> RegexList;
- class UndoRecord
- {
- public:
- UndoRecord() {}
- ~UndoRecord() {}
- UndoRecord(
- const std::vector<UndoOperation>& aOperations,
- TextEditor::EditorState& aBefore,
- TextEditor::EditorState& aAfter);
- void Undo(TextEditor* aEditor);
- void Redo(TextEditor* aEditor);
- std::vector<UndoOperation> mOperations;
- EditorState mBefore;
- EditorState mAfter;
- };
- std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const;
- std::string GetClipboardText() const;
- std::string GetSelectedText(int aCursor = -1) const;
- void SetCursorPosition(const Coordinates& aPosition, int aCursor = -1, bool aClearSelection = true);
- int InsertTextAt(Coordinates& aWhere, const char* aValue);
- void InsertTextAtCursor(const std::string& aValue, int aCursor = -1);
- void InsertTextAtCursor(const char* aValue, int aCursor = -1);
- enum class MoveDirection { Right = 0, Left = 1, Up = 2, Down = 3 };
- bool Move(int& aLine, int& aCharIndex, bool aLeft = false, bool aLockLine = false) const;
- void MoveCharIndexAndColumn(int aLine, int& aCharIndex, int& aColumn) const;
- void MoveCoords(Coordinates& aCoords, MoveDirection aDirection, bool aWordMode = false, int aLineCount = 1) const;
- void MoveUp(int aAmount = 1, bool aSelect = false);
- void MoveDown(int aAmount = 1, bool aSelect = false);
- void MoveLeft(bool aSelect = false, bool aWordMode = false);
- void MoveRight(bool aSelect = false, bool aWordMode = false);
- void MoveTop(bool aSelect = false);
- void MoveBottom(bool aSelect = false);
- void MoveHome(bool aSelect = false);
- void MoveEnd(bool aSelect = false);
- void EnterCharacter(ImWchar aChar, bool aShift);
- void Backspace(bool aWordMode = false);
- void Delete(bool aWordMode = false, const EditorState* aEditorState = nullptr);
- void SetSelection(Coordinates aStart, Coordinates aEnd, int aCursor = -1);
- void SetSelection(int aStartLine, int aStartChar, int aEndLine, int aEndChar, int aCursor = -1);
- void SelectNextOccurrenceOf(const char* aText, int aTextSize, int aCursor = -1, bool aCaseSensitive = true);
- void AddCursorForNextOccurrence(bool aCaseSensitive = true);
- bool FindNextOccurrence(const char* aText, int aTextSize, const Coordinates& aFrom, Coordinates& outStart, Coordinates& outEnd, bool aCaseSensitive = true);
- bool FindMatchingBracket(int aLine, int aCharIndex, Coordinates& out);
- void ChangeCurrentLinesIndentation(bool aIncrease);
- void MoveUpCurrentLines();
- void MoveDownCurrentLines();
- void ToggleLineComment();
- void RemoveCurrentLines();
- float TextDistanceToLineStart(const Coordinates& aFrom, bool aSanitizeCoords = true) const;
- void EnsureCursorVisible(int aCursor = -1, bool aStartToo = false);
- Coordinates SanitizeCoordinates(const Coordinates& aValue) const;
- Coordinates GetActualCursorCoordinates(int aCursor = -1, bool aStart = false) const;
- Coordinates ScreenPosToCoordinates(const ImVec2& aPosition, bool aInsertionMode = false, bool* isOverLineNumber = nullptr) const;
- Coordinates FindWordStart(const Coordinates& aFrom) const;
- Coordinates FindWordEnd(const Coordinates& aFrom) const;
- int GetCharacterIndexL(const Coordinates& aCoordinates) const;
- int GetCharacterIndexR(const Coordinates& aCoordinates) const;
- int GetCharacterColumn(int aLine, int aIndex) const;
- int GetFirstVisibleCharacterIndex(int aLine) const;
- int GetLineMaxColumn(int aLine, int aLimit = -1) const;
- Line& InsertLine(int aIndex);
- void RemoveLine(int aIndex, const std::unordered_set<int>* aHandledCursors = nullptr);
- void RemoveLines(int aStart, int aEnd);
- void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd);
- void DeleteSelection(int aCursor = -1);
- void RemoveGlyphsFromLine(int aLine, int aStartChar, int aEndChar = -1);
- void AddGlyphsToLine(int aLine, int aTargetIndex, Line::iterator aSourceStart, Line::iterator aSourceEnd);
- void AddGlyphToLine(int aLine, int aTargetIndex, Glyph aGlyph);
- ImU32 GetGlyphColor(const Glyph& aGlyph) const;
- void HandleKeyboardInputs(bool aParentIsFocused = false);
- void HandleMouseInputs();
- void UpdateViewVariables(float aScrollX, float aScrollY);
- void Render(bool aParentIsFocused = false);
- void OnCursorPositionChanged();
- void OnLineChanged(bool aBeforeChange, int aLine, int aColumn, int aCharCount, bool aDeleted);
- void MergeCursorsIfPossible();
- void AddUndo(UndoRecord& aValue);
- void Colorize(int aFromLine = 0, int aCount = -1);
- void ColorizeRange(int aFromLine = 0, int aToLine = 0);
- void ColorizeInternal();
- std::vector<Line> mLines;
- EditorState mState;
- std::vector<UndoRecord> mUndoBuffer;
- int mUndoIndex = 0;
- int mTabSize = 4;
- float mLineSpacing = 1.0f;
- bool mOverwrite = false;
- bool mReadOnly = false;
- bool mAutoIndent = true;
- bool mShowWhitespaces = true;
- bool mShowLineNumbers = true;
- bool mShortTabs = false;
- bool mTextChanged = false;
- int mSetViewAtLine = -1;
- SetViewAtLineMode mSetViewAtLineMode;
- int mEnsureCursorVisible = -1;
- bool mEnsureCursorVisibleStartToo = false;
- bool mScrollToTop = false;
- float mTextStart = 20.0f; // position (in pixels) where a code line starts relative to the left of the TextEditor.
- int mLeftMargin = 10;
- ImVec2 mCharAdvance;
- float mCurrentSpaceHeight = 20.0f;
- float mCurrentSpaceWidth = 20.0f;
- float mLastClickTime = -1.0f;
- ImVec2 mLastClickPos;
- int mFirstVisibleLine = 0;
- int mLastVisibleLine = 0;
- int mVisibleLineCount = 0;
- int mFirstVisibleColumn = 0;
- int mLastVisibleColumn = 0;
- int mVisibleColumnCount = 0;
- float mContentWidth = 0.0f;
- float mContentHeight = 0.0f;
- float mScrollX = 0.0f;
- float mScrollY = 0.0f;
- bool mPanning = false;
- bool mDraggingSelection = false;
- ImVec2 mLastMousePos;
- bool mCursorPositionChanged = false;
- bool mCursorOnBracket = false;
- Coordinates mMatchingBracketCoords;
- int mColorRangeMin = 0;
- int mColorRangeMax = 0;
- bool mCheckComments = true;
- PaletteId mPaletteId;
- Palette mPalette;
- LanguageDefinitionId mLanguageDefinitionId;
- const LanguageDefinition* mLanguageDefinition = nullptr;
- RegexList mRegexList;
- std::string mLineBuffer;
- inline bool IsHorizontalScrollbarVisible() const { return mCurrentSpaceWidth > mContentWidth; }
- inline bool IsVerticalScrollbarVisible() const { return mCurrentSpaceHeight > mContentHeight; }
- inline int TabSizeAtColumn(int aColumn) const { return mTabSize - (aColumn % mTabSize); }
- static const Palette& GetDarkPalette();
- static const Palette& GetMarianaPalette();
- static const Palette& GetLightPalette();
- static const Palette& GetRetroBluePalette();
- static const std::unordered_map<char, char> OPEN_TO_CLOSE_CHAR;
- static const std::unordered_map<char, char> CLOSE_TO_OPEN_CHAR;
- static PaletteId defaultPalette;
- };
|