· 6 years ago · Nov 06, 2019, 03:02 AM
1#include <algorithm>
2#include <chrono>
3#include <string>
4#include <regex>
5#include <cmath>
6
7#include "TextEditor.h"
8
9#define IMGUI_DEFINE_MATH_OPERATORS
10#include "ImGui/imgui.h" // for imGui::GetCurrentWindow()
11
12// TODO
13// - multiline comments vs single-line: latter is blocking start of a ML
14
15template<class InputIt1, class InputIt2, class BinaryPredicate>
16bool 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
27TextEditor::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::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count())
51{
52 SetPalette(GetDarkPalette());
53 SetLanguageDefinition(LanguageDefinition::HLSL());
54 mLines.push_back(Line());
55}
56
57TextEditor::~TextEditor()
58{
59}
60
61void 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
72void TextEditor::SetPalette(const Palette & aValue)
73{
74 mPaletteBase = aValue;
75}
76
77std::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
114TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const
115{
116 return SanitizeCoordinates(mState.mCursorPosition);
117}
118
119TextEditor::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)
146static 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
162static 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
199void 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
220void 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
260int 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
309void 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
324TextEditor::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
376TextEditor::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
410TextEditor::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
443TextEditor::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
493int 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
511int 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
530int 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
541int 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
559bool 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
575void 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
606void 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
636TextEditor::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
655std::string TextEditor::GetWordUnderCursor() const
656{
657 auto c = GetCursorPosition();
658 return GetWordAt(c);
659}
660
661std::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
677ImU32 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
698void 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
778void 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
854void 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::milliseconds>(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
1121void 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
1156void 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
1183void TextEditor::SetTextLines(const std::vector<std::string> & 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
1214void 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
1387void TextEditor::SetReadOnly(bool aValue)
1388{
1389 mReadOnly = aValue;
1390}
1391
1392void TextEditor::SetColorizerEnable(bool aValue)
1393{
1394 mColorizerEnabled = aValue;
1395}
1396
1397void TextEditor::SetCursorPosition(const Coordinates & aPosition)
1398{
1399 if (mState.mCursorPosition != aPosition)
1400 {
1401 mState.mCursorPosition = aPosition;
1402 mCursorPositionChanged = true;
1403 EnsureCursorVisible();
1404 }
1405}
1406
1407void 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
1414void 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
1421void 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
1459void TextEditor::SetTabSize(int aValue)
1460{
1461 mTabSize = std::max(0, std::min(32, aValue));
1462}
1463
1464void TextEditor::InsertText(const std::string & aValue)
1465{
1466 InsertText(aValue.c_str());
1467}
1468
1469void 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
1485void 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
1499void 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
1525void 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
1553static bool IsUTFSequence(char c)
1554{
1555 return (c & 0xC0) == 0x80;
1556}
1557
1558void 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
1624void 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
1675void 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
1693void 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
1708void 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
1733void 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
1758void 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
1816void 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
1894void TextEditor::SelectWordUnderCursor()
1895{
1896 auto c = GetCursorPosition();
1897 SetSelection(FindWordStart(c), FindWordEnd(c));
1898}
1899
1900void TextEditor::SelectAll()
1901{
1902 SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), 0));
1903}
1904
1905bool TextEditor::HasSelection() const
1906{
1907 return mState.mSelectionEnd > mState.mSelectionStart;
1908}
1909
1910void 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
1929void 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
1954void 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
1984bool TextEditor::CanUndo() const
1985{
1986 return !mReadOnly && mUndoIndex > 0;
1987}
1988
1989bool TextEditor::CanRedo() const
1990{
1991 return !mReadOnly && mUndoIndex < (int)mUndoBuffer.size();
1992}
1993
1994void TextEditor::Undo(int aSteps)
1995{
1996 while (CanUndo() && aSteps-- > 0)
1997 mUndoBuffer[--mUndoIndex].Undo(this);
1998}
1999
2000void TextEditor::Redo(int aSteps)
2001{
2002 while (CanRedo() && aSteps-- > 0)
2003 mUndoBuffer[mUndoIndex++].Redo(this);
2004}
2005
2006const 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
2034const 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
2062const 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
2091std::string TextEditor::GetText() const
2092{
2093 return GetText(Coordinates(), Coordinates((int)mLines.size(), 0));
2094}
2095
2096std::vector<std::string> TextEditor::GetTextLines() const
2097{
2098 std::vector<std::string> 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
2117std::string TextEditor::GetSelectedText() const
2118{
2119 return GetText(mState.mSelectionStart, mState.mSelectionEnd);
2120}
2121
2122std::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
2130void TextEditor::ProcessInputs()
2131{
2132}
2133
2134void 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
2144void 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
2249void 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<int>::max();
2386 mColorRangeMax = 0;
2387 }
2388 return;
2389 }
2390}
2391
2392float 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
2421void 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
2454int TextEditor::GetPageSize() const
2455{
2456 auto height = ImGui::GetWindowHeight() - 20.0f;
2457 return (int)floor(height / mCharAdvance.y);
2458}
2459
2460TextEditor::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
2482void 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
2502void 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
2521static 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
2550static 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
2577static 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
2596static 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
2700static 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
2738const 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
2807const 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<std::string, PaletteIndex>("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor));
2856 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
2857 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral));
2858 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
2859 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2860 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2861 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
2862 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
2863 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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
2879const 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<std::string, PaletteIndex>("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor));
2905 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
2906 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral));
2907 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
2908 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2909 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2910 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
2911 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
2912 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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
2928const 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
2994const 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<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
3036 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\\'[^\\\']*\\\'", PaletteIndex::String));
3037 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
3038 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
3039 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
3040 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
3041 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
3042 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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
3058const 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<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
3086 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::String));
3087 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
3088 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
3089 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
3090 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
3091 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
3092 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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
3108const 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<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
3141 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\\'[^\\\']*\\\'", PaletteIndex::String));
3142 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
3143 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
3144 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
3145 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
3146 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", 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}