├── .gitignore ├── LICENSE ├── CONTRIBUTING ├── README.md ├── TextEditor.h └── TextEditor.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | TextEditor.cpp.bak 34 | *.bak 35 | Save/TextEditor.h 36 | Save/TextEditor.cpp 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 BalazsJako 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Pull requests are welcome, feel free to contribute if you have implemented something which might be useful for the general audience of this little piece of software. Apparently, it became kind of a community project now. :) 3 | 4 | Whem contributing, please follow the following guidelines. I will keep it updated as we bump into something which worth doing better. 5 | - Try to follow the same coding and naming conventions you find in the source already. I know that everyone has its own preference/taste in coding, but please keep the source consistent in style. 6 | - Please submit to the 'dev' branch first for testing, and it will be merged to 'main' if it seems to work fine. I would like try keep 'master' in a good working condition, as more and more people are using it. 7 | - Please send your submissions in small, well defined requests, i. e. do not accumulate many unrelated changes in one large pull request. Keep your submissions as small as possible, it will make everyone's life easier. 8 | - Avoid using ImGui internal since it would make the source fragile against internal changes in ImGui. 9 | - Try to keep the perormance high within the render function. Try to avoid doing anything which leads to memory allocations (like using temporary std::string, std::vector variables), or complex algorithm. If you really have to, try to amortise it between frames. 10 | 11 | Thank you. :) 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImGuiColorTextEdit 2 | Syntax highlighting text editor for ImGui 3 | 4 | ![Screenshot](https://github.com/BalazsJako/ImGuiColorTextEdit/wiki/ImGuiTextEdit.png "Screenshot") 5 | 6 | Demo project: https://github.com/BalazsJako/ColorTextEditorDemo 7 | 8 | This started as my attempt to write a relatively simple widget which provides text editing functionality with syntax highlighting. Now there are other contributors who provide valuable additions. 9 | 10 | While it relies on Omar Cornut's https://github.com/ocornut/imgui, it does not follow the "pure" one widget - one function approach. Since the editor has to maintain a relatively complex and large internal state, it did not seem to be practical to try and enforce fully immediate mode. It stores its internal state in an object instance which is reused across frames. 11 | 12 | The code is (still) work in progress, please report if you find any issues. 13 | 14 | # Main features 15 | - approximates typical code editor look and feel (essential mouse/keyboard commands work - I mean, the commands _I_ normally use :)) 16 | - undo/redo 17 | - UTF-8 support 18 | - works with both fixed and variable-width fonts 19 | - extensible syntax highlighting for multiple languages 20 | - identifier declarations: a small piece of description can be associated with an identifier. The editor displays it in a tooltip when the mouse cursor is hovered over the identifier 21 | - error markers: the user can specify a list of error messages together the line of occurence, the editor will highligh the lines with red backround and display error message in a tooltip when the mouse cursor is hovered over the line 22 | - large files: there is no explicit limit set on file size or number of lines (below 2GB, performance is not affected when large files are loaded (except syntax coloring, see below) 23 | - color palette support: you can switch between different color palettes, or even define your own 24 | - whitespace indicators (TAB, space) 25 | 26 | # Known issues 27 | - syntax highligthing of most languages - except C/C++ - is based on std::regex, which is diasppointingly slow. Because of that, the highlighting process is amortized between multiple frames. C/C++ has a hand-written tokenizer which is much faster. 28 | 29 | Please post your screenshots if you find this little piece of software useful. :) 30 | 31 | # Contribute 32 | 33 | If you want to contribute, please refer to CONTRIBUTE file. 34 | -------------------------------------------------------------------------------- /TextEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "imgui.h" 12 | 13 | class TextEditor 14 | { 15 | public: 16 | enum class PaletteIndex 17 | { 18 | Default, 19 | Keyword, 20 | Number, 21 | String, 22 | CharLiteral, 23 | Punctuation, 24 | Preprocessor, 25 | Identifier, 26 | KnownIdentifier, 27 | PreprocIdentifier, 28 | Comment, 29 | MultiLineComment, 30 | Background, 31 | Cursor, 32 | Selection, 33 | ErrorMarker, 34 | Breakpoint, 35 | LineNumber, 36 | CurrentLineFill, 37 | CurrentLineFillInactive, 38 | CurrentLineEdge, 39 | Max 40 | }; 41 | 42 | enum class SelectionMode 43 | { 44 | Normal, 45 | Word, 46 | Line 47 | }; 48 | 49 | struct Breakpoint 50 | { 51 | int mLine; 52 | bool mEnabled; 53 | std::string mCondition; 54 | 55 | Breakpoint() 56 | : mLine(-1) 57 | , mEnabled(false) 58 | {} 59 | }; 60 | 61 | // Represents a character coordinate from the user's point of view, 62 | // i. e. consider an uniform grid (assuming fixed-width font) on the 63 | // screen as it is rendered, and each cell has its own coordinate, starting from 0. 64 | // Tabs are counted as [1..mTabSize] count empty spaces, depending on 65 | // how many space is necessary to reach the next tab stop. 66 | // For example, coordinate (1, 5) represents the character 'B' in a line "\tABC", when mTabSize = 4, 67 | // because it is rendered as " ABC" on the screen. 68 | struct Coordinates 69 | { 70 | int mLine, mColumn; 71 | Coordinates() : mLine(0), mColumn(0) {} 72 | Coordinates(int aLine, int aColumn) : mLine(aLine), mColumn(aColumn) 73 | { 74 | assert(aLine >= 0); 75 | assert(aColumn >= 0); 76 | } 77 | static Coordinates Invalid() { static Coordinates invalid(-1, -1); return invalid; } 78 | 79 | bool operator ==(const Coordinates& o) const 80 | { 81 | return 82 | mLine == o.mLine && 83 | mColumn == o.mColumn; 84 | } 85 | 86 | bool operator !=(const Coordinates& o) const 87 | { 88 | return 89 | mLine != o.mLine || 90 | mColumn != o.mColumn; 91 | } 92 | 93 | bool operator <(const Coordinates& o) const 94 | { 95 | if (mLine != o.mLine) 96 | return mLine < o.mLine; 97 | return mColumn < o.mColumn; 98 | } 99 | 100 | bool operator >(const Coordinates& o) const 101 | { 102 | if (mLine != o.mLine) 103 | return mLine > o.mLine; 104 | return mColumn > o.mColumn; 105 | } 106 | 107 | bool operator <=(const Coordinates& o) const 108 | { 109 | if (mLine != o.mLine) 110 | return mLine < o.mLine; 111 | return mColumn <= o.mColumn; 112 | } 113 | 114 | bool operator >=(const Coordinates& o) const 115 | { 116 | if (mLine != o.mLine) 117 | return mLine > o.mLine; 118 | return mColumn >= o.mColumn; 119 | } 120 | }; 121 | 122 | struct Identifier 123 | { 124 | Coordinates mLocation; 125 | std::string mDeclaration; 126 | }; 127 | 128 | typedef std::string String; 129 | typedef std::unordered_map Identifiers; 130 | typedef std::unordered_set Keywords; 131 | typedef std::map ErrorMarkers; 132 | typedef std::unordered_set Breakpoints; 133 | typedef std::array Palette; 134 | typedef uint8_t Char; 135 | 136 | struct Glyph 137 | { 138 | Char mChar; 139 | PaletteIndex mColorIndex = PaletteIndex::Default; 140 | bool mComment : 1; 141 | bool mMultiLineComment : 1; 142 | bool mPreprocessor : 1; 143 | 144 | Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex), 145 | mComment(false), mMultiLineComment(false), mPreprocessor(false) {} 146 | }; 147 | 148 | typedef std::vector Line; 149 | typedef std::vector Lines; 150 | 151 | struct LanguageDefinition 152 | { 153 | typedef std::pair TokenRegexString; 154 | typedef std::vector TokenRegexStrings; 155 | typedef bool(*TokenizeCallback)(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex); 156 | 157 | std::string mName; 158 | Keywords mKeywords; 159 | Identifiers mIdentifiers; 160 | Identifiers mPreprocIdentifiers; 161 | std::string mCommentStart, mCommentEnd, mSingleLineComment; 162 | char mPreprocChar; 163 | bool mAutoIndentation; 164 | 165 | TokenizeCallback mTokenize; 166 | 167 | TokenRegexStrings mTokenRegexStrings; 168 | 169 | bool mCaseSensitive; 170 | 171 | LanguageDefinition() 172 | : mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true) 173 | { 174 | } 175 | 176 | static const LanguageDefinition& CPlusPlus(); 177 | static const LanguageDefinition& HLSL(); 178 | static const LanguageDefinition& GLSL(); 179 | static const LanguageDefinition& C(); 180 | static const LanguageDefinition& SQL(); 181 | static const LanguageDefinition& AngelScript(); 182 | static const LanguageDefinition& Lua(); 183 | }; 184 | 185 | TextEditor(); 186 | ~TextEditor(); 187 | 188 | void SetLanguageDefinition(const LanguageDefinition& aLanguageDef); 189 | const LanguageDefinition& GetLanguageDefinition() const { return mLanguageDefinition; } 190 | 191 | const Palette& GetPalette() const { return mPaletteBase; } 192 | void SetPalette(const Palette& aValue); 193 | 194 | void SetErrorMarkers(const ErrorMarkers& aMarkers) { mErrorMarkers = aMarkers; } 195 | void SetBreakpoints(const Breakpoints& aMarkers) { mBreakpoints = aMarkers; } 196 | 197 | void Render(const char* aTitle, const ImVec2& aSize = ImVec2(), bool aBorder = false); 198 | void SetText(const std::string& aText); 199 | std::string GetText() const; 200 | 201 | void SetTextLines(const std::vector& aLines); 202 | std::vector GetTextLines() const; 203 | 204 | std::string GetSelectedText() const; 205 | std::string GetCurrentLineText()const; 206 | 207 | int GetTotalLines() const { return (int)mLines.size(); } 208 | bool IsOverwrite() const { return mOverwrite; } 209 | 210 | void SetReadOnly(bool aValue); 211 | bool IsReadOnly() const { return mReadOnly; } 212 | bool IsTextChanged() const { return mTextChanged; } 213 | bool IsCursorPositionChanged() const { return mCursorPositionChanged; } 214 | 215 | bool IsColorizerEnabled() const { return mColorizerEnabled; } 216 | void SetColorizerEnable(bool aValue); 217 | 218 | Coordinates GetCursorPosition() const { return GetActualCursorCoordinates(); } 219 | void SetCursorPosition(const Coordinates& aPosition); 220 | 221 | inline void SetHandleMouseInputs (bool aValue){ mHandleMouseInputs = aValue;} 222 | inline bool IsHandleMouseInputsEnabled() const { return mHandleKeyboardInputs; } 223 | 224 | inline void SetHandleKeyboardInputs (bool aValue){ mHandleKeyboardInputs = aValue;} 225 | inline bool IsHandleKeyboardInputsEnabled() const { return mHandleKeyboardInputs; } 226 | 227 | inline void SetImGuiChildIgnored (bool aValue){ mIgnoreImGuiChild = aValue;} 228 | inline bool IsImGuiChildIgnored() const { return mIgnoreImGuiChild; } 229 | 230 | inline void SetShowWhitespaces(bool aValue) { mShowWhitespaces = aValue; } 231 | inline bool IsShowingWhitespaces() const { return mShowWhitespaces; } 232 | 233 | void SetTabSize(int aValue); 234 | inline int GetTabSize() const { return mTabSize; } 235 | 236 | void InsertText(const std::string& aValue); 237 | void InsertText(const char* aValue); 238 | 239 | void MoveUp(int aAmount = 1, bool aSelect = false); 240 | void MoveDown(int aAmount = 1, bool aSelect = false); 241 | void MoveLeft(int aAmount = 1, bool aSelect = false, bool aWordMode = false); 242 | void MoveRight(int aAmount = 1, bool aSelect = false, bool aWordMode = false); 243 | void MoveTop(bool aSelect = false); 244 | void MoveBottom(bool aSelect = false); 245 | void MoveHome(bool aSelect = false); 246 | void MoveEnd(bool aSelect = false); 247 | 248 | void SetSelectionStart(const Coordinates& aPosition); 249 | void SetSelectionEnd(const Coordinates& aPosition); 250 | void SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode = SelectionMode::Normal); 251 | void SelectWordUnderCursor(); 252 | void SelectAll(); 253 | bool HasSelection() const; 254 | 255 | void Copy(); 256 | void Cut(); 257 | void Paste(); 258 | void Delete(); 259 | 260 | bool CanUndo() const; 261 | bool CanRedo() const; 262 | void Undo(int aSteps = 1); 263 | void Redo(int aSteps = 1); 264 | 265 | static const Palette& GetDarkPalette(); 266 | static const Palette& GetLightPalette(); 267 | static const Palette& GetRetroBluePalette(); 268 | 269 | private: 270 | typedef std::vector> RegexList; 271 | 272 | struct EditorState 273 | { 274 | Coordinates mSelectionStart; 275 | Coordinates mSelectionEnd; 276 | Coordinates mCursorPosition; 277 | }; 278 | 279 | class UndoRecord 280 | { 281 | public: 282 | UndoRecord() {} 283 | ~UndoRecord() {} 284 | 285 | UndoRecord( 286 | const std::string& aAdded, 287 | const TextEditor::Coordinates aAddedStart, 288 | const TextEditor::Coordinates aAddedEnd, 289 | 290 | const std::string& aRemoved, 291 | const TextEditor::Coordinates aRemovedStart, 292 | const TextEditor::Coordinates aRemovedEnd, 293 | 294 | TextEditor::EditorState& aBefore, 295 | TextEditor::EditorState& aAfter); 296 | 297 | void Undo(TextEditor* aEditor); 298 | void Redo(TextEditor* aEditor); 299 | 300 | std::string mAdded; 301 | Coordinates mAddedStart; 302 | Coordinates mAddedEnd; 303 | 304 | std::string mRemoved; 305 | Coordinates mRemovedStart; 306 | Coordinates mRemovedEnd; 307 | 308 | EditorState mBefore; 309 | EditorState mAfter; 310 | }; 311 | 312 | typedef std::vector UndoBuffer; 313 | 314 | void ProcessInputs(); 315 | void Colorize(int aFromLine = 0, int aCount = -1); 316 | void ColorizeRange(int aFromLine = 0, int aToLine = 0); 317 | void ColorizeInternal(); 318 | float TextDistanceToLineStart(const Coordinates& aFrom) const; 319 | void EnsureCursorVisible(); 320 | int GetPageSize() const; 321 | std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const; 322 | Coordinates GetActualCursorCoordinates() const; 323 | Coordinates SanitizeCoordinates(const Coordinates& aValue) const; 324 | void Advance(Coordinates& aCoordinates) const; 325 | void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd); 326 | int InsertTextAt(Coordinates& aWhere, const char* aValue); 327 | void AddUndo(UndoRecord& aValue); 328 | Coordinates ScreenPosToCoordinates(const ImVec2& aPosition) const; 329 | Coordinates FindWordStart(const Coordinates& aFrom) const; 330 | Coordinates FindWordEnd(const Coordinates& aFrom) const; 331 | Coordinates FindNextWord(const Coordinates& aFrom) const; 332 | int GetCharacterIndex(const Coordinates& aCoordinates) const; 333 | int GetCharacterColumn(int aLine, int aIndex) const; 334 | int GetLineCharacterCount(int aLine) const; 335 | int GetLineMaxColumn(int aLine) const; 336 | bool IsOnWordBoundary(const Coordinates& aAt) const; 337 | void RemoveLine(int aStart, int aEnd); 338 | void RemoveLine(int aIndex); 339 | Line& InsertLine(int aIndex); 340 | void EnterCharacter(ImWchar aChar, bool aShift); 341 | void Backspace(); 342 | void DeleteSelection(); 343 | std::string GetWordUnderCursor() const; 344 | std::string GetWordAt(const Coordinates& aCoords) const; 345 | ImU32 GetGlyphColor(const Glyph& aGlyph) const; 346 | 347 | void HandleKeyboardInputs(); 348 | void HandleMouseInputs(); 349 | void Render(); 350 | 351 | float mLineSpacing; 352 | Lines mLines; 353 | EditorState mState; 354 | UndoBuffer mUndoBuffer; 355 | int mUndoIndex; 356 | 357 | int mTabSize; 358 | bool mOverwrite; 359 | bool mReadOnly; 360 | bool mWithinRender; 361 | bool mScrollToCursor; 362 | bool mScrollToTop; 363 | bool mTextChanged; 364 | bool mColorizerEnabled; 365 | float mTextStart; // position (in pixels) where a code line starts relative to the left of the TextEditor. 366 | int mLeftMargin; 367 | bool mCursorPositionChanged; 368 | int mColorRangeMin, mColorRangeMax; 369 | SelectionMode mSelectionMode; 370 | bool mHandleKeyboardInputs; 371 | bool mHandleMouseInputs; 372 | bool mIgnoreImGuiChild; 373 | bool mShowWhitespaces; 374 | 375 | Palette mPaletteBase; 376 | Palette mPalette; 377 | LanguageDefinition mLanguageDefinition; 378 | RegexList mRegexList; 379 | 380 | bool mCheckComments; 381 | Breakpoints mBreakpoints; 382 | ErrorMarkers mErrorMarkers; 383 | ImVec2 mCharAdvance; 384 | Coordinates mInteractiveStart, mInteractiveEnd; 385 | std::string mLineBuffer; 386 | uint64_t mStartTime; 387 | 388 | float mLastClick; 389 | }; 390 | -------------------------------------------------------------------------------- /TextEditor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "TextEditor.h" 8 | 9 | #include "imgui.h" 10 | 11 | // TODO 12 | // - multiline comments vs single-line: latter is blocking start of a ML 13 | 14 | template 15 | bool equals(InputIt1 first1, InputIt1 last1, 16 | InputIt2 first2, InputIt2 last2, BinaryPredicate p) 17 | { 18 | for (; first1 != last1 && first2 != last2; ++first1, ++first2) 19 | { 20 | if (!p(*first1, *first2)) 21 | return false; 22 | } 23 | return first1 == last1 && first2 == last2; 24 | } 25 | 26 | TextEditor::TextEditor() 27 | : mLineSpacing(1.0f) 28 | , mUndoIndex(0) 29 | , mTabSize(4) 30 | , mOverwrite(false) 31 | , mReadOnly(false) 32 | , mWithinRender(false) 33 | , mScrollToCursor(false) 34 | , mScrollToTop(false) 35 | , mTextChanged(false) 36 | , mColorizerEnabled(true) 37 | , mTextStart(20.0f) 38 | , mLeftMargin(10) 39 | , mCursorPositionChanged(false) 40 | , mColorRangeMin(0) 41 | , mColorRangeMax(0) 42 | , mSelectionMode(SelectionMode::Normal) 43 | , mCheckComments(true) 44 | , mLastClick(-1.0f) 45 | , mHandleKeyboardInputs(true) 46 | , mHandleMouseInputs(true) 47 | , mIgnoreImGuiChild(false) 48 | , mShowWhitespaces(true) 49 | , mStartTime(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) 50 | { 51 | SetPalette(GetDarkPalette()); 52 | SetLanguageDefinition(LanguageDefinition::HLSL()); 53 | mLines.push_back(Line()); 54 | } 55 | 56 | TextEditor::~TextEditor() 57 | { 58 | } 59 | 60 | void TextEditor::SetLanguageDefinition(const LanguageDefinition & aLanguageDef) 61 | { 62 | mLanguageDefinition = aLanguageDef; 63 | mRegexList.clear(); 64 | 65 | for (auto& r : mLanguageDefinition.mTokenRegexStrings) 66 | mRegexList.push_back(std::make_pair(std::regex(r.first, std::regex_constants::optimize), r.second)); 67 | 68 | Colorize(); 69 | } 70 | 71 | void TextEditor::SetPalette(const Palette & aValue) 72 | { 73 | mPaletteBase = aValue; 74 | } 75 | 76 | std::string TextEditor::GetText(const Coordinates & aStart, const Coordinates & aEnd) const 77 | { 78 | std::string result; 79 | 80 | auto lstart = aStart.mLine; 81 | auto lend = aEnd.mLine; 82 | auto istart = GetCharacterIndex(aStart); 83 | auto iend = GetCharacterIndex(aEnd); 84 | size_t s = 0; 85 | 86 | for (size_t i = lstart; i < lend; i++) 87 | s += mLines[i].size(); 88 | 89 | result.reserve(s + s / 8); 90 | 91 | while (istart < iend || lstart < lend) 92 | { 93 | if (lstart >= (int)mLines.size()) 94 | break; 95 | 96 | auto& line = mLines[lstart]; 97 | if (istart < (int)line.size()) 98 | { 99 | result += line[istart].mChar; 100 | istart++; 101 | } 102 | else 103 | { 104 | istart = 0; 105 | ++lstart; 106 | result += '\n'; 107 | } 108 | } 109 | 110 | return result; 111 | } 112 | 113 | TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const 114 | { 115 | return SanitizeCoordinates(mState.mCursorPosition); 116 | } 117 | 118 | TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates & aValue) const 119 | { 120 | auto line = aValue.mLine; 121 | auto column = aValue.mColumn; 122 | if (line >= (int)mLines.size()) 123 | { 124 | if (mLines.empty()) 125 | { 126 | line = 0; 127 | column = 0; 128 | } 129 | else 130 | { 131 | line = (int)mLines.size() - 1; 132 | column = GetLineMaxColumn(line); 133 | } 134 | return Coordinates(line, column); 135 | } 136 | else 137 | { 138 | column = mLines.empty() ? 0 : std::min(column, GetLineMaxColumn(line)); 139 | return Coordinates(line, column); 140 | } 141 | } 142 | 143 | // https://en.wikipedia.org/wiki/UTF-8 144 | // We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code) 145 | static int UTF8CharLength(TextEditor::Char c) 146 | { 147 | if ((c & 0xFE) == 0xFC) 148 | return 6; 149 | if ((c & 0xFC) == 0xF8) 150 | return 5; 151 | if ((c & 0xF8) == 0xF0) 152 | return 4; 153 | else if ((c & 0xF0) == 0xE0) 154 | return 3; 155 | else if ((c & 0xE0) == 0xC0) 156 | return 2; 157 | return 1; 158 | } 159 | 160 | // "Borrowed" from ImGui source 161 | static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) 162 | { 163 | if (c < 0x80) 164 | { 165 | buf[0] = (char)c; 166 | return 1; 167 | } 168 | if (c < 0x800) 169 | { 170 | if (buf_size < 2) return 0; 171 | buf[0] = (char)(0xc0 + (c >> 6)); 172 | buf[1] = (char)(0x80 + (c & 0x3f)); 173 | return 2; 174 | } 175 | if (c >= 0xdc00 && c < 0xe000) 176 | { 177 | return 0; 178 | } 179 | if (c >= 0xd800 && c < 0xdc00) 180 | { 181 | if (buf_size < 4) return 0; 182 | buf[0] = (char)(0xf0 + (c >> 18)); 183 | buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); 184 | buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); 185 | buf[3] = (char)(0x80 + ((c) & 0x3f)); 186 | return 4; 187 | } 188 | //else if (c < 0x10000) 189 | { 190 | if (buf_size < 3) return 0; 191 | buf[0] = (char)(0xe0 + (c >> 12)); 192 | buf[1] = (char)(0x80 + ((c >> 6) & 0x3f)); 193 | buf[2] = (char)(0x80 + ((c) & 0x3f)); 194 | return 3; 195 | } 196 | } 197 | 198 | void TextEditor::Advance(Coordinates & aCoordinates) const 199 | { 200 | if (aCoordinates.mLine < (int)mLines.size()) 201 | { 202 | auto& line = mLines[aCoordinates.mLine]; 203 | auto cindex = GetCharacterIndex(aCoordinates); 204 | 205 | if (cindex + 1 < (int)line.size()) 206 | { 207 | auto delta = UTF8CharLength(line[cindex].mChar); 208 | cindex = std::min(cindex + delta, (int)line.size() - 1); 209 | } 210 | else 211 | { 212 | ++aCoordinates.mLine; 213 | cindex = 0; 214 | } 215 | aCoordinates.mColumn = GetCharacterColumn(aCoordinates.mLine, cindex); 216 | } 217 | } 218 | 219 | void TextEditor::DeleteRange(const Coordinates & aStart, const Coordinates & aEnd) 220 | { 221 | assert(aEnd >= aStart); 222 | assert(!mReadOnly); 223 | 224 | //printf("D(%d.%d)-(%d.%d)\n", aStart.mLine, aStart.mColumn, aEnd.mLine, aEnd.mColumn); 225 | 226 | if (aEnd == aStart) 227 | return; 228 | 229 | auto start = GetCharacterIndex(aStart); 230 | auto end = GetCharacterIndex(aEnd); 231 | 232 | if (aStart.mLine == aEnd.mLine) 233 | { 234 | auto& line = mLines[aStart.mLine]; 235 | auto n = GetLineMaxColumn(aStart.mLine); 236 | if (aEnd.mColumn >= n) 237 | line.erase(line.begin() + start, line.end()); 238 | else 239 | line.erase(line.begin() + start, line.begin() + end); 240 | } 241 | else 242 | { 243 | auto& firstLine = mLines[aStart.mLine]; 244 | auto& lastLine = mLines[aEnd.mLine]; 245 | 246 | firstLine.erase(firstLine.begin() + start, firstLine.end()); 247 | lastLine.erase(lastLine.begin(), lastLine.begin() + end); 248 | 249 | if (aStart.mLine < aEnd.mLine) 250 | firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end()); 251 | 252 | if (aStart.mLine < aEnd.mLine) 253 | RemoveLine(aStart.mLine + 1, aEnd.mLine + 1); 254 | } 255 | 256 | mTextChanged = true; 257 | } 258 | 259 | int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char * aValue) 260 | { 261 | assert(!mReadOnly); 262 | 263 | int cindex = GetCharacterIndex(aWhere); 264 | int totalLines = 0; 265 | while (*aValue != '\0') 266 | { 267 | assert(!mLines.empty()); 268 | 269 | if (*aValue == '\r') 270 | { 271 | // skip 272 | ++aValue; 273 | } 274 | else if (*aValue == '\n') 275 | { 276 | if (cindex < (int)mLines[aWhere.mLine].size()) 277 | { 278 | auto& newLine = InsertLine(aWhere.mLine + 1); 279 | auto& line = mLines[aWhere.mLine]; 280 | newLine.insert(newLine.begin(), line.begin() + cindex, line.end()); 281 | line.erase(line.begin() + cindex, line.end()); 282 | } 283 | else 284 | { 285 | InsertLine(aWhere.mLine + 1); 286 | } 287 | ++aWhere.mLine; 288 | aWhere.mColumn = 0; 289 | cindex = 0; 290 | ++totalLines; 291 | ++aValue; 292 | } 293 | else 294 | { 295 | auto& line = mLines[aWhere.mLine]; 296 | auto d = UTF8CharLength(*aValue); 297 | while (d-- > 0 && *aValue != '\0') 298 | line.insert(line.begin() + cindex++, Glyph(*aValue++, PaletteIndex::Default)); 299 | ++aWhere.mColumn; 300 | } 301 | 302 | mTextChanged = true; 303 | } 304 | 305 | return totalLines; 306 | } 307 | 308 | void TextEditor::AddUndo(UndoRecord& aValue) 309 | { 310 | assert(!mReadOnly); 311 | //printf("AddUndo: (@%d.%d) +\'%s' [%d.%d .. %d.%d], -\'%s', [%d.%d .. %d.%d] (@%d.%d)\n", 312 | // aValue.mBefore.mCursorPosition.mLine, aValue.mBefore.mCursorPosition.mColumn, 313 | // aValue.mAdded.c_str(), aValue.mAddedStart.mLine, aValue.mAddedStart.mColumn, aValue.mAddedEnd.mLine, aValue.mAddedEnd.mColumn, 314 | // aValue.mRemoved.c_str(), aValue.mRemovedStart.mLine, aValue.mRemovedStart.mColumn, aValue.mRemovedEnd.mLine, aValue.mRemovedEnd.mColumn, 315 | // aValue.mAfter.mCursorPosition.mLine, aValue.mAfter.mCursorPosition.mColumn 316 | // ); 317 | 318 | mUndoBuffer.resize((size_t)(mUndoIndex + 1)); 319 | mUndoBuffer.back() = aValue; 320 | ++mUndoIndex; 321 | } 322 | 323 | TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition) const 324 | { 325 | ImVec2 origin = ImGui::GetCursorScreenPos(); 326 | ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y); 327 | 328 | int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y)); 329 | 330 | int columnCoord = 0; 331 | 332 | if (lineNo >= 0 && lineNo < (int)mLines.size()) 333 | { 334 | auto& line = mLines.at(lineNo); 335 | 336 | int columnIndex = 0; 337 | float columnX = 0.0f; 338 | 339 | while ((size_t)columnIndex < line.size()) 340 | { 341 | float columnWidth = 0.0f; 342 | 343 | if (line[columnIndex].mChar == '\t') 344 | { 345 | float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ").x; 346 | float oldX = columnX; 347 | float newColumnX = (1.0f + std::floor((1.0f + columnX) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); 348 | columnWidth = newColumnX - oldX; 349 | if (mTextStart + columnX + columnWidth * 0.5f > local.x) 350 | break; 351 | columnX = newColumnX; 352 | columnCoord = (columnCoord / mTabSize) * mTabSize + mTabSize; 353 | columnIndex++; 354 | } 355 | else 356 | { 357 | char buf[7]; 358 | auto d = UTF8CharLength(line[columnIndex].mChar); 359 | int i = 0; 360 | while (i < 6 && d-- > 0) 361 | buf[i++] = line[columnIndex++].mChar; 362 | buf[i] = '\0'; 363 | columnWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf).x; 364 | if (mTextStart + columnX + columnWidth * 0.5f > local.x) 365 | break; 366 | columnX += columnWidth; 367 | columnCoord++; 368 | } 369 | } 370 | } 371 | 372 | return SanitizeCoordinates(Coordinates(lineNo, columnCoord)); 373 | } 374 | 375 | TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates & aFrom) const 376 | { 377 | Coordinates at = aFrom; 378 | if (at.mLine >= (int)mLines.size()) 379 | return at; 380 | 381 | auto& line = mLines[at.mLine]; 382 | auto cindex = GetCharacterIndex(at); 383 | 384 | if (cindex >= (int)line.size()) 385 | return at; 386 | 387 | while (cindex > 0 && isspace(line[cindex].mChar)) 388 | --cindex; 389 | 390 | auto cstart = (PaletteIndex)line[cindex].mColorIndex; 391 | while (cindex > 0) 392 | { 393 | auto c = line[cindex].mChar; 394 | if ((c & 0xC0) != 0x80) // not UTF code sequence 10xxxxxx 395 | { 396 | if (c <= 32 && isspace(c)) 397 | { 398 | cindex++; 399 | break; 400 | } 401 | if (cstart != (PaletteIndex)line[size_t(cindex - 1)].mColorIndex) 402 | break; 403 | } 404 | --cindex; 405 | } 406 | return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex)); 407 | } 408 | 409 | TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates & aFrom) const 410 | { 411 | Coordinates at = aFrom; 412 | if (at.mLine >= (int)mLines.size()) 413 | return at; 414 | 415 | auto& line = mLines[at.mLine]; 416 | auto cindex = GetCharacterIndex(at); 417 | 418 | if (cindex >= (int)line.size()) 419 | return at; 420 | 421 | bool prevspace = isspace(line[cindex].mChar) != 0; 422 | auto cstart = (PaletteIndex)line[cindex].mColorIndex; 423 | while (cindex < (int)line.size()) 424 | { 425 | auto c = line[cindex].mChar; 426 | auto d = UTF8CharLength(c); 427 | if (cstart != (PaletteIndex)line[cindex].mColorIndex) 428 | break; 429 | 430 | if (prevspace != !!isspace(c)) 431 | { 432 | if (isspace(c)) 433 | while (cindex < (int)line.size() && isspace(line[cindex].mChar)) 434 | ++cindex; 435 | break; 436 | } 437 | cindex += d; 438 | } 439 | return Coordinates(aFrom.mLine, GetCharacterColumn(aFrom.mLine, cindex)); 440 | } 441 | 442 | TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates & aFrom) const 443 | { 444 | Coordinates at = aFrom; 445 | if (at.mLine >= (int)mLines.size()) 446 | return at; 447 | 448 | // skip to the next non-word character 449 | auto cindex = GetCharacterIndex(aFrom); 450 | bool isword = false; 451 | bool skip = false; 452 | if (cindex < (int)mLines[at.mLine].size()) 453 | { 454 | auto& line = mLines[at.mLine]; 455 | isword = isalnum(line[cindex].mChar) != 0; 456 | skip = isword; 457 | } 458 | 459 | while (!isword || skip) 460 | { 461 | if (at.mLine >= mLines.size()) 462 | { 463 | auto l = std::max(0, (int) mLines.size() - 1); 464 | return Coordinates(l, GetLineMaxColumn(l)); 465 | } 466 | 467 | auto& line = mLines[at.mLine]; 468 | if (cindex < (int)line.size()) 469 | { 470 | isword = isalnum(line[cindex].mChar) != 0; 471 | 472 | if (isword && !skip) 473 | return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex)); 474 | 475 | if (!isword) 476 | skip = false; 477 | 478 | cindex++; 479 | } 480 | else 481 | { 482 | cindex = 0; 483 | ++at.mLine; 484 | skip = false; 485 | isword = false; 486 | } 487 | } 488 | 489 | return at; 490 | } 491 | 492 | int TextEditor::GetCharacterIndex(const Coordinates& aCoordinates) const 493 | { 494 | if (aCoordinates.mLine >= mLines.size()) 495 | return -1; 496 | auto& line = mLines[aCoordinates.mLine]; 497 | int c = 0; 498 | int i = 0; 499 | for (; i < line.size() && c < aCoordinates.mColumn;) 500 | { 501 | if (line[i].mChar == '\t') 502 | c = (c / mTabSize) * mTabSize + mTabSize; 503 | else 504 | ++c; 505 | i += UTF8CharLength(line[i].mChar); 506 | } 507 | return i; 508 | } 509 | 510 | int TextEditor::GetCharacterColumn(int aLine, int aIndex) const 511 | { 512 | if (aLine >= mLines.size()) 513 | return 0; 514 | auto& line = mLines[aLine]; 515 | int col = 0; 516 | int i = 0; 517 | while (i < aIndex && i < (int)line.size()) 518 | { 519 | auto c = line[i].mChar; 520 | i += UTF8CharLength(c); 521 | if (c == '\t') 522 | col = (col / mTabSize) * mTabSize + mTabSize; 523 | else 524 | col++; 525 | } 526 | return col; 527 | } 528 | 529 | int TextEditor::GetLineCharacterCount(int aLine) const 530 | { 531 | if (aLine >= mLines.size()) 532 | return 0; 533 | auto& line = mLines[aLine]; 534 | int c = 0; 535 | for (unsigned i = 0; i < line.size(); c++) 536 | i += UTF8CharLength(line[i].mChar); 537 | return c; 538 | } 539 | 540 | int TextEditor::GetLineMaxColumn(int aLine) const 541 | { 542 | if (aLine >= mLines.size()) 543 | return 0; 544 | auto& line = mLines[aLine]; 545 | int col = 0; 546 | for (unsigned i = 0; i < line.size(); ) 547 | { 548 | auto c = line[i].mChar; 549 | if (c == '\t') 550 | col = (col / mTabSize) * mTabSize + mTabSize; 551 | else 552 | col++; 553 | i += UTF8CharLength(c); 554 | } 555 | return col; 556 | } 557 | 558 | bool TextEditor::IsOnWordBoundary(const Coordinates & aAt) const 559 | { 560 | if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0) 561 | return true; 562 | 563 | auto& line = mLines[aAt.mLine]; 564 | auto cindex = GetCharacterIndex(aAt); 565 | if (cindex >= (int)line.size()) 566 | return true; 567 | 568 | if (mColorizerEnabled) 569 | return line[cindex].mColorIndex != line[size_t(cindex - 1)].mColorIndex; 570 | 571 | return isspace(line[cindex].mChar) != isspace(line[cindex - 1].mChar); 572 | } 573 | 574 | void TextEditor::RemoveLine(int aStart, int aEnd) 575 | { 576 | assert(!mReadOnly); 577 | assert(aEnd >= aStart); 578 | assert(mLines.size() > (size_t)(aEnd - aStart)); 579 | 580 | ErrorMarkers etmp; 581 | for (auto& i : mErrorMarkers) 582 | { 583 | ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second); 584 | if (e.first >= aStart && e.first <= aEnd) 585 | continue; 586 | etmp.insert(e); 587 | } 588 | mErrorMarkers = std::move(etmp); 589 | 590 | Breakpoints btmp; 591 | for (auto i : mBreakpoints) 592 | { 593 | if (i >= aStart && i <= aEnd) 594 | continue; 595 | btmp.insert(i >= aStart ? i - 1 : i); 596 | } 597 | mBreakpoints = std::move(btmp); 598 | 599 | mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd); 600 | assert(!mLines.empty()); 601 | 602 | mTextChanged = true; 603 | } 604 | 605 | void TextEditor::RemoveLine(int aIndex) 606 | { 607 | assert(!mReadOnly); 608 | assert(mLines.size() > 1); 609 | 610 | ErrorMarkers etmp; 611 | for (auto& i : mErrorMarkers) 612 | { 613 | ErrorMarkers::value_type e(i.first > aIndex ? i.first - 1 : i.first, i.second); 614 | if (e.first - 1 == aIndex) 615 | continue; 616 | etmp.insert(e); 617 | } 618 | mErrorMarkers = std::move(etmp); 619 | 620 | Breakpoints btmp; 621 | for (auto i : mBreakpoints) 622 | { 623 | if (i == aIndex) 624 | continue; 625 | btmp.insert(i >= aIndex ? i - 1 : i); 626 | } 627 | mBreakpoints = std::move(btmp); 628 | 629 | mLines.erase(mLines.begin() + aIndex); 630 | assert(!mLines.empty()); 631 | 632 | mTextChanged = true; 633 | } 634 | 635 | TextEditor::Line& TextEditor::InsertLine(int aIndex) 636 | { 637 | assert(!mReadOnly); 638 | 639 | auto& result = *mLines.insert(mLines.begin() + aIndex, Line()); 640 | 641 | ErrorMarkers etmp; 642 | for (auto& i : mErrorMarkers) 643 | etmp.insert(ErrorMarkers::value_type(i.first >= aIndex ? i.first + 1 : i.first, i.second)); 644 | mErrorMarkers = std::move(etmp); 645 | 646 | Breakpoints btmp; 647 | for (auto i : mBreakpoints) 648 | btmp.insert(i >= aIndex ? i + 1 : i); 649 | mBreakpoints = std::move(btmp); 650 | 651 | return result; 652 | } 653 | 654 | std::string TextEditor::GetWordUnderCursor() const 655 | { 656 | auto c = GetCursorPosition(); 657 | return GetWordAt(c); 658 | } 659 | 660 | std::string TextEditor::GetWordAt(const Coordinates & aCoords) const 661 | { 662 | auto start = FindWordStart(aCoords); 663 | auto end = FindWordEnd(aCoords); 664 | 665 | std::string r; 666 | 667 | auto istart = GetCharacterIndex(start); 668 | auto iend = GetCharacterIndex(end); 669 | 670 | for (auto it = istart; it < iend; ++it) 671 | r.push_back(mLines[aCoords.mLine][it].mChar); 672 | 673 | return r; 674 | } 675 | 676 | ImU32 TextEditor::GetGlyphColor(const Glyph & aGlyph) const 677 | { 678 | if (!mColorizerEnabled) 679 | return mPalette[(int)PaletteIndex::Default]; 680 | if (aGlyph.mComment) 681 | return mPalette[(int)PaletteIndex::Comment]; 682 | if (aGlyph.mMultiLineComment) 683 | return mPalette[(int)PaletteIndex::MultiLineComment]; 684 | auto const color = mPalette[(int)aGlyph.mColorIndex]; 685 | if (aGlyph.mPreprocessor) 686 | { 687 | const auto ppcolor = mPalette[(int)PaletteIndex::Preprocessor]; 688 | const int c0 = ((ppcolor & 0xff) + (color & 0xff)) / 2; 689 | const int c1 = (((ppcolor >> 8) & 0xff) + ((color >> 8) & 0xff)) / 2; 690 | const int c2 = (((ppcolor >> 16) & 0xff) + ((color >> 16) & 0xff)) / 2; 691 | const int c3 = (((ppcolor >> 24) & 0xff) + ((color >> 24) & 0xff)) / 2; 692 | return ImU32(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24)); 693 | } 694 | return color; 695 | } 696 | 697 | void TextEditor::HandleKeyboardInputs() 698 | { 699 | ImGuiIO& io = ImGui::GetIO(); 700 | auto shift = io.KeyShift; 701 | auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; 702 | auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; 703 | 704 | if (ImGui::IsWindowFocused()) 705 | { 706 | if (ImGui::IsWindowHovered()) 707 | ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput); 708 | //ImGui::CaptureKeyboardFromApp(true); 709 | 710 | io.WantCaptureKeyboard = true; 711 | io.WantTextInput = true; 712 | 713 | if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Z)) 714 | Undo(); 715 | else if (!IsReadOnly() && !ctrl && !shift && alt && ImGui::IsKeyPressed(ImGuiKey_Backspace)) 716 | Undo(); 717 | else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Y)) 718 | Redo(); 719 | else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_UpArrow)) 720 | MoveUp(1, shift); 721 | else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_DownArrow)) 722 | MoveDown(1, shift); 723 | else if (!alt && ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) 724 | MoveLeft(1, shift, ctrl); 725 | else if (!alt && ImGui::IsKeyPressed(ImGuiKey_RightArrow)) 726 | MoveRight(1, shift, ctrl); 727 | else if (!alt && ImGui::IsKeyPressed(ImGuiKey_PageUp)) 728 | MoveUp(GetPageSize() - 4, shift); 729 | else if (!alt && ImGui::IsKeyPressed(ImGuiKey_PageDown)) 730 | MoveDown(GetPageSize() - 4, shift); 731 | else if (!alt && ctrl && ImGui::IsKeyPressed(ImGuiKey_Home)) 732 | MoveTop(shift); 733 | else if (ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_End)) 734 | MoveBottom(shift); 735 | else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Home)) 736 | MoveHome(shift); 737 | else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_End)) 738 | MoveEnd(shift); 739 | else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Delete)) 740 | Delete(); 741 | else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Backspace)) 742 | Backspace(); 743 | else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert)) 744 | mOverwrite ^= true; 745 | else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert)) 746 | Copy(); 747 | else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_C)) 748 | Copy(); 749 | else if (!IsReadOnly() && !ctrl && shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert)) 750 | Paste(); 751 | else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_V)) 752 | Paste(); 753 | else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_X)) 754 | Cut(); 755 | else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Delete)) 756 | Cut(); 757 | else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_A)) 758 | SelectAll(); 759 | else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Enter)) 760 | EnterCharacter('\n', false); 761 | else if (!IsReadOnly() && !ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Tab)) 762 | EnterCharacter('\t', shift); 763 | 764 | if (!IsReadOnly() && !io.InputQueueCharacters.empty()) 765 | { 766 | for (int i = 0; i < io.InputQueueCharacters.Size; i++) 767 | { 768 | auto c = io.InputQueueCharacters[i]; 769 | if (c != 0 && (c == '\n' || c >= 32)) 770 | EnterCharacter(c, shift); 771 | } 772 | io.InputQueueCharacters.resize(0); 773 | } 774 | } 775 | } 776 | 777 | void TextEditor::HandleMouseInputs() 778 | { 779 | ImGuiIO& io = ImGui::GetIO(); 780 | auto shift = io.KeyShift; 781 | auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; 782 | auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; 783 | 784 | if (ImGui::IsWindowHovered()) 785 | { 786 | if (!shift && !alt) 787 | { 788 | auto click = ImGui::IsMouseClicked(0); 789 | auto doubleClick = ImGui::IsMouseDoubleClicked(0); 790 | auto t = ImGui::GetTime(); 791 | auto tripleClick = click && !doubleClick && (mLastClick != -1.0f && (t - mLastClick) < io.MouseDoubleClickTime); 792 | 793 | /* 794 | Left mouse button triple click 795 | */ 796 | 797 | if (tripleClick) 798 | { 799 | if (!ctrl) 800 | { 801 | mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); 802 | mSelectionMode = SelectionMode::Line; 803 | SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); 804 | } 805 | 806 | mLastClick = -1.0f; 807 | } 808 | 809 | /* 810 | Left mouse button double click 811 | */ 812 | 813 | else if (doubleClick) 814 | { 815 | if (!ctrl) 816 | { 817 | mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); 818 | if (mSelectionMode == SelectionMode::Line) 819 | mSelectionMode = SelectionMode::Normal; 820 | else 821 | mSelectionMode = SelectionMode::Word; 822 | SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); 823 | } 824 | 825 | mLastClick = (float)ImGui::GetTime(); 826 | } 827 | 828 | /* 829 | Left mouse button click 830 | */ 831 | else if (click) 832 | { 833 | mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); 834 | if (ctrl) 835 | mSelectionMode = SelectionMode::Word; 836 | else 837 | mSelectionMode = SelectionMode::Normal; 838 | SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); 839 | 840 | mLastClick = (float)ImGui::GetTime(); 841 | } 842 | // Mouse left button dragging (=> update selection) 843 | else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0)) 844 | { 845 | io.WantCaptureMouse = true; 846 | mState.mCursorPosition = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); 847 | SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); 848 | } 849 | } 850 | } 851 | } 852 | 853 | void TextEditor::Render() 854 | { 855 | /* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/ 856 | const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x; 857 | mCharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * mLineSpacing); 858 | 859 | /* Update palette with the current alpha from style */ 860 | for (int i = 0; i < (int)PaletteIndex::Max; ++i) 861 | { 862 | auto color = ImGui::ColorConvertU32ToFloat4(mPaletteBase[i]); 863 | color.w *= ImGui::GetStyle().Alpha; 864 | mPalette[i] = ImGui::ColorConvertFloat4ToU32(color); 865 | } 866 | 867 | assert(mLineBuffer.empty()); 868 | 869 | auto contentSize = ImGui::GetWindowContentRegionMax(); 870 | auto drawList = ImGui::GetWindowDrawList(); 871 | float longest(mTextStart); 872 | 873 | if (mScrollToTop) 874 | { 875 | mScrollToTop = false; 876 | ImGui::SetScrollY(0.f); 877 | } 878 | 879 | ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos(); 880 | auto scrollX = ImGui::GetScrollX(); 881 | auto scrollY = ImGui::GetScrollY(); 882 | 883 | auto lineNo = (int)floor(scrollY / mCharAdvance.y); 884 | auto globalLineMax = (int)mLines.size(); 885 | auto lineMax = std::max(0, std::min((int)mLines.size() - 1, lineNo + (int)floor((scrollY + contentSize.y) / mCharAdvance.y))); 886 | 887 | // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width 888 | char buf[16]; 889 | snprintf(buf, 16, " %d ", globalLineMax); 890 | mTextStart = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x + mLeftMargin; 891 | 892 | if (!mLines.empty()) 893 | { 894 | float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x; 895 | 896 | while (lineNo <= lineMax) 897 | { 898 | ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y); 899 | ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y); 900 | 901 | auto& line = mLines[lineNo]; 902 | longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), longest); 903 | auto columnNo = 0; 904 | Coordinates lineStartCoord(lineNo, 0); 905 | Coordinates lineEndCoord(lineNo, GetLineMaxColumn(lineNo)); 906 | 907 | // Draw selection for the current line 908 | float sstart = -1.0f; 909 | float ssend = -1.0f; 910 | 911 | assert(mState.mSelectionStart <= mState.mSelectionEnd); 912 | if (mState.mSelectionStart <= lineEndCoord) 913 | sstart = mState.mSelectionStart > lineStartCoord ? TextDistanceToLineStart(mState.mSelectionStart) : 0.0f; 914 | if (mState.mSelectionEnd > lineStartCoord) 915 | ssend = TextDistanceToLineStart(mState.mSelectionEnd < lineEndCoord ? mState.mSelectionEnd : lineEndCoord); 916 | 917 | if (mState.mSelectionEnd.mLine > lineNo) 918 | ssend += mCharAdvance.x; 919 | 920 | if (sstart != -1 && ssend != -1 && sstart < ssend) 921 | { 922 | ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, lineStartScreenPos.y); 923 | ImVec2 vend(lineStartScreenPos.x + mTextStart + ssend, lineStartScreenPos.y + mCharAdvance.y); 924 | drawList->AddRectFilled(vstart, vend, mPalette[(int)PaletteIndex::Selection]); 925 | } 926 | 927 | // Draw breakpoints 928 | auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y); 929 | 930 | if (mBreakpoints.count(lineNo + 1) != 0) 931 | { 932 | auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y); 933 | drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::Breakpoint]); 934 | } 935 | 936 | // Draw error markers 937 | auto errorIt = mErrorMarkers.find(lineNo + 1); 938 | if (errorIt != mErrorMarkers.end()) 939 | { 940 | auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y); 941 | drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::ErrorMarker]); 942 | 943 | if (ImGui::IsMouseHoveringRect(lineStartScreenPos, end)) 944 | { 945 | ImGui::BeginTooltip(); 946 | ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f)); 947 | ImGui::Text("Error at line %d:", errorIt->first); 948 | ImGui::PopStyleColor(); 949 | ImGui::Separator(); 950 | ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.2f, 1.0f)); 951 | ImGui::Text("%s", errorIt->second.c_str()); 952 | ImGui::PopStyleColor(); 953 | ImGui::EndTooltip(); 954 | } 955 | } 956 | 957 | // Draw line number (right aligned) 958 | snprintf(buf, 16, "%d ", lineNo + 1); 959 | 960 | auto lineNoWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x; 961 | drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], buf); 962 | 963 | if (mState.mCursorPosition.mLine == lineNo) 964 | { 965 | auto focused = ImGui::IsWindowFocused(); 966 | 967 | // Highlight the current line (where the cursor is) 968 | if (!HasSelection()) 969 | { 970 | auto end = ImVec2(start.x + contentSize.x + scrollX, start.y + mCharAdvance.y); 971 | drawList->AddRectFilled(start, end, mPalette[(int)(focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]); 972 | drawList->AddRect(start, end, mPalette[(int)PaletteIndex::CurrentLineEdge], 1.0f); 973 | } 974 | 975 | // Render the cursor 976 | if (focused) 977 | { 978 | auto timeEnd = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 979 | auto elapsed = timeEnd - mStartTime; 980 | if (elapsed > 400) 981 | { 982 | float width = 1.0f; 983 | auto cindex = GetCharacterIndex(mState.mCursorPosition); 984 | float cx = TextDistanceToLineStart(mState.mCursorPosition); 985 | 986 | if (mOverwrite && cindex < (int)line.size()) 987 | { 988 | auto c = line[cindex].mChar; 989 | if (c == '\t') 990 | { 991 | auto x = (1.0f + std::floor((1.0f + cx) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); 992 | width = x - cx; 993 | } 994 | else 995 | { 996 | char buf2[2]; 997 | buf2[0] = line[cindex].mChar; 998 | buf2[1] = '\0'; 999 | width = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf2).x; 1000 | } 1001 | } 1002 | ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y); 1003 | ImVec2 cend(textScreenPos.x + cx + width, lineStartScreenPos.y + mCharAdvance.y); 1004 | drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]); 1005 | if (elapsed > 800) 1006 | mStartTime = timeEnd; 1007 | } 1008 | } 1009 | } 1010 | 1011 | // Render colorized text 1012 | auto prevColor = line.empty() ? mPalette[(int)PaletteIndex::Default] : GetGlyphColor(line[0]); 1013 | ImVec2 bufferOffset; 1014 | 1015 | for (int i = 0; i < line.size();) 1016 | { 1017 | auto& glyph = line[i]; 1018 | auto color = GetGlyphColor(glyph); 1019 | 1020 | if ((color != prevColor || glyph.mChar == '\t' || glyph.mChar == ' ') && !mLineBuffer.empty()) 1021 | { 1022 | const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); 1023 | drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); 1024 | auto textSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, mLineBuffer.c_str(), nullptr, nullptr); 1025 | bufferOffset.x += textSize.x; 1026 | mLineBuffer.clear(); 1027 | } 1028 | prevColor = color; 1029 | 1030 | if (glyph.mChar == '\t') 1031 | { 1032 | auto oldX = bufferOffset.x; 1033 | bufferOffset.x = (1.0f + std::floor((1.0f + bufferOffset.x) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); 1034 | ++i; 1035 | 1036 | if (mShowWhitespaces) 1037 | { 1038 | const auto s = ImGui::GetFontSize(); 1039 | const auto x1 = textScreenPos.x + oldX + 1.0f; 1040 | const auto x2 = textScreenPos.x + bufferOffset.x - 1.0f; 1041 | const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; 1042 | const ImVec2 p1(x1, y); 1043 | const ImVec2 p2(x2, y); 1044 | const ImVec2 p3(x2 - s * 0.2f, y - s * 0.2f); 1045 | const ImVec2 p4(x2 - s * 0.2f, y + s * 0.2f); 1046 | drawList->AddLine(p1, p2, 0x90909090); 1047 | drawList->AddLine(p2, p3, 0x90909090); 1048 | drawList->AddLine(p2, p4, 0x90909090); 1049 | } 1050 | } 1051 | else if (glyph.mChar == ' ') 1052 | { 1053 | if (mShowWhitespaces) 1054 | { 1055 | const auto s = ImGui::GetFontSize(); 1056 | const auto x = textScreenPos.x + bufferOffset.x + spaceSize * 0.5f; 1057 | const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; 1058 | drawList->AddCircleFilled(ImVec2(x, y), 1.5f, 0x80808080, 4); 1059 | } 1060 | bufferOffset.x += spaceSize; 1061 | i++; 1062 | } 1063 | else 1064 | { 1065 | auto l = UTF8CharLength(glyph.mChar); 1066 | while (l-- > 0) 1067 | mLineBuffer.push_back(line[i++].mChar); 1068 | } 1069 | ++columnNo; 1070 | } 1071 | 1072 | if (!mLineBuffer.empty()) 1073 | { 1074 | const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); 1075 | drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); 1076 | mLineBuffer.clear(); 1077 | } 1078 | 1079 | ++lineNo; 1080 | } 1081 | 1082 | // Draw a tooltip on known identifiers/preprocessor symbols 1083 | if (ImGui::IsMousePosValid()) 1084 | { 1085 | auto id = GetWordAt(ScreenPosToCoordinates(ImGui::GetMousePos())); 1086 | if (!id.empty()) 1087 | { 1088 | auto it = mLanguageDefinition.mIdentifiers.find(id); 1089 | if (it != mLanguageDefinition.mIdentifiers.end()) 1090 | { 1091 | ImGui::BeginTooltip(); 1092 | ImGui::TextUnformatted(it->second.mDeclaration.c_str()); 1093 | ImGui::EndTooltip(); 1094 | } 1095 | else 1096 | { 1097 | auto pi = mLanguageDefinition.mPreprocIdentifiers.find(id); 1098 | if (pi != mLanguageDefinition.mPreprocIdentifiers.end()) 1099 | { 1100 | ImGui::BeginTooltip(); 1101 | ImGui::TextUnformatted(pi->second.mDeclaration.c_str()); 1102 | ImGui::EndTooltip(); 1103 | } 1104 | } 1105 | } 1106 | } 1107 | } 1108 | 1109 | 1110 | ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y)); 1111 | 1112 | if (mScrollToCursor) 1113 | { 1114 | EnsureCursorVisible(); 1115 | ImGui::SetWindowFocus(); 1116 | mScrollToCursor = false; 1117 | } 1118 | } 1119 | 1120 | void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) 1121 | { 1122 | mWithinRender = true; 1123 | mTextChanged = false; 1124 | mCursorPositionChanged = false; 1125 | 1126 | ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background])); 1127 | ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); 1128 | if (!mIgnoreImGuiChild) 1129 | ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove); 1130 | 1131 | if (mHandleKeyboardInputs) 1132 | { 1133 | HandleKeyboardInputs(); 1134 | ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, false); 1135 | } 1136 | 1137 | if (mHandleMouseInputs) 1138 | HandleMouseInputs(); 1139 | 1140 | ColorizeInternal(); 1141 | Render(); 1142 | 1143 | if (mHandleKeyboardInputs) 1144 | ImGui::PopItemFlag(); 1145 | 1146 | if (!mIgnoreImGuiChild) 1147 | ImGui::EndChild(); 1148 | 1149 | ImGui::PopStyleVar(); 1150 | ImGui::PopStyleColor(); 1151 | 1152 | mWithinRender = false; 1153 | } 1154 | 1155 | void TextEditor::SetText(const std::string & aText) 1156 | { 1157 | mLines.clear(); 1158 | mLines.emplace_back(Line()); 1159 | for (auto chr : aText) 1160 | { 1161 | if (chr == '\r') 1162 | { 1163 | // ignore the carriage return character 1164 | } 1165 | else if (chr == '\n') 1166 | mLines.emplace_back(Line()); 1167 | else 1168 | { 1169 | mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default)); 1170 | } 1171 | } 1172 | 1173 | mTextChanged = true; 1174 | mScrollToTop = true; 1175 | 1176 | mUndoBuffer.clear(); 1177 | mUndoIndex = 0; 1178 | 1179 | Colorize(); 1180 | } 1181 | 1182 | void TextEditor::SetTextLines(const std::vector & aLines) 1183 | { 1184 | mLines.clear(); 1185 | 1186 | if (aLines.empty()) 1187 | { 1188 | mLines.emplace_back(Line()); 1189 | } 1190 | else 1191 | { 1192 | mLines.resize(aLines.size()); 1193 | 1194 | for (size_t i = 0; i < aLines.size(); ++i) 1195 | { 1196 | const std::string & aLine = aLines[i]; 1197 | 1198 | mLines[i].reserve(aLine.size()); 1199 | for (size_t j = 0; j < aLine.size(); ++j) 1200 | mLines[i].emplace_back(Glyph(aLine[j], PaletteIndex::Default)); 1201 | } 1202 | } 1203 | 1204 | mTextChanged = true; 1205 | mScrollToTop = true; 1206 | 1207 | mUndoBuffer.clear(); 1208 | mUndoIndex = 0; 1209 | 1210 | Colorize(); 1211 | } 1212 | 1213 | void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) 1214 | { 1215 | assert(!mReadOnly); 1216 | 1217 | UndoRecord u; 1218 | 1219 | u.mBefore = mState; 1220 | 1221 | if (HasSelection()) 1222 | { 1223 | if (aChar == '\t' && mState.mSelectionStart.mLine != mState.mSelectionEnd.mLine) 1224 | { 1225 | 1226 | auto start = mState.mSelectionStart; 1227 | auto end = mState.mSelectionEnd; 1228 | auto originalEnd = end; 1229 | 1230 | if (start > end) 1231 | std::swap(start, end); 1232 | start.mColumn = 0; 1233 | // end.mColumn = end.mLine < mLines.size() ? mLines[end.mLine].size() : 0; 1234 | if (end.mColumn == 0 && end.mLine > 0) 1235 | --end.mLine; 1236 | if (end.mLine >= (int)mLines.size()) 1237 | end.mLine = mLines.empty() ? 0 : (int)mLines.size() - 1; 1238 | end.mColumn = GetLineMaxColumn(end.mLine); 1239 | 1240 | //if (end.mColumn >= GetLineMaxColumn(end.mLine)) 1241 | // end.mColumn = GetLineMaxColumn(end.mLine) - 1; 1242 | 1243 | u.mRemovedStart = start; 1244 | u.mRemovedEnd = end; 1245 | u.mRemoved = GetText(start, end); 1246 | 1247 | bool modified = false; 1248 | 1249 | for (int i = start.mLine; i <= end.mLine; i++) 1250 | { 1251 | auto& line = mLines[i]; 1252 | if (aShift) 1253 | { 1254 | if (!line.empty()) 1255 | { 1256 | if (line.front().mChar == '\t') 1257 | { 1258 | line.erase(line.begin()); 1259 | modified = true; 1260 | } 1261 | else 1262 | { 1263 | for (int j = 0; j < mTabSize && !line.empty() && line.front().mChar == ' '; j++) 1264 | { 1265 | line.erase(line.begin()); 1266 | modified = true; 1267 | } 1268 | } 1269 | } 1270 | } 1271 | else 1272 | { 1273 | line.insert(line.begin(), Glyph('\t', TextEditor::PaletteIndex::Background)); 1274 | modified = true; 1275 | } 1276 | } 1277 | 1278 | if (modified) 1279 | { 1280 | start = Coordinates(start.mLine, GetCharacterColumn(start.mLine, 0)); 1281 | Coordinates rangeEnd; 1282 | if (originalEnd.mColumn != 0) 1283 | { 1284 | end = Coordinates(end.mLine, GetLineMaxColumn(end.mLine)); 1285 | rangeEnd = end; 1286 | u.mAdded = GetText(start, end); 1287 | } 1288 | else 1289 | { 1290 | end = Coordinates(originalEnd.mLine, 0); 1291 | rangeEnd = Coordinates(end.mLine - 1, GetLineMaxColumn(end.mLine - 1)); 1292 | u.mAdded = GetText(start, rangeEnd); 1293 | } 1294 | 1295 | u.mAddedStart = start; 1296 | u.mAddedEnd = rangeEnd; 1297 | u.mAfter = mState; 1298 | 1299 | mState.mSelectionStart = start; 1300 | mState.mSelectionEnd = end; 1301 | AddUndo(u); 1302 | 1303 | mTextChanged = true; 1304 | 1305 | EnsureCursorVisible(); 1306 | } 1307 | 1308 | return; 1309 | } // c == '\t' 1310 | else 1311 | { 1312 | u.mRemoved = GetSelectedText(); 1313 | u.mRemovedStart = mState.mSelectionStart; 1314 | u.mRemovedEnd = mState.mSelectionEnd; 1315 | DeleteSelection(); 1316 | } 1317 | } // HasSelection 1318 | 1319 | auto coord = GetActualCursorCoordinates(); 1320 | u.mAddedStart = coord; 1321 | 1322 | assert(!mLines.empty()); 1323 | 1324 | if (aChar == '\n') 1325 | { 1326 | InsertLine(coord.mLine + 1); 1327 | auto& line = mLines[coord.mLine]; 1328 | auto& newLine = mLines[coord.mLine + 1]; 1329 | 1330 | if (mLanguageDefinition.mAutoIndentation) 1331 | for (size_t it = 0; it < line.size() && isascii(line[it].mChar) && isblank(line[it].mChar); ++it) 1332 | newLine.push_back(line[it]); 1333 | 1334 | const size_t whitespaceSize = newLine.size(); 1335 | auto cindex = GetCharacterIndex(coord); 1336 | newLine.insert(newLine.end(), line.begin() + cindex, line.end()); 1337 | line.erase(line.begin() + cindex, line.begin() + line.size()); 1338 | SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (int)whitespaceSize))); 1339 | u.mAdded = (char)aChar; 1340 | } 1341 | else 1342 | { 1343 | char buf[7]; 1344 | int e = ImTextCharToUtf8(buf, 7, aChar); 1345 | if (e > 0) 1346 | { 1347 | buf[e] = '\0'; 1348 | auto& line = mLines[coord.mLine]; 1349 | auto cindex = GetCharacterIndex(coord); 1350 | 1351 | if (mOverwrite && cindex < (int)line.size()) 1352 | { 1353 | auto d = UTF8CharLength(line[cindex].mChar); 1354 | 1355 | u.mRemovedStart = mState.mCursorPosition; 1356 | u.mRemovedEnd = Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + d)); 1357 | 1358 | while (d-- > 0 && cindex < (int)line.size()) 1359 | { 1360 | u.mRemoved += line[cindex].mChar; 1361 | line.erase(line.begin() + cindex); 1362 | } 1363 | } 1364 | 1365 | for (auto p = buf; *p != '\0'; p++, ++cindex) 1366 | line.insert(line.begin() + cindex, Glyph(*p, PaletteIndex::Default)); 1367 | u.mAdded = buf; 1368 | 1369 | SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex))); 1370 | } 1371 | else 1372 | return; 1373 | } 1374 | 1375 | mTextChanged = true; 1376 | 1377 | u.mAddedEnd = GetActualCursorCoordinates(); 1378 | u.mAfter = mState; 1379 | 1380 | AddUndo(u); 1381 | 1382 | Colorize(coord.mLine - 1, 3); 1383 | EnsureCursorVisible(); 1384 | } 1385 | 1386 | void TextEditor::SetReadOnly(bool aValue) 1387 | { 1388 | mReadOnly = aValue; 1389 | } 1390 | 1391 | void TextEditor::SetColorizerEnable(bool aValue) 1392 | { 1393 | mColorizerEnabled = aValue; 1394 | } 1395 | 1396 | void TextEditor::SetCursorPosition(const Coordinates & aPosition) 1397 | { 1398 | if (mState.mCursorPosition != aPosition) 1399 | { 1400 | mState.mCursorPosition = aPosition; 1401 | mCursorPositionChanged = true; 1402 | EnsureCursorVisible(); 1403 | } 1404 | } 1405 | 1406 | void TextEditor::SetSelectionStart(const Coordinates & aPosition) 1407 | { 1408 | mState.mSelectionStart = SanitizeCoordinates(aPosition); 1409 | if (mState.mSelectionStart > mState.mSelectionEnd) 1410 | std::swap(mState.mSelectionStart, mState.mSelectionEnd); 1411 | } 1412 | 1413 | void TextEditor::SetSelectionEnd(const Coordinates & aPosition) 1414 | { 1415 | mState.mSelectionEnd = SanitizeCoordinates(aPosition); 1416 | if (mState.mSelectionStart > mState.mSelectionEnd) 1417 | std::swap(mState.mSelectionStart, mState.mSelectionEnd); 1418 | } 1419 | 1420 | void TextEditor::SetSelection(const Coordinates & aStart, const Coordinates & aEnd, SelectionMode aMode) 1421 | { 1422 | auto oldSelStart = mState.mSelectionStart; 1423 | auto oldSelEnd = mState.mSelectionEnd; 1424 | 1425 | mState.mSelectionStart = SanitizeCoordinates(aStart); 1426 | mState.mSelectionEnd = SanitizeCoordinates(aEnd); 1427 | if (mState.mSelectionStart > mState.mSelectionEnd) 1428 | std::swap(mState.mSelectionStart, mState.mSelectionEnd); 1429 | 1430 | switch (aMode) 1431 | { 1432 | case TextEditor::SelectionMode::Normal: 1433 | break; 1434 | case TextEditor::SelectionMode::Word: 1435 | { 1436 | mState.mSelectionStart = FindWordStart(mState.mSelectionStart); 1437 | if (!IsOnWordBoundary(mState.mSelectionEnd)) 1438 | mState.mSelectionEnd = FindWordEnd(FindWordStart(mState.mSelectionEnd)); 1439 | break; 1440 | } 1441 | case TextEditor::SelectionMode::Line: 1442 | { 1443 | const auto lineNo = mState.mSelectionEnd.mLine; 1444 | const auto lineSize = (size_t)lineNo < mLines.size() ? mLines[lineNo].size() : 0; 1445 | mState.mSelectionStart = Coordinates(mState.mSelectionStart.mLine, 0); 1446 | mState.mSelectionEnd = Coordinates(lineNo, GetLineMaxColumn(lineNo)); 1447 | break; 1448 | } 1449 | default: 1450 | break; 1451 | } 1452 | 1453 | if (mState.mSelectionStart != oldSelStart || 1454 | mState.mSelectionEnd != oldSelEnd) 1455 | mCursorPositionChanged = true; 1456 | } 1457 | 1458 | void TextEditor::SetTabSize(int aValue) 1459 | { 1460 | mTabSize = std::max(0, std::min(32, aValue)); 1461 | } 1462 | 1463 | void TextEditor::InsertText(const std::string & aValue) 1464 | { 1465 | InsertText(aValue.c_str()); 1466 | } 1467 | 1468 | void TextEditor::InsertText(const char * aValue) 1469 | { 1470 | if (aValue == nullptr) 1471 | return; 1472 | 1473 | auto pos = GetActualCursorCoordinates(); 1474 | auto start = std::min(pos, mState.mSelectionStart); 1475 | int totalLines = pos.mLine - start.mLine; 1476 | 1477 | totalLines += InsertTextAt(pos, aValue); 1478 | 1479 | SetSelection(pos, pos); 1480 | SetCursorPosition(pos); 1481 | Colorize(start.mLine - 1, totalLines + 2); 1482 | } 1483 | 1484 | void TextEditor::DeleteSelection() 1485 | { 1486 | assert(mState.mSelectionEnd >= mState.mSelectionStart); 1487 | 1488 | if (mState.mSelectionEnd == mState.mSelectionStart) 1489 | return; 1490 | 1491 | DeleteRange(mState.mSelectionStart, mState.mSelectionEnd); 1492 | 1493 | SetSelection(mState.mSelectionStart, mState.mSelectionStart); 1494 | SetCursorPosition(mState.mSelectionStart); 1495 | Colorize(mState.mSelectionStart.mLine, 1); 1496 | } 1497 | 1498 | void TextEditor::MoveUp(int aAmount, bool aSelect) 1499 | { 1500 | auto oldPos = mState.mCursorPosition; 1501 | mState.mCursorPosition.mLine = std::max(0, mState.mCursorPosition.mLine - aAmount); 1502 | if (oldPos != mState.mCursorPosition) 1503 | { 1504 | if (aSelect) 1505 | { 1506 | if (oldPos == mInteractiveStart) 1507 | mInteractiveStart = mState.mCursorPosition; 1508 | else if (oldPos == mInteractiveEnd) 1509 | mInteractiveEnd = mState.mCursorPosition; 1510 | else 1511 | { 1512 | mInteractiveStart = mState.mCursorPosition; 1513 | mInteractiveEnd = oldPos; 1514 | } 1515 | } 1516 | else 1517 | mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; 1518 | SetSelection(mInteractiveStart, mInteractiveEnd); 1519 | 1520 | EnsureCursorVisible(); 1521 | } 1522 | } 1523 | 1524 | void TextEditor::MoveDown(int aAmount, bool aSelect) 1525 | { 1526 | assert(mState.mCursorPosition.mColumn >= 0); 1527 | auto oldPos = mState.mCursorPosition; 1528 | mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + aAmount)); 1529 | 1530 | if (mState.mCursorPosition != oldPos) 1531 | { 1532 | if (aSelect) 1533 | { 1534 | if (oldPos == mInteractiveEnd) 1535 | mInteractiveEnd = mState.mCursorPosition; 1536 | else if (oldPos == mInteractiveStart) 1537 | mInteractiveStart = mState.mCursorPosition; 1538 | else 1539 | { 1540 | mInteractiveStart = oldPos; 1541 | mInteractiveEnd = mState.mCursorPosition; 1542 | } 1543 | } 1544 | else 1545 | mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; 1546 | SetSelection(mInteractiveStart, mInteractiveEnd); 1547 | 1548 | EnsureCursorVisible(); 1549 | } 1550 | } 1551 | 1552 | static bool IsUTFSequence(char c) 1553 | { 1554 | return (c & 0xC0) == 0x80; 1555 | } 1556 | 1557 | void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) 1558 | { 1559 | if (mLines.empty()) 1560 | return; 1561 | 1562 | auto oldPos = mState.mCursorPosition; 1563 | mState.mCursorPosition = GetActualCursorCoordinates(); 1564 | auto line = mState.mCursorPosition.mLine; 1565 | auto cindex = GetCharacterIndex(mState.mCursorPosition); 1566 | 1567 | while (aAmount-- > 0) 1568 | { 1569 | if (cindex == 0) 1570 | { 1571 | if (line > 0) 1572 | { 1573 | --line; 1574 | if ((int)mLines.size() > line) 1575 | cindex = (int)mLines[line].size(); 1576 | else 1577 | cindex = 0; 1578 | } 1579 | } 1580 | else 1581 | { 1582 | --cindex; 1583 | if (cindex > 0) 1584 | { 1585 | if ((int)mLines.size() > line) 1586 | { 1587 | while (cindex > 0 && IsUTFSequence(mLines[line][cindex].mChar)) 1588 | --cindex; 1589 | } 1590 | } 1591 | } 1592 | 1593 | mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); 1594 | if (aWordMode) 1595 | { 1596 | mState.mCursorPosition = FindWordStart(mState.mCursorPosition); 1597 | cindex = GetCharacterIndex(mState.mCursorPosition); 1598 | } 1599 | } 1600 | 1601 | mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); 1602 | 1603 | assert(mState.mCursorPosition.mColumn >= 0); 1604 | if (aSelect) 1605 | { 1606 | if (oldPos == mInteractiveStart) 1607 | mInteractiveStart = mState.mCursorPosition; 1608 | else if (oldPos == mInteractiveEnd) 1609 | mInteractiveEnd = mState.mCursorPosition; 1610 | else 1611 | { 1612 | mInteractiveStart = mState.mCursorPosition; 1613 | mInteractiveEnd = oldPos; 1614 | } 1615 | } 1616 | else 1617 | mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; 1618 | SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal); 1619 | 1620 | EnsureCursorVisible(); 1621 | } 1622 | 1623 | void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) 1624 | { 1625 | auto oldPos = mState.mCursorPosition; 1626 | 1627 | if (mLines.empty() || oldPos.mLine >= mLines.size()) 1628 | return; 1629 | 1630 | auto cindex = GetCharacterIndex(mState.mCursorPosition); 1631 | while (aAmount-- > 0) 1632 | { 1633 | auto lindex = mState.mCursorPosition.mLine; 1634 | auto& line = mLines[lindex]; 1635 | 1636 | if (cindex >= line.size()) 1637 | { 1638 | if (mState.mCursorPosition.mLine < mLines.size() - 1) 1639 | { 1640 | mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + 1)); 1641 | mState.mCursorPosition.mColumn = 0; 1642 | } 1643 | else 1644 | return; 1645 | } 1646 | else 1647 | { 1648 | cindex += UTF8CharLength(line[cindex].mChar); 1649 | mState.mCursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex)); 1650 | if (aWordMode) 1651 | mState.mCursorPosition = FindNextWord(mState.mCursorPosition); 1652 | } 1653 | } 1654 | 1655 | if (aSelect) 1656 | { 1657 | if (oldPos == mInteractiveEnd) 1658 | mInteractiveEnd = SanitizeCoordinates(mState.mCursorPosition); 1659 | else if (oldPos == mInteractiveStart) 1660 | mInteractiveStart = mState.mCursorPosition; 1661 | else 1662 | { 1663 | mInteractiveStart = oldPos; 1664 | mInteractiveEnd = mState.mCursorPosition; 1665 | } 1666 | } 1667 | else 1668 | mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; 1669 | SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal); 1670 | 1671 | EnsureCursorVisible(); 1672 | } 1673 | 1674 | void TextEditor::MoveTop(bool aSelect) 1675 | { 1676 | auto oldPos = mState.mCursorPosition; 1677 | SetCursorPosition(Coordinates(0, 0)); 1678 | 1679 | if (mState.mCursorPosition != oldPos) 1680 | { 1681 | if (aSelect) 1682 | { 1683 | mInteractiveEnd = oldPos; 1684 | mInteractiveStart = mState.mCursorPosition; 1685 | } 1686 | else 1687 | mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; 1688 | SetSelection(mInteractiveStart, mInteractiveEnd); 1689 | } 1690 | } 1691 | 1692 | void TextEditor::TextEditor::MoveBottom(bool aSelect) 1693 | { 1694 | auto oldPos = GetCursorPosition(); 1695 | auto newPos = Coordinates((int)mLines.size() - 1, 0); 1696 | SetCursorPosition(newPos); 1697 | if (aSelect) 1698 | { 1699 | mInteractiveStart = oldPos; 1700 | mInteractiveEnd = newPos; 1701 | } 1702 | else 1703 | mInteractiveStart = mInteractiveEnd = newPos; 1704 | SetSelection(mInteractiveStart, mInteractiveEnd); 1705 | } 1706 | 1707 | void TextEditor::MoveHome(bool aSelect) 1708 | { 1709 | auto oldPos = mState.mCursorPosition; 1710 | SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, 0)); 1711 | 1712 | if (mState.mCursorPosition != oldPos) 1713 | { 1714 | if (aSelect) 1715 | { 1716 | if (oldPos == mInteractiveStart) 1717 | mInteractiveStart = mState.mCursorPosition; 1718 | else if (oldPos == mInteractiveEnd) 1719 | mInteractiveEnd = mState.mCursorPosition; 1720 | else 1721 | { 1722 | mInteractiveStart = mState.mCursorPosition; 1723 | mInteractiveEnd = oldPos; 1724 | } 1725 | } 1726 | else 1727 | mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; 1728 | SetSelection(mInteractiveStart, mInteractiveEnd); 1729 | } 1730 | } 1731 | 1732 | void TextEditor::MoveEnd(bool aSelect) 1733 | { 1734 | auto oldPos = mState.mCursorPosition; 1735 | SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, GetLineMaxColumn(oldPos.mLine))); 1736 | 1737 | if (mState.mCursorPosition != oldPos) 1738 | { 1739 | if (aSelect) 1740 | { 1741 | if (oldPos == mInteractiveEnd) 1742 | mInteractiveEnd = mState.mCursorPosition; 1743 | else if (oldPos == mInteractiveStart) 1744 | mInteractiveStart = mState.mCursorPosition; 1745 | else 1746 | { 1747 | mInteractiveStart = oldPos; 1748 | mInteractiveEnd = mState.mCursorPosition; 1749 | } 1750 | } 1751 | else 1752 | mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; 1753 | SetSelection(mInteractiveStart, mInteractiveEnd); 1754 | } 1755 | } 1756 | 1757 | void TextEditor::Delete() 1758 | { 1759 | assert(!mReadOnly); 1760 | 1761 | if (mLines.empty()) 1762 | return; 1763 | 1764 | UndoRecord u; 1765 | u.mBefore = mState; 1766 | 1767 | if (HasSelection()) 1768 | { 1769 | u.mRemoved = GetSelectedText(); 1770 | u.mRemovedStart = mState.mSelectionStart; 1771 | u.mRemovedEnd = mState.mSelectionEnd; 1772 | 1773 | DeleteSelection(); 1774 | } 1775 | else 1776 | { 1777 | auto pos = GetActualCursorCoordinates(); 1778 | SetCursorPosition(pos); 1779 | auto& line = mLines[pos.mLine]; 1780 | 1781 | if (pos.mColumn == GetLineMaxColumn(pos.mLine)) 1782 | { 1783 | if (pos.mLine == (int)mLines.size() - 1) 1784 | return; 1785 | 1786 | u.mRemoved = '\n'; 1787 | u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); 1788 | Advance(u.mRemovedEnd); 1789 | 1790 | auto& nextLine = mLines[pos.mLine + 1]; 1791 | line.insert(line.end(), nextLine.begin(), nextLine.end()); 1792 | RemoveLine(pos.mLine + 1); 1793 | } 1794 | else 1795 | { 1796 | auto cindex = GetCharacterIndex(pos); 1797 | u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); 1798 | u.mRemovedEnd.mColumn++; 1799 | u.mRemoved = GetText(u.mRemovedStart, u.mRemovedEnd); 1800 | 1801 | auto d = UTF8CharLength(line[cindex].mChar); 1802 | while (d-- > 0 && cindex < (int)line.size()) 1803 | line.erase(line.begin() + cindex); 1804 | } 1805 | 1806 | mTextChanged = true; 1807 | 1808 | Colorize(pos.mLine, 1); 1809 | } 1810 | 1811 | u.mAfter = mState; 1812 | AddUndo(u); 1813 | } 1814 | 1815 | void TextEditor::Backspace() 1816 | { 1817 | assert(!mReadOnly); 1818 | 1819 | if (mLines.empty()) 1820 | return; 1821 | 1822 | UndoRecord u; 1823 | u.mBefore = mState; 1824 | 1825 | if (HasSelection()) 1826 | { 1827 | u.mRemoved = GetSelectedText(); 1828 | u.mRemovedStart = mState.mSelectionStart; 1829 | u.mRemovedEnd = mState.mSelectionEnd; 1830 | 1831 | DeleteSelection(); 1832 | } 1833 | else 1834 | { 1835 | auto pos = GetActualCursorCoordinates(); 1836 | SetCursorPosition(pos); 1837 | 1838 | if (mState.mCursorPosition.mColumn == 0) 1839 | { 1840 | if (mState.mCursorPosition.mLine == 0) 1841 | return; 1842 | 1843 | u.mRemoved = '\n'; 1844 | u.mRemovedStart = u.mRemovedEnd = Coordinates(pos.mLine - 1, GetLineMaxColumn(pos.mLine - 1)); 1845 | Advance(u.mRemovedEnd); 1846 | 1847 | auto& line = mLines[mState.mCursorPosition.mLine]; 1848 | auto& prevLine = mLines[mState.mCursorPosition.mLine - 1]; 1849 | auto prevSize = GetLineMaxColumn(mState.mCursorPosition.mLine - 1); 1850 | prevLine.insert(prevLine.end(), line.begin(), line.end()); 1851 | 1852 | ErrorMarkers etmp; 1853 | for (auto& i : mErrorMarkers) 1854 | etmp.insert(ErrorMarkers::value_type(i.first - 1 == mState.mCursorPosition.mLine ? i.first - 1 : i.first, i.second)); 1855 | mErrorMarkers = std::move(etmp); 1856 | 1857 | RemoveLine(mState.mCursorPosition.mLine); 1858 | --mState.mCursorPosition.mLine; 1859 | mState.mCursorPosition.mColumn = prevSize; 1860 | } 1861 | else 1862 | { 1863 | auto& line = mLines[mState.mCursorPosition.mLine]; 1864 | auto cindex = GetCharacterIndex(pos) - 1; 1865 | auto cend = cindex + 1; 1866 | while (cindex > 0 && IsUTFSequence(line[cindex].mChar)) 1867 | --cindex; 1868 | 1869 | //if (cindex > 0 && UTF8CharLength(line[cindex].mChar) > 1) 1870 | // --cindex; 1871 | 1872 | u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); 1873 | --u.mRemovedStart.mColumn; 1874 | --mState.mCursorPosition.mColumn; 1875 | 1876 | while (cindex < line.size() && cend-- > cindex) 1877 | { 1878 | u.mRemoved += line[cindex].mChar; 1879 | line.erase(line.begin() + cindex); 1880 | } 1881 | } 1882 | 1883 | mTextChanged = true; 1884 | 1885 | EnsureCursorVisible(); 1886 | Colorize(mState.mCursorPosition.mLine, 1); 1887 | } 1888 | 1889 | u.mAfter = mState; 1890 | AddUndo(u); 1891 | } 1892 | 1893 | void TextEditor::SelectWordUnderCursor() 1894 | { 1895 | auto c = GetCursorPosition(); 1896 | SetSelection(FindWordStart(c), FindWordEnd(c)); 1897 | } 1898 | 1899 | void TextEditor::SelectAll() 1900 | { 1901 | SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), 0)); 1902 | } 1903 | 1904 | bool TextEditor::HasSelection() const 1905 | { 1906 | return mState.mSelectionEnd > mState.mSelectionStart; 1907 | } 1908 | 1909 | void TextEditor::Copy() 1910 | { 1911 | if (HasSelection()) 1912 | { 1913 | ImGui::SetClipboardText(GetSelectedText().c_str()); 1914 | } 1915 | else 1916 | { 1917 | if (!mLines.empty()) 1918 | { 1919 | std::string str; 1920 | auto& line = mLines[GetActualCursorCoordinates().mLine]; 1921 | for (auto& g : line) 1922 | str.push_back(g.mChar); 1923 | ImGui::SetClipboardText(str.c_str()); 1924 | } 1925 | } 1926 | } 1927 | 1928 | void TextEditor::Cut() 1929 | { 1930 | if (IsReadOnly()) 1931 | { 1932 | Copy(); 1933 | } 1934 | else 1935 | { 1936 | if (HasSelection()) 1937 | { 1938 | UndoRecord u; 1939 | u.mBefore = mState; 1940 | u.mRemoved = GetSelectedText(); 1941 | u.mRemovedStart = mState.mSelectionStart; 1942 | u.mRemovedEnd = mState.mSelectionEnd; 1943 | 1944 | Copy(); 1945 | DeleteSelection(); 1946 | 1947 | u.mAfter = mState; 1948 | AddUndo(u); 1949 | } 1950 | } 1951 | } 1952 | 1953 | void TextEditor::Paste() 1954 | { 1955 | if (IsReadOnly()) 1956 | return; 1957 | 1958 | auto clipText = ImGui::GetClipboardText(); 1959 | if (clipText != nullptr && strlen(clipText) > 0) 1960 | { 1961 | UndoRecord u; 1962 | u.mBefore = mState; 1963 | 1964 | if (HasSelection()) 1965 | { 1966 | u.mRemoved = GetSelectedText(); 1967 | u.mRemovedStart = mState.mSelectionStart; 1968 | u.mRemovedEnd = mState.mSelectionEnd; 1969 | DeleteSelection(); 1970 | } 1971 | 1972 | u.mAdded = clipText; 1973 | u.mAddedStart = GetActualCursorCoordinates(); 1974 | 1975 | InsertText(clipText); 1976 | 1977 | u.mAddedEnd = GetActualCursorCoordinates(); 1978 | u.mAfter = mState; 1979 | AddUndo(u); 1980 | } 1981 | } 1982 | 1983 | bool TextEditor::CanUndo() const 1984 | { 1985 | return !mReadOnly && mUndoIndex > 0; 1986 | } 1987 | 1988 | bool TextEditor::CanRedo() const 1989 | { 1990 | return !mReadOnly && mUndoIndex < (int)mUndoBuffer.size(); 1991 | } 1992 | 1993 | void TextEditor::Undo(int aSteps) 1994 | { 1995 | while (CanUndo() && aSteps-- > 0) 1996 | mUndoBuffer[--mUndoIndex].Undo(this); 1997 | } 1998 | 1999 | void TextEditor::Redo(int aSteps) 2000 | { 2001 | while (CanRedo() && aSteps-- > 0) 2002 | mUndoBuffer[mUndoIndex++].Redo(this); 2003 | } 2004 | 2005 | const TextEditor::Palette & TextEditor::GetDarkPalette() 2006 | { 2007 | const static Palette p = { { 2008 | 0xff7f7f7f, // Default 2009 | 0xffd69c56, // Keyword 2010 | 0xff00ff00, // Number 2011 | 0xff7070e0, // String 2012 | 0xff70a0e0, // Char literal 2013 | 0xffffffff, // Punctuation 2014 | 0xff408080, // Preprocessor 2015 | 0xffaaaaaa, // Identifier 2016 | 0xff9bc64d, // Known identifier 2017 | 0xffc040a0, // Preproc identifier 2018 | 0xff206020, // Comment (single line) 2019 | 0xff406020, // Comment (multi line) 2020 | 0xff101010, // Background 2021 | 0xffe0e0e0, // Cursor 2022 | 0x80a06020, // Selection 2023 | 0x800020ff, // ErrorMarker 2024 | 0x40f08000, // Breakpoint 2025 | 0xff707000, // Line number 2026 | 0x40000000, // Current line fill 2027 | 0x40808080, // Current line fill (inactive) 2028 | 0x40a0a0a0, // Current line edge 2029 | } }; 2030 | return p; 2031 | } 2032 | 2033 | const TextEditor::Palette & TextEditor::GetLightPalette() 2034 | { 2035 | const static Palette p = { { 2036 | 0xff7f7f7f, // None 2037 | 0xffff0c06, // Keyword 2038 | 0xff008000, // Number 2039 | 0xff2020a0, // String 2040 | 0xff304070, // Char literal 2041 | 0xff000000, // Punctuation 2042 | 0xff406060, // Preprocessor 2043 | 0xff404040, // Identifier 2044 | 0xff606010, // Known identifier 2045 | 0xffc040a0, // Preproc identifier 2046 | 0xff205020, // Comment (single line) 2047 | 0xff405020, // Comment (multi line) 2048 | 0xffffffff, // Background 2049 | 0xff000000, // Cursor 2050 | 0x80600000, // Selection 2051 | 0xa00010ff, // ErrorMarker 2052 | 0x80f08000, // Breakpoint 2053 | 0xff505000, // Line number 2054 | 0x40000000, // Current line fill 2055 | 0x40808080, // Current line fill (inactive) 2056 | 0x40000000, // Current line edge 2057 | } }; 2058 | return p; 2059 | } 2060 | 2061 | const TextEditor::Palette & TextEditor::GetRetroBluePalette() 2062 | { 2063 | const static Palette p = { { 2064 | 0xff00ffff, // None 2065 | 0xffffff00, // Keyword 2066 | 0xff00ff00, // Number 2067 | 0xff808000, // String 2068 | 0xff808000, // Char literal 2069 | 0xffffffff, // Punctuation 2070 | 0xff008000, // Preprocessor 2071 | 0xff00ffff, // Identifier 2072 | 0xffffffff, // Known identifier 2073 | 0xffff00ff, // Preproc identifier 2074 | 0xff808080, // Comment (single line) 2075 | 0xff404040, // Comment (multi line) 2076 | 0xff800000, // Background 2077 | 0xff0080ff, // Cursor 2078 | 0x80ffff00, // Selection 2079 | 0xa00000ff, // ErrorMarker 2080 | 0x80ff8000, // Breakpoint 2081 | 0xff808000, // Line number 2082 | 0x40000000, // Current line fill 2083 | 0x40808080, // Current line fill (inactive) 2084 | 0x40000000, // Current line edge 2085 | } }; 2086 | return p; 2087 | } 2088 | 2089 | 2090 | std::string TextEditor::GetText() const 2091 | { 2092 | return GetText(Coordinates(), Coordinates((int)mLines.size(), 0)); 2093 | } 2094 | 2095 | std::vector TextEditor::GetTextLines() const 2096 | { 2097 | std::vector result; 2098 | 2099 | result.reserve(mLines.size()); 2100 | 2101 | for (auto & line : mLines) 2102 | { 2103 | std::string text; 2104 | 2105 | text.resize(line.size()); 2106 | 2107 | for (size_t i = 0; i < line.size(); ++i) 2108 | text[i] = line[i].mChar; 2109 | 2110 | result.emplace_back(std::move(text)); 2111 | } 2112 | 2113 | return result; 2114 | } 2115 | 2116 | std::string TextEditor::GetSelectedText() const 2117 | { 2118 | return GetText(mState.mSelectionStart, mState.mSelectionEnd); 2119 | } 2120 | 2121 | std::string TextEditor::GetCurrentLineText()const 2122 | { 2123 | auto lineLength = GetLineMaxColumn(mState.mCursorPosition.mLine); 2124 | return GetText( 2125 | Coordinates(mState.mCursorPosition.mLine, 0), 2126 | Coordinates(mState.mCursorPosition.mLine, lineLength)); 2127 | } 2128 | 2129 | void TextEditor::ProcessInputs() 2130 | { 2131 | } 2132 | 2133 | void TextEditor::Colorize(int aFromLine, int aLines) 2134 | { 2135 | int toLine = aLines == -1 ? (int)mLines.size() : std::min((int)mLines.size(), aFromLine + aLines); 2136 | mColorRangeMin = std::min(mColorRangeMin, aFromLine); 2137 | mColorRangeMax = std::max(mColorRangeMax, toLine); 2138 | mColorRangeMin = std::max(0, mColorRangeMin); 2139 | mColorRangeMax = std::max(mColorRangeMin, mColorRangeMax); 2140 | mCheckComments = true; 2141 | } 2142 | 2143 | void TextEditor::ColorizeRange(int aFromLine, int aToLine) 2144 | { 2145 | if (mLines.empty() || aFromLine >= aToLine) 2146 | return; 2147 | 2148 | std::string buffer; 2149 | std::cmatch results; 2150 | std::string id; 2151 | 2152 | int endLine = std::max(0, std::min((int)mLines.size(), aToLine)); 2153 | for (int i = aFromLine; i < endLine; ++i) 2154 | { 2155 | auto& line = mLines[i]; 2156 | 2157 | if (line.empty()) 2158 | continue; 2159 | 2160 | buffer.resize(line.size()); 2161 | for (size_t j = 0; j < line.size(); ++j) 2162 | { 2163 | auto& col = line[j]; 2164 | buffer[j] = col.mChar; 2165 | col.mColorIndex = PaletteIndex::Default; 2166 | } 2167 | 2168 | const char * bufferBegin = &buffer.front(); 2169 | const char * bufferEnd = bufferBegin + buffer.size(); 2170 | 2171 | auto last = bufferEnd; 2172 | 2173 | for (auto first = bufferBegin; first != last; ) 2174 | { 2175 | const char * token_begin = nullptr; 2176 | const char * token_end = nullptr; 2177 | PaletteIndex token_color = PaletteIndex::Default; 2178 | 2179 | bool hasTokenizeResult = false; 2180 | 2181 | if (mLanguageDefinition.mTokenize != nullptr) 2182 | { 2183 | if (mLanguageDefinition.mTokenize(first, last, token_begin, token_end, token_color)) 2184 | hasTokenizeResult = true; 2185 | } 2186 | 2187 | if (hasTokenizeResult == false) 2188 | { 2189 | // todo : remove 2190 | //printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), first); 2191 | 2192 | for (auto& p : mRegexList) 2193 | { 2194 | if (std::regex_search(first, last, results, p.first, std::regex_constants::match_continuous)) 2195 | { 2196 | hasTokenizeResult = true; 2197 | 2198 | auto& v = *results.begin(); 2199 | token_begin = v.first; 2200 | token_end = v.second; 2201 | token_color = p.second; 2202 | break; 2203 | } 2204 | } 2205 | } 2206 | 2207 | if (hasTokenizeResult == false) 2208 | { 2209 | first++; 2210 | } 2211 | else 2212 | { 2213 | const size_t token_length = token_end - token_begin; 2214 | 2215 | if (token_color == PaletteIndex::Identifier) 2216 | { 2217 | id.assign(token_begin, token_end); 2218 | 2219 | // todo : allmost all language definitions use lower case to specify keywords, so shouldn't this use ::tolower ? 2220 | if (!mLanguageDefinition.mCaseSensitive) 2221 | std::transform(id.begin(), id.end(), id.begin(), ::toupper); 2222 | 2223 | if (!line[first - bufferBegin].mPreprocessor) 2224 | { 2225 | if (mLanguageDefinition.mKeywords.count(id) != 0) 2226 | token_color = PaletteIndex::Keyword; 2227 | else if (mLanguageDefinition.mIdentifiers.count(id) != 0) 2228 | token_color = PaletteIndex::KnownIdentifier; 2229 | else if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0) 2230 | token_color = PaletteIndex::PreprocIdentifier; 2231 | } 2232 | else 2233 | { 2234 | if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0) 2235 | token_color = PaletteIndex::PreprocIdentifier; 2236 | } 2237 | } 2238 | 2239 | for (size_t j = 0; j < token_length; ++j) 2240 | line[(token_begin - bufferBegin) + j].mColorIndex = token_color; 2241 | 2242 | first = token_end; 2243 | } 2244 | } 2245 | } 2246 | } 2247 | 2248 | void TextEditor::ColorizeInternal() 2249 | { 2250 | if (mLines.empty() || !mColorizerEnabled) 2251 | return; 2252 | 2253 | if (mCheckComments) 2254 | { 2255 | auto endLine = mLines.size(); 2256 | auto endIndex = 0; 2257 | auto commentStartLine = endLine; 2258 | auto commentStartIndex = endIndex; 2259 | auto withinString = false; 2260 | auto withinSingleLineComment = false; 2261 | auto withinPreproc = false; 2262 | auto firstChar = true; // there is no other non-whitespace characters in the line before 2263 | auto concatenate = false; // '\' on the very end of the line 2264 | auto currentLine = 0; 2265 | auto currentIndex = 0; 2266 | while (currentLine < endLine || currentIndex < endIndex) 2267 | { 2268 | auto& line = mLines[currentLine]; 2269 | 2270 | if (currentIndex == 0 && !concatenate) 2271 | { 2272 | withinSingleLineComment = false; 2273 | withinPreproc = false; 2274 | firstChar = true; 2275 | } 2276 | 2277 | concatenate = false; 2278 | 2279 | if (!line.empty()) 2280 | { 2281 | auto& g = line[currentIndex]; 2282 | auto c = g.mChar; 2283 | 2284 | if (c != mLanguageDefinition.mPreprocChar && !isspace(c)) 2285 | firstChar = false; 2286 | 2287 | if (currentIndex == (int)line.size() - 1 && line[line.size() - 1].mChar == '\\') 2288 | concatenate = true; 2289 | 2290 | bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); 2291 | 2292 | if (withinString) 2293 | { 2294 | line[currentIndex].mMultiLineComment = inComment; 2295 | 2296 | if (c == '\"') 2297 | { 2298 | if (currentIndex + 1 < (int)line.size() && line[currentIndex + 1].mChar == '\"') 2299 | { 2300 | currentIndex += 1; 2301 | if (currentIndex < (int)line.size()) 2302 | line[currentIndex].mMultiLineComment = inComment; 2303 | } 2304 | else 2305 | withinString = false; 2306 | } 2307 | else if (c == '\\') 2308 | { 2309 | currentIndex += 1; 2310 | if (currentIndex < (int)line.size()) 2311 | line[currentIndex].mMultiLineComment = inComment; 2312 | } 2313 | } 2314 | else 2315 | { 2316 | if (firstChar && c == mLanguageDefinition.mPreprocChar) 2317 | withinPreproc = true; 2318 | 2319 | if (c == '\"') 2320 | { 2321 | withinString = true; 2322 | line[currentIndex].mMultiLineComment = inComment; 2323 | } 2324 | else 2325 | { 2326 | auto pred = [](const char& a, const Glyph& b) { return a == b.mChar; }; 2327 | auto from = line.begin() + currentIndex; 2328 | auto& startStr = mLanguageDefinition.mCommentStart; 2329 | auto& singleStartStr = mLanguageDefinition.mSingleLineComment; 2330 | 2331 | if (singleStartStr.size() > 0 && 2332 | currentIndex + singleStartStr.size() <= line.size() && 2333 | equals(singleStartStr.begin(), singleStartStr.end(), from, from + singleStartStr.size(), pred)) 2334 | { 2335 | withinSingleLineComment = true; 2336 | } 2337 | else if (!withinSingleLineComment && currentIndex + startStr.size() <= line.size() && 2338 | equals(startStr.begin(), startStr.end(), from, from + startStr.size(), pred)) 2339 | { 2340 | commentStartLine = currentLine; 2341 | commentStartIndex = currentIndex; 2342 | } 2343 | 2344 | inComment = inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); 2345 | 2346 | line[currentIndex].mMultiLineComment = inComment; 2347 | line[currentIndex].mComment = withinSingleLineComment; 2348 | 2349 | auto& endStr = mLanguageDefinition.mCommentEnd; 2350 | if (currentIndex + 1 >= (int)endStr.size() && 2351 | equals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred)) 2352 | { 2353 | commentStartIndex = endIndex; 2354 | commentStartLine = endLine; 2355 | } 2356 | } 2357 | } 2358 | line[currentIndex].mPreprocessor = withinPreproc; 2359 | currentIndex += UTF8CharLength(c); 2360 | if (currentIndex >= (int)line.size()) 2361 | { 2362 | currentIndex = 0; 2363 | ++currentLine; 2364 | } 2365 | } 2366 | else 2367 | { 2368 | currentIndex = 0; 2369 | ++currentLine; 2370 | } 2371 | } 2372 | mCheckComments = false; 2373 | } 2374 | 2375 | if (mColorRangeMin < mColorRangeMax) 2376 | { 2377 | const int increment = (mLanguageDefinition.mTokenize == nullptr) ? 10 : 10000; 2378 | const int to = std::min(mColorRangeMin + increment, mColorRangeMax); 2379 | ColorizeRange(mColorRangeMin, to); 2380 | mColorRangeMin = to; 2381 | 2382 | if (mColorRangeMax == mColorRangeMin) 2383 | { 2384 | mColorRangeMin = std::numeric_limits::max(); 2385 | mColorRangeMax = 0; 2386 | } 2387 | return; 2388 | } 2389 | } 2390 | 2391 | float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const 2392 | { 2393 | auto& line = mLines[aFrom.mLine]; 2394 | float distance = 0.0f; 2395 | float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x; 2396 | int colIndex = GetCharacterIndex(aFrom); 2397 | for (size_t it = 0u; it < line.size() && it < colIndex; ) 2398 | { 2399 | if (line[it].mChar == '\t') 2400 | { 2401 | distance = (1.0f + std::floor((1.0f + distance) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); 2402 | ++it; 2403 | } 2404 | else 2405 | { 2406 | auto d = UTF8CharLength(line[it].mChar); 2407 | char tempCString[7]; 2408 | int i = 0; 2409 | for (; i < 6 && d-- > 0 && it < (int)line.size(); i++, it++) 2410 | tempCString[i] = line[it].mChar; 2411 | 2412 | tempCString[i] = '\0'; 2413 | distance += ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, tempCString, nullptr, nullptr).x; 2414 | } 2415 | } 2416 | 2417 | return distance; 2418 | } 2419 | 2420 | void TextEditor::EnsureCursorVisible() 2421 | { 2422 | if (!mWithinRender) 2423 | { 2424 | mScrollToCursor = true; 2425 | return; 2426 | } 2427 | 2428 | float scrollX = ImGui::GetScrollX(); 2429 | float scrollY = ImGui::GetScrollY(); 2430 | 2431 | auto height = ImGui::GetWindowHeight(); 2432 | auto width = ImGui::GetWindowWidth(); 2433 | 2434 | auto top = 1 + (int)ceil(scrollY / mCharAdvance.y); 2435 | auto bottom = (int)ceil((scrollY + height) / mCharAdvance.y); 2436 | 2437 | auto left = (int)ceil(scrollX / mCharAdvance.x); 2438 | auto right = (int)ceil((scrollX + width) / mCharAdvance.x); 2439 | 2440 | auto pos = GetActualCursorCoordinates(); 2441 | auto len = TextDistanceToLineStart(pos); 2442 | 2443 | if (pos.mLine < top) 2444 | ImGui::SetScrollY(std::max(0.0f, (pos.mLine - 1) * mCharAdvance.y)); 2445 | if (pos.mLine > bottom - 4) 2446 | ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 4) * mCharAdvance.y - height)); 2447 | if (len + mTextStart < left + 4) 2448 | ImGui::SetScrollX(std::max(0.0f, len + mTextStart - 4)); 2449 | if (len + mTextStart > right - 4) 2450 | ImGui::SetScrollX(std::max(0.0f, len + mTextStart + 4 - width)); 2451 | } 2452 | 2453 | int TextEditor::GetPageSize() const 2454 | { 2455 | auto height = ImGui::GetWindowHeight() - 20.0f; 2456 | return (int)floor(height / mCharAdvance.y); 2457 | } 2458 | 2459 | TextEditor::UndoRecord::UndoRecord( 2460 | const std::string& aAdded, 2461 | const TextEditor::Coordinates aAddedStart, 2462 | const TextEditor::Coordinates aAddedEnd, 2463 | const std::string& aRemoved, 2464 | const TextEditor::Coordinates aRemovedStart, 2465 | const TextEditor::Coordinates aRemovedEnd, 2466 | TextEditor::EditorState& aBefore, 2467 | TextEditor::EditorState& aAfter) 2468 | : mAdded(aAdded) 2469 | , mAddedStart(aAddedStart) 2470 | , mAddedEnd(aAddedEnd) 2471 | , mRemoved(aRemoved) 2472 | , mRemovedStart(aRemovedStart) 2473 | , mRemovedEnd(aRemovedEnd) 2474 | , mBefore(aBefore) 2475 | , mAfter(aAfter) 2476 | { 2477 | assert(mAddedStart <= mAddedEnd); 2478 | assert(mRemovedStart <= mRemovedEnd); 2479 | } 2480 | 2481 | void TextEditor::UndoRecord::Undo(TextEditor * aEditor) 2482 | { 2483 | if (!mAdded.empty()) 2484 | { 2485 | aEditor->DeleteRange(mAddedStart, mAddedEnd); 2486 | aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 2); 2487 | } 2488 | 2489 | if (!mRemoved.empty()) 2490 | { 2491 | auto start = mRemovedStart; 2492 | aEditor->InsertTextAt(start, mRemoved.c_str()); 2493 | aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 2); 2494 | } 2495 | 2496 | aEditor->mState = mBefore; 2497 | aEditor->EnsureCursorVisible(); 2498 | 2499 | } 2500 | 2501 | void TextEditor::UndoRecord::Redo(TextEditor * aEditor) 2502 | { 2503 | if (!mRemoved.empty()) 2504 | { 2505 | aEditor->DeleteRange(mRemovedStart, mRemovedEnd); 2506 | aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 1); 2507 | } 2508 | 2509 | if (!mAdded.empty()) 2510 | { 2511 | auto start = mAddedStart; 2512 | aEditor->InsertTextAt(start, mAdded.c_str()); 2513 | aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 1); 2514 | } 2515 | 2516 | aEditor->mState = mAfter; 2517 | aEditor->EnsureCursorVisible(); 2518 | } 2519 | 2520 | static bool TokenizeCStyleString(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) 2521 | { 2522 | const char * p = in_begin; 2523 | 2524 | if (*p == '"') 2525 | { 2526 | p++; 2527 | 2528 | while (p < in_end) 2529 | { 2530 | // handle end of string 2531 | if (*p == '"') 2532 | { 2533 | out_begin = in_begin; 2534 | out_end = p + 1; 2535 | return true; 2536 | } 2537 | 2538 | // handle escape character for " 2539 | if (*p == '\\' && p + 1 < in_end && p[1] == '"') 2540 | p++; 2541 | 2542 | p++; 2543 | } 2544 | } 2545 | 2546 | return false; 2547 | } 2548 | 2549 | static bool TokenizeCStyleCharacterLiteral(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) 2550 | { 2551 | const char * p = in_begin; 2552 | 2553 | if (*p == '\'') 2554 | { 2555 | p++; 2556 | 2557 | // handle escape characters 2558 | if (p < in_end && *p == '\\') 2559 | p++; 2560 | 2561 | if (p < in_end) 2562 | p++; 2563 | 2564 | // handle end of character literal 2565 | if (p < in_end && *p == '\'') 2566 | { 2567 | out_begin = in_begin; 2568 | out_end = p + 1; 2569 | return true; 2570 | } 2571 | } 2572 | 2573 | return false; 2574 | } 2575 | 2576 | static bool TokenizeCStyleIdentifier(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) 2577 | { 2578 | const char * p = in_begin; 2579 | 2580 | if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_') 2581 | { 2582 | p++; 2583 | 2584 | while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) 2585 | p++; 2586 | 2587 | out_begin = in_begin; 2588 | out_end = p; 2589 | return true; 2590 | } 2591 | 2592 | return false; 2593 | } 2594 | 2595 | static bool TokenizeCStyleNumber(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) 2596 | { 2597 | const char * p = in_begin; 2598 | 2599 | const bool startsWithNumber = *p >= '0' && *p <= '9'; 2600 | 2601 | if (*p != '+' && *p != '-' && !startsWithNumber) 2602 | return false; 2603 | 2604 | p++; 2605 | 2606 | bool hasNumber = startsWithNumber; 2607 | 2608 | while (p < in_end && (*p >= '0' && *p <= '9')) 2609 | { 2610 | hasNumber = true; 2611 | 2612 | p++; 2613 | } 2614 | 2615 | if (hasNumber == false) 2616 | return false; 2617 | 2618 | bool isFloat = false; 2619 | bool isHex = false; 2620 | bool isBinary = false; 2621 | 2622 | if (p < in_end) 2623 | { 2624 | if (*p == '.') 2625 | { 2626 | isFloat = true; 2627 | 2628 | p++; 2629 | 2630 | while (p < in_end && (*p >= '0' && *p <= '9')) 2631 | p++; 2632 | } 2633 | else if (*p == 'x' || *p == 'X') 2634 | { 2635 | // hex formatted integer of the type 0xef80 2636 | 2637 | isHex = true; 2638 | 2639 | p++; 2640 | 2641 | while (p < in_end && ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F'))) 2642 | p++; 2643 | } 2644 | else if (*p == 'b' || *p == 'B') 2645 | { 2646 | // binary formatted integer of the type 0b01011101 2647 | 2648 | isBinary = true; 2649 | 2650 | p++; 2651 | 2652 | while (p < in_end && (*p >= '0' && *p <= '1')) 2653 | p++; 2654 | } 2655 | } 2656 | 2657 | if (isHex == false && isBinary == false) 2658 | { 2659 | // floating point exponent 2660 | if (p < in_end && (*p == 'e' || *p == 'E')) 2661 | { 2662 | isFloat = true; 2663 | 2664 | p++; 2665 | 2666 | if (p < in_end && (*p == '+' || *p == '-')) 2667 | p++; 2668 | 2669 | bool hasDigits = false; 2670 | 2671 | while (p < in_end && (*p >= '0' && *p <= '9')) 2672 | { 2673 | hasDigits = true; 2674 | 2675 | p++; 2676 | } 2677 | 2678 | if (hasDigits == false) 2679 | return false; 2680 | } 2681 | 2682 | // single precision floating point type 2683 | if (p < in_end && *p == 'f') 2684 | p++; 2685 | } 2686 | 2687 | if (isFloat == false) 2688 | { 2689 | // integer size type 2690 | while (p < in_end && (*p == 'u' || *p == 'U' || *p == 'l' || *p == 'L')) 2691 | p++; 2692 | } 2693 | 2694 | out_begin = in_begin; 2695 | out_end = p; 2696 | return true; 2697 | } 2698 | 2699 | static bool TokenizeCStylePunctuation(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) 2700 | { 2701 | (void)in_end; 2702 | 2703 | switch (*in_begin) 2704 | { 2705 | case '[': 2706 | case ']': 2707 | case '{': 2708 | case '}': 2709 | case '!': 2710 | case '%': 2711 | case '^': 2712 | case '&': 2713 | case '*': 2714 | case '(': 2715 | case ')': 2716 | case '-': 2717 | case '+': 2718 | case '=': 2719 | case '~': 2720 | case '|': 2721 | case '<': 2722 | case '>': 2723 | case '?': 2724 | case ':': 2725 | case '/': 2726 | case ';': 2727 | case ',': 2728 | case '.': 2729 | out_begin = in_begin; 2730 | out_end = in_begin + 1; 2731 | return true; 2732 | } 2733 | 2734 | return false; 2735 | } 2736 | 2737 | const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::CPlusPlus() 2738 | { 2739 | static bool inited = false; 2740 | static LanguageDefinition langDef; 2741 | if (!inited) 2742 | { 2743 | static const char* const cppKeywords[] = { 2744 | "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "char16_t", "char32_t", "class", 2745 | "compl", "concept", "const", "constexpr", "const_cast", "continue", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", 2746 | "for", "friend", "goto", "if", "import", "inline", "int", "long", "module", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public", 2747 | "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this", "thread_local", 2748 | "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq" 2749 | }; 2750 | for (auto& k : cppKeywords) 2751 | langDef.mKeywords.insert(k); 2752 | 2753 | static const char* const identifiers[] = { 2754 | "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", 2755 | "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "printf", "sprintf", "snprintf", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper", 2756 | "std", "string", "vector", "map", "unordered_map", "set", "unordered_set", "min", "max" 2757 | }; 2758 | for (auto& k : identifiers) 2759 | { 2760 | Identifier id; 2761 | id.mDeclaration = "Built-in function"; 2762 | langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); 2763 | } 2764 | 2765 | langDef.mTokenize = [](const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex) -> bool 2766 | { 2767 | paletteIndex = PaletteIndex::Max; 2768 | 2769 | while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) 2770 | in_begin++; 2771 | 2772 | if (in_begin == in_end) 2773 | { 2774 | out_begin = in_end; 2775 | out_end = in_end; 2776 | paletteIndex = PaletteIndex::Default; 2777 | } 2778 | else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) 2779 | paletteIndex = PaletteIndex::String; 2780 | else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) 2781 | paletteIndex = PaletteIndex::CharLiteral; 2782 | else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) 2783 | paletteIndex = PaletteIndex::Identifier; 2784 | else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) 2785 | paletteIndex = PaletteIndex::Number; 2786 | else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) 2787 | paletteIndex = PaletteIndex::Punctuation; 2788 | 2789 | return paletteIndex != PaletteIndex::Max; 2790 | }; 2791 | 2792 | langDef.mCommentStart = "/*"; 2793 | langDef.mCommentEnd = "*/"; 2794 | langDef.mSingleLineComment = "//"; 2795 | 2796 | langDef.mCaseSensitive = true; 2797 | langDef.mAutoIndentation = true; 2798 | 2799 | langDef.mName = "C++"; 2800 | 2801 | inited = true; 2802 | } 2803 | return langDef; 2804 | } 2805 | 2806 | const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::HLSL() 2807 | { 2808 | static bool inited = false; 2809 | static LanguageDefinition langDef; 2810 | if (!inited) 2811 | { 2812 | static const char* const keywords[] = { 2813 | "AppendStructuredBuffer", "asm", "asm_fragment", "BlendState", "bool", "break", "Buffer", "ByteAddressBuffer", "case", "cbuffer", "centroid", "class", "column_major", "compile", "compile_fragment", 2814 | "CompileShader", "const", "continue", "ComputeShader", "ConsumeStructuredBuffer", "default", "DepthStencilState", "DepthStencilView", "discard", "do", "double", "DomainShader", "dword", "else", 2815 | "export", "extern", "false", "float", "for", "fxgroup", "GeometryShader", "groupshared", "half", "Hullshader", "if", "in", "inline", "inout", "InputPatch", "int", "interface", "line", "lineadj", 2816 | "linear", "LineStream", "matrix", "min16float", "min10float", "min16int", "min12int", "min16uint", "namespace", "nointerpolation", "noperspective", "NULL", "out", "OutputPatch", "packoffset", 2817 | "pass", "pixelfragment", "PixelShader", "point", "PointStream", "precise", "RasterizerState", "RenderTargetView", "return", "register", "row_major", "RWBuffer", "RWByteAddressBuffer", "RWStructuredBuffer", 2818 | "RWTexture1D", "RWTexture1DArray", "RWTexture2D", "RWTexture2DArray", "RWTexture3D", "sample", "sampler", "SamplerState", "SamplerComparisonState", "shared", "snorm", "stateblock", "stateblock_state", 2819 | "static", "string", "struct", "switch", "StructuredBuffer", "tbuffer", "technique", "technique10", "technique11", "texture", "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", "Texture2DMS", 2820 | "Texture2DMSArray", "Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle", "triangleadj", "TriangleStream", "uint", "uniform", "unorm", "unsigned", "vector", "vertexfragment", 2821 | "VertexShader", "void", "volatile", "while", 2822 | "bool1","bool2","bool3","bool4","double1","double2","double3","double4", "float1", "float2", "float3", "float4", "int1", "int2", "int3", "int4", "in", "out", "inout", 2823 | "uint1", "uint2", "uint3", "uint4", "dword1", "dword2", "dword3", "dword4", "half1", "half2", "half3", "half4", 2824 | "float1x1","float2x1","float3x1","float4x1","float1x2","float2x2","float3x2","float4x2", 2825 | "float1x3","float2x3","float3x3","float4x3","float1x4","float2x4","float3x4","float4x4", 2826 | "half1x1","half2x1","half3x1","half4x1","half1x2","half2x2","half3x2","half4x2", 2827 | "half1x3","half2x3","half3x3","half4x3","half1x4","half2x4","half3x4","half4x4", 2828 | }; 2829 | for (auto& k : keywords) 2830 | langDef.mKeywords.insert(k); 2831 | 2832 | static const char* const identifiers[] = { 2833 | "abort", "abs", "acos", "all", "AllMemoryBarrier", "AllMemoryBarrierWithGroupSync", "any", "asdouble", "asfloat", "asin", "asint", "asint", "asuint", 2834 | "asuint", "atan", "atan2", "ceil", "CheckAccessFullyMapped", "clamp", "clip", "cos", "cosh", "countbits", "cross", "D3DCOLORtoUBYTE4", "ddx", 2835 | "ddx_coarse", "ddx_fine", "ddy", "ddy_coarse", "ddy_fine", "degrees", "determinant", "DeviceMemoryBarrier", "DeviceMemoryBarrierWithGroupSync", 2836 | "distance", "dot", "dst", "errorf", "EvaluateAttributeAtCentroid", "EvaluateAttributeAtSample", "EvaluateAttributeSnapped", "exp", "exp2", 2837 | "f16tof32", "f32tof16", "faceforward", "firstbithigh", "firstbitlow", "floor", "fma", "fmod", "frac", "frexp", "fwidth", "GetRenderTargetSampleCount", 2838 | "GetRenderTargetSamplePosition", "GroupMemoryBarrier", "GroupMemoryBarrierWithGroupSync", "InterlockedAdd", "InterlockedAnd", "InterlockedCompareExchange", 2839 | "InterlockedCompareStore", "InterlockedExchange", "InterlockedMax", "InterlockedMin", "InterlockedOr", "InterlockedXor", "isfinite", "isinf", "isnan", 2840 | "ldexp", "length", "lerp", "lit", "log", "log10", "log2", "mad", "max", "min", "modf", "msad4", "mul", "noise", "normalize", "pow", "printf", 2841 | "Process2DQuadTessFactorsAvg", "Process2DQuadTessFactorsMax", "Process2DQuadTessFactorsMin", "ProcessIsolineTessFactors", "ProcessQuadTessFactorsAvg", 2842 | "ProcessQuadTessFactorsMax", "ProcessQuadTessFactorsMin", "ProcessTriTessFactorsAvg", "ProcessTriTessFactorsMax", "ProcessTriTessFactorsMin", 2843 | "radians", "rcp", "reflect", "refract", "reversebits", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "sqrt", "step", 2844 | "tan", "tanh", "tex1D", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", "tex2D", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj", 2845 | "tex3D", "tex3D", "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", "texCUBE", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose", "trunc" 2846 | }; 2847 | for (auto& k : identifiers) 2848 | { 2849 | Identifier id; 2850 | id.mDeclaration = "Built-in function"; 2851 | langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); 2852 | } 2853 | 2854 | langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); 2855 | langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); 2856 | langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); 2857 | langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); 2858 | langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); 2859 | langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); 2860 | langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); 2861 | langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); 2862 | langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); 2863 | 2864 | langDef.mCommentStart = "/*"; 2865 | langDef.mCommentEnd = "*/"; 2866 | langDef.mSingleLineComment = "//"; 2867 | 2868 | langDef.mCaseSensitive = true; 2869 | langDef.mAutoIndentation = true; 2870 | 2871 | langDef.mName = "HLSL"; 2872 | 2873 | inited = true; 2874 | } 2875 | return langDef; 2876 | } 2877 | 2878 | const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL() 2879 | { 2880 | static bool inited = false; 2881 | static LanguageDefinition langDef; 2882 | if (!inited) 2883 | { 2884 | static const char* const keywords[] = { 2885 | "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", 2886 | "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", 2887 | "_Noreturn", "_Static_assert", "_Thread_local" 2888 | }; 2889 | for (auto& k : keywords) 2890 | langDef.mKeywords.insert(k); 2891 | 2892 | static const char* const identifiers[] = { 2893 | "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", 2894 | "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" 2895 | }; 2896 | for (auto& k : identifiers) 2897 | { 2898 | Identifier id; 2899 | id.mDeclaration = "Built-in function"; 2900 | langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); 2901 | } 2902 | 2903 | langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); 2904 | langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); 2905 | langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); 2906 | langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); 2907 | langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); 2908 | langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); 2909 | langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); 2910 | langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); 2911 | langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); 2912 | 2913 | langDef.mCommentStart = "/*"; 2914 | langDef.mCommentEnd = "*/"; 2915 | langDef.mSingleLineComment = "//"; 2916 | 2917 | langDef.mCaseSensitive = true; 2918 | langDef.mAutoIndentation = true; 2919 | 2920 | langDef.mName = "GLSL"; 2921 | 2922 | inited = true; 2923 | } 2924 | return langDef; 2925 | } 2926 | 2927 | const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::C() 2928 | { 2929 | static bool inited = false; 2930 | static LanguageDefinition langDef; 2931 | if (!inited) 2932 | { 2933 | static const char* const keywords[] = { 2934 | "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", 2935 | "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", 2936 | "_Noreturn", "_Static_assert", "_Thread_local" 2937 | }; 2938 | for (auto& k : keywords) 2939 | langDef.mKeywords.insert(k); 2940 | 2941 | static const char* const identifiers[] = { 2942 | "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", 2943 | "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper" 2944 | }; 2945 | for (auto& k : identifiers) 2946 | { 2947 | Identifier id; 2948 | id.mDeclaration = "Built-in function"; 2949 | langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); 2950 | } 2951 | 2952 | langDef.mTokenize = [](const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex) -> bool 2953 | { 2954 | paletteIndex = PaletteIndex::Max; 2955 | 2956 | while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) 2957 | in_begin++; 2958 | 2959 | if (in_begin == in_end) 2960 | { 2961 | out_begin = in_end; 2962 | out_end = in_end; 2963 | paletteIndex = PaletteIndex::Default; 2964 | } 2965 | else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) 2966 | paletteIndex = PaletteIndex::String; 2967 | else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) 2968 | paletteIndex = PaletteIndex::CharLiteral; 2969 | else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) 2970 | paletteIndex = PaletteIndex::Identifier; 2971 | else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) 2972 | paletteIndex = PaletteIndex::Number; 2973 | else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) 2974 | paletteIndex = PaletteIndex::Punctuation; 2975 | 2976 | return paletteIndex != PaletteIndex::Max; 2977 | }; 2978 | 2979 | langDef.mCommentStart = "/*"; 2980 | langDef.mCommentEnd = "*/"; 2981 | langDef.mSingleLineComment = "//"; 2982 | 2983 | langDef.mCaseSensitive = true; 2984 | langDef.mAutoIndentation = true; 2985 | 2986 | langDef.mName = "C"; 2987 | 2988 | inited = true; 2989 | } 2990 | return langDef; 2991 | } 2992 | 2993 | const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::SQL() 2994 | { 2995 | static bool inited = false; 2996 | static LanguageDefinition langDef; 2997 | if (!inited) 2998 | { 2999 | static const char* const keywords[] = { 3000 | "ADD", "EXCEPT", "PERCENT", "ALL", "EXEC", "PLAN", "ALTER", "EXECUTE", "PRECISION", "AND", "EXISTS", "PRIMARY", "ANY", "EXIT", "PRINT", "AS", "FETCH", "PROC", "ASC", "FILE", "PROCEDURE", 3001 | "AUTHORIZATION", "FILLFACTOR", "PUBLIC", "BACKUP", "FOR", "RAISERROR", "BEGIN", "FOREIGN", "READ", "BETWEEN", "FREETEXT", "READTEXT", "BREAK", "FREETEXTTABLE", "RECONFIGURE", 3002 | "BROWSE", "FROM", "REFERENCES", "BULK", "FULL", "REPLICATION", "BY", "FUNCTION", "RESTORE", "CASCADE", "GOTO", "RESTRICT", "CASE", "GRANT", "RETURN", "CHECK", "GROUP", "REVOKE", 3003 | "CHECKPOINT", "HAVING", "RIGHT", "CLOSE", "HOLDLOCK", "ROLLBACK", "CLUSTERED", "IDENTITY", "ROWCOUNT", "COALESCE", "IDENTITY_INSERT", "ROWGUIDCOL", "COLLATE", "IDENTITYCOL", "RULE", 3004 | "COLUMN", "IF", "SAVE", "COMMIT", "IN", "SCHEMA", "COMPUTE", "INDEX", "SELECT", "CONSTRAINT", "INNER", "SESSION_USER", "CONTAINS", "INSERT", "SET", "CONTAINSTABLE", "INTERSECT", "SETUSER", 3005 | "CONTINUE", "INTO", "SHUTDOWN", "CONVERT", "IS", "SOME", "CREATE", "JOIN", "STATISTICS", "CROSS", "KEY", "SYSTEM_USER", "CURRENT", "KILL", "TABLE", "CURRENT_DATE", "LEFT", "TEXTSIZE", 3006 | "CURRENT_TIME", "LIKE", "THEN", "CURRENT_TIMESTAMP", "LINENO", "TO", "CURRENT_USER", "LOAD", "TOP", "CURSOR", "NATIONAL", "TRAN", "DATABASE", "NOCHECK", "TRANSACTION", 3007 | "DBCC", "NONCLUSTERED", "TRIGGER", "DEALLOCATE", "NOT", "TRUNCATE", "DECLARE", "NULL", "TSEQUAL", "DEFAULT", "NULLIF", "UNION", "DELETE", "OF", "UNIQUE", "DENY", "OFF", "UPDATE", 3008 | "DESC", "OFFSETS", "UPDATETEXT", "DISK", "ON", "USE", "DISTINCT", "OPEN", "USER", "DISTRIBUTED", "OPENDATASOURCE", "VALUES", "DOUBLE", "OPENQUERY", "VARYING","DROP", "OPENROWSET", "VIEW", 3009 | "DUMMY", "OPENXML", "WAITFOR", "DUMP", "OPTION", "WHEN", "ELSE", "OR", "WHERE", "END", "ORDER", "WHILE", "ERRLVL", "OUTER", "WITH", "ESCAPE", "OVER", "WRITETEXT" 3010 | }; 3011 | 3012 | for (auto& k : keywords) 3013 | langDef.mKeywords.insert(k); 3014 | 3015 | static const char* const identifiers[] = { 3016 | "ABS", "ACOS", "ADD_MONTHS", "ASCII", "ASCIISTR", "ASIN", "ATAN", "ATAN2", "AVG", "BFILENAME", "BIN_TO_NUM", "BITAND", "CARDINALITY", "CASE", "CAST", "CEIL", 3017 | "CHARTOROWID", "CHR", "COALESCE", "COMPOSE", "CONCAT", "CONVERT", "CORR", "COS", "COSH", "COUNT", "COVAR_POP", "COVAR_SAMP", "CUME_DIST", "CURRENT_DATE", 3018 | "CURRENT_TIMESTAMP", "DBTIMEZONE", "DECODE", "DECOMPOSE", "DENSE_RANK", "DUMP", "EMPTY_BLOB", "EMPTY_CLOB", "EXP", "EXTRACT", "FIRST_VALUE", "FLOOR", "FROM_TZ", "GREATEST", 3019 | "GROUP_ID", "HEXTORAW", "INITCAP", "INSTR", "INSTR2", "INSTR4", "INSTRB", "INSTRC", "LAG", "LAST_DAY", "LAST_VALUE", "LEAD", "LEAST", "LENGTH", "LENGTH2", "LENGTH4", 3020 | "LENGTHB", "LENGTHC", "LISTAGG", "LN", "LNNVL", "LOCALTIMESTAMP", "LOG", "LOWER", "LPAD", "LTRIM", "MAX", "MEDIAN", "MIN", "MOD", "MONTHS_BETWEEN", "NANVL", "NCHR", 3021 | "NEW_TIME", "NEXT_DAY", "NTH_VALUE", "NULLIF", "NUMTODSINTERVAL", "NUMTOYMINTERVAL", "NVL", "NVL2", "POWER", "RANK", "RAWTOHEX", "REGEXP_COUNT", "REGEXP_INSTR", 3022 | "REGEXP_REPLACE", "REGEXP_SUBSTR", "REMAINDER", "REPLACE", "ROUND", "ROWNUM", "RPAD", "RTRIM", "SESSIONTIMEZONE", "SIGN", "SIN", "SINH", 3023 | "SOUNDEX", "SQRT", "STDDEV", "SUBSTR", "SUM", "SYS_CONTEXT", "SYSDATE", "SYSTIMESTAMP", "TAN", "TANH", "TO_CHAR", "TO_CLOB", "TO_DATE", "TO_DSINTERVAL", "TO_LOB", 3024 | "TO_MULTI_BYTE", "TO_NCLOB", "TO_NUMBER", "TO_SINGLE_BYTE", "TO_TIMESTAMP", "TO_TIMESTAMP_TZ", "TO_YMINTERVAL", "TRANSLATE", "TRIM", "TRUNC", "TZ_OFFSET", "UID", "UPPER", 3025 | "USER", "USERENV", "VAR_POP", "VAR_SAMP", "VARIANCE", "VSIZE " 3026 | }; 3027 | for (auto& k : identifiers) 3028 | { 3029 | Identifier id; 3030 | id.mDeclaration = "Built-in function"; 3031 | langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); 3032 | } 3033 | 3034 | langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); 3035 | langDef.mTokenRegexStrings.push_back(std::make_pair("\\\'[^\\\']*\\\'", PaletteIndex::String)); 3036 | langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); 3037 | langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); 3038 | langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); 3039 | langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); 3040 | langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); 3041 | langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); 3042 | 3043 | langDef.mCommentStart = "/*"; 3044 | langDef.mCommentEnd = "*/"; 3045 | langDef.mSingleLineComment = "//"; 3046 | 3047 | langDef.mCaseSensitive = false; 3048 | langDef.mAutoIndentation = false; 3049 | 3050 | langDef.mName = "SQL"; 3051 | 3052 | inited = true; 3053 | } 3054 | return langDef; 3055 | } 3056 | 3057 | const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::AngelScript() 3058 | { 3059 | static bool inited = false; 3060 | static LanguageDefinition langDef; 3061 | if (!inited) 3062 | { 3063 | static const char* const keywords[] = { 3064 | "and", "abstract", "auto", "bool", "break", "case", "cast", "class", "const", "continue", "default", "do", "double", "else", "enum", "false", "final", "float", "for", 3065 | "from", "funcdef", "function", "get", "if", "import", "in", "inout", "int", "interface", "int8", "int16", "int32", "int64", "is", "mixin", "namespace", "not", 3066 | "null", "or", "out", "override", "private", "protected", "return", "set", "shared", "super", "switch", "this ", "true", "typedef", "uint", "uint8", "uint16", "uint32", 3067 | "uint64", "void", "while", "xor" 3068 | }; 3069 | 3070 | for (auto& k : keywords) 3071 | langDef.mKeywords.insert(k); 3072 | 3073 | static const char* const identifiers[] = { 3074 | "cos", "sin", "tab", "acos", "asin", "atan", "atan2", "cosh", "sinh", "tanh", "log", "log10", "pow", "sqrt", "abs", "ceil", "floor", "fraction", "closeTo", "fpFromIEEE", "fpToIEEE", 3075 | "complex", "opEquals", "opAddAssign", "opSubAssign", "opMulAssign", "opDivAssign", "opAdd", "opSub", "opMul", "opDiv" 3076 | }; 3077 | for (auto& k : identifiers) 3078 | { 3079 | Identifier id; 3080 | id.mDeclaration = "Built-in function"; 3081 | langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); 3082 | } 3083 | 3084 | langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); 3085 | langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::String)); 3086 | langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); 3087 | langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); 3088 | langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); 3089 | langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); 3090 | langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); 3091 | langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); 3092 | 3093 | langDef.mCommentStart = "/*"; 3094 | langDef.mCommentEnd = "*/"; 3095 | langDef.mSingleLineComment = "//"; 3096 | 3097 | langDef.mCaseSensitive = true; 3098 | langDef.mAutoIndentation = true; 3099 | 3100 | langDef.mName = "AngelScript"; 3101 | 3102 | inited = true; 3103 | } 3104 | return langDef; 3105 | } 3106 | 3107 | const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lua() 3108 | { 3109 | static bool inited = false; 3110 | static LanguageDefinition langDef; 3111 | if (!inited) 3112 | { 3113 | static const char* const keywords[] = { 3114 | "and", "break", "do", "", "else", "elseif", "end", "false", "for", "function", "if", "in", "", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" 3115 | }; 3116 | 3117 | for (auto& k : keywords) 3118 | langDef.mKeywords.insert(k); 3119 | 3120 | static const char* const identifiers[] = { 3121 | "assert", "collectgarbage", "dofile", "error", "getmetatable", "ipairs", "loadfile", "load", "loadstring", "next", "pairs", "pcall", "print", "rawequal", "rawlen", "rawget", "rawset", 3122 | "select", "setmetatable", "tonumber", "tostring", "type", "xpcall", "_G", "_VERSION","arshift", "band", "bnot", "bor", "bxor", "btest", "extract", "lrotate", "lshift", "replace", 3123 | "rrotate", "rshift", "create", "resume", "running", "status", "wrap", "yield", "isyieldable", "debug","getuservalue", "gethook", "getinfo", "getlocal", "getregistry", "getmetatable", 3124 | "getupvalue", "upvaluejoin", "upvalueid", "setuservalue", "sethook", "setlocal", "setmetatable", "setupvalue", "traceback", "close", "flush", "input", "lines", "open", "output", "popen", 3125 | "read", "tmpfile", "type", "write", "close", "flush", "lines", "read", "seek", "setvbuf", "write", "__gc", "__tostring", "abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", "tointeger", 3126 | "floor", "fmod", "ult", "log", "max", "min", "modf", "rad", "random", "randomseed", "sin", "sqrt", "string", "tan", "type", "atan2", "cosh", "sinh", "tanh", 3127 | "pow", "frexp", "ldexp", "log10", "pi", "huge", "maxinteger", "mininteger", "loadlib", "searchpath", "seeall", "preload", "cpath", "path", "searchers", "loaded", "module", "require", "clock", 3128 | "date", "difftime", "execute", "exit", "getenv", "remove", "rename", "setlocale", "time", "tmpname", "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep", 3129 | "reverse", "sub", "upper", "pack", "packsize", "unpack", "concat", "maxn", "insert", "pack", "unpack", "remove", "move", "sort", "offset", "codepoint", "char", "len", "codes", "charpattern", 3130 | "coroutine", "table", "io", "os", "string", "utf8", "bit32", "math", "debug", "package" 3131 | }; 3132 | for (auto& k : identifiers) 3133 | { 3134 | Identifier id; 3135 | id.mDeclaration = "Built-in function"; 3136 | langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); 3137 | } 3138 | 3139 | langDef.mTokenRegexStrings.push_back(std::make_pair("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); 3140 | langDef.mTokenRegexStrings.push_back(std::make_pair("\\\'[^\\\']*\\\'", PaletteIndex::String)); 3141 | langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); 3142 | langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); 3143 | langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); 3144 | langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); 3145 | langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation)); 3146 | 3147 | langDef.mCommentStart = "--[["; 3148 | langDef.mCommentEnd = "]]"; 3149 | langDef.mSingleLineComment = "--"; 3150 | 3151 | langDef.mCaseSensitive = true; 3152 | langDef.mAutoIndentation = false; 3153 | 3154 | langDef.mName = "Lua"; 3155 | 3156 | inited = true; 3157 | } 3158 | return langDef; 3159 | } 3160 | --------------------------------------------------------------------------------