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