1 #include <algorithm>
2
3 // [Bruno Levy] replaced chrono with
4 // geogram stopwatch for now (some
5 // of our older compilers do not support
6 // chrono yet).
7 // (TODO: check whether we still need that)
8 #include <geogram/basic/stopwatch.h>
9
10 #include <string>
11 #include <regex>
12 #include <cmath>
13 #include <iostream>
14
15 // [Bruno Levy] include path redirected to geogram.
16 #include <geogram_gfx/third_party/ImGuiColorTextEdit/TextEditor.h>
17 #include <geogram_gfx/third_party/ImGui/imgui_internal.h>
18
19 // [Bruno Levy] includes for GLFW, needed by new callbacks
20 // (for key constants).
21 #ifdef __ANDROID__
22 // TODO
23 #define GLFW_KEY_F2 0
24 #define GLFW_KEY_F5 0
25 #define GLFW_KEY_TAB 0
26 #else
27 #ifdef GEO_USE_SYSTEM_GLFW3
28 #include <GLFW/glfw3.h>
29 #else
30 #include <third_party/glfw/include/GLFW/glfw3.h>
31 #endif
32 #endif
33
34 static const int cTextStart = 7;
35
36 // TODO
37 // - multiline comments vs single-line: latter is blocking start of a ML
38 // - handle non-monospace fonts
39 // - handle unicode/utf
40 // - testing
41
42 template<class InputIt1, class InputIt2, class BinaryPredicate>
equals(InputIt1 first1,InputIt1 last1,InputIt2 first2,InputIt2 last2,BinaryPredicate p)43 bool equals(InputIt1 first1, InputIt1 last1,
44 InputIt2 first2, InputIt2 last2, BinaryPredicate p)
45 {
46 for (; first1 != last1 && first2 != last2; ++first1, ++first2)
47 {
48 if (!p(*first1, *first2))
49 return false;
50 }
51 return first1 == last1 && first2 == last2;
52 }
53
54
55 // [Bruno Levy] snprintf and vsnprintf for old MSVC compilers
56
57 #if defined(_MSC_VER) && _MSC_VER < 1900
58 # define snprintf c99_snprintf
59 # define vsnprintf c99_vsnprintf
60
61 namespace {
c99_vsnprintf(char * outBuf,size_t size,const char * format,va_list ap)62 __inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) {
63 int count = -1;
64
65 if (size != 0)
66 count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
67 if (count == -1)
68 count = _vscprintf(format, ap);
69 return count;
70 }
71
c99_snprintf(char * outBuf,size_t size,const char * format,...)72 __inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...) {
73 int count;
74 va_list ap;
75 va_start(ap, format);
76 count = c99_vsnprintf(outBuf, size, format, ap);
77 va_end(ap);
78 return count;
79 }
80 }
81 #endif
82
83
84 // [Bruno Levy] functions for management of HighDPI displays.
85 namespace {
86 #if defined(__EMSCRIPTEN__) || defined(__ANDROID__)
pixel_ratio()87 double pixel_ratio() {
88 return 1.0;
89 }
90 #else
91 /**
92 * \brief Computes the pixel ratio for hidpi devices.
93 * \details Uses the current GLFW window.
94 */
95 double pixel_ratio() {
96 int buf_size[2];
97 int win_size[2];
98 GLFWwindow* window = glfwGetCurrentContext();
99 glfwGetFramebufferSize(window, &buf_size[0], &buf_size[1]);
100 glfwGetWindowSize(window, &win_size[0], &win_size[1]);
101 // The window may be iconified.
102 if(win_size[0] == 0) {
103 return 1.0;
104 }
105 return double(buf_size[0]) / double(win_size[0]);
106 }
107 #endif
108
109 }
110
111
TextEditor()112 TextEditor::TextEditor()
113 : mLineSpacing(0.0f)
114 , mUndoIndex(0)
115 , mTabSize(4)
116 , mOverwrite(false)
117 , mReadOnly(false)
118 , mWithinRender(false)
119 , mScrollToCursor(false)
120 , mTextChanged(false)
121 , mColorRangeMin(0)
122 , mColorRangeMax(0)
123 , mSelectionMode(SelectionMode::Normal)
124 , mCheckMultilineComments(true)
125 {
126 SetPalette(GetDarkPalette());
127 SetLanguageDefinition(LanguageDefinition::HLSL());
128 mLines.push_back(Line());
129 // [Bruno Levy] additional callback.
130 callback_ = nullptr;
131 callback_client_data_ = nullptr;
132 }
133
134
~TextEditor()135 TextEditor::~TextEditor()
136 {
137 }
138
SetLanguageDefinition(const LanguageDefinition & aLanguageDef)139 void TextEditor::SetLanguageDefinition(const LanguageDefinition & aLanguageDef)
140 {
141 mLanguageDefinition = aLanguageDef;
142 mRegexList.clear();
143
144 for (auto& r : mLanguageDefinition.mTokenRegexStrings)
145 mRegexList.push_back(std::make_pair(std::regex(r.first, std::regex_constants::optimize), r.second));
146 }
147
SetPalette(const Palette & aValue)148 void TextEditor::SetPalette(const Palette & aValue)
149 {
150 mPalette = aValue;
151 }
152
AppendBuffer(std::string & aBuffer,char chr,int aIndex)153 int TextEditor::AppendBuffer(std::string& aBuffer, char chr, int aIndex)
154 {
155 if (chr != '\t')
156 {
157 aBuffer.push_back(chr);
158 return aIndex + 1;
159 }
160 else
161 {
162 auto num = mTabSize - aIndex % mTabSize;
163 for (int j = num; j > 0; --j)
164 aBuffer.push_back(' ');
165 return aIndex + num;
166 }
167 }
168
GetText(const Coordinates & aStart,const Coordinates & aEnd) const169 std::string TextEditor::GetText(const Coordinates & aStart, const Coordinates & aEnd) const
170 {
171 std::string result;
172
173 int prevLineNo = aStart.mLine;
174 for (auto it = aStart; it <= aEnd; Advance(it))
175 {
176 if (prevLineNo != it.mLine && it.mLine < (int) mLines.size())
177 result.push_back('\n');
178
179 if (it == aEnd)
180 break;
181
182 prevLineNo = it.mLine;
183 const auto& line = mLines[it.mLine];
184 if (!line.empty() && it.mColumn < (int)line.size())
185 result.push_back(line[it.mColumn].mChar);
186 }
187
188 return result;
189 }
190
GetLine(int l) const191 std::string TextEditor::GetLine(int l) const {
192 std::string result;
193 if(l >= int(mLines.size())) {
194 return result;
195 }
196 const Line& line = mLines[l];
197 for(int i=0; i<int(line.size()); ++i) {
198 result.push_back(line[i].mChar);
199 }
200 return result;
201 }
202
GetActualCursorCoordinates() const203 TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const
204 {
205 return SanitizeCoordinates(mState.mCursorPosition);
206 }
207
SanitizeCoordinates(const Coordinates & aValue) const208 TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates & aValue) const
209 {
210 // [Bruno Levy] fixed for coords outside text
211 auto line = std::max(0, std::min((int)mLines.size() - 1, aValue.mLine));
212 auto column = mLines.empty() ? 0 : std::min((int)mLines[line].size(), aValue.mColumn);
213 return Coordinates(line, column);
214 }
215
Advance(Coordinates & aCoordinates) const216 void TextEditor::Advance(Coordinates & aCoordinates) const
217 {
218 if (aCoordinates.mLine < (int)mLines.size())
219 {
220 auto& line = mLines[aCoordinates.mLine];
221
222 if (aCoordinates.mColumn + 1 < (int)line.size())
223 ++aCoordinates.mColumn;
224 else
225 {
226 ++aCoordinates.mLine;
227 aCoordinates.mColumn = 0;
228 }
229 }
230 }
231
DeleteRange(const Coordinates & aStart,const Coordinates & aEnd)232 void TextEditor::DeleteRange(const Coordinates & aStart, const Coordinates & aEnd)
233 {
234 assert(aEnd >= aStart);
235 assert(!mReadOnly);
236
237 if (aEnd == aStart)
238 return;
239
240 if (aStart.mLine == aEnd.mLine)
241 {
242 auto& line = mLines[aStart.mLine];
243 if (aEnd.mColumn >= (int)line.size())
244 line.erase(line.begin() + aStart.mColumn, line.end());
245 else
246 line.erase(line.begin() + aStart.mColumn, line.begin() + aEnd.mColumn);
247 }
248 else
249 {
250 auto& firstLine = mLines[aStart.mLine];
251 auto& lastLine = mLines[aEnd.mLine];
252
253 firstLine.erase(firstLine.begin() + aStart.mColumn, firstLine.end());
254 lastLine.erase(lastLine.begin(), lastLine.begin() + aEnd.mColumn);
255
256 if (aStart.mLine < aEnd.mLine)
257 firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end());
258
259 if (aStart.mLine < aEnd.mLine)
260 RemoveLine(aStart.mLine + 1, aEnd.mLine + 1);
261 }
262
263 mTextChanged = true;
264 }
265
InsertTextAt(Coordinates & aWhere,const char * aValue)266 int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char * aValue)
267 {
268 assert(!mReadOnly);
269
270 int totalLines = 0;
271 auto chr = *aValue;
272 while (chr != '\0')
273 {
274 if (mLines.empty())
275 mLines.push_back(Line());
276
277 if (chr == '\r')
278 {
279 // skip
280 }
281 else if (chr == '\n')
282 {
283 if (aWhere.mColumn < (int)mLines[aWhere.mLine].size())
284 {
285 auto& newLine = InsertLine(aWhere.mLine + 1);
286 auto& line = mLines[aWhere.mLine];
287 newLine.insert(newLine.begin(), line.begin() + aWhere.mColumn, line.end());
288 line.erase(line.begin() + aWhere.mColumn, line.end());
289 }
290 else
291 {
292 InsertLine(aWhere.mLine + 1);
293 }
294 ++aWhere.mLine;
295 aWhere.mColumn = 0;
296 ++totalLines;
297 }
298 else
299 {
300 auto& line = mLines[aWhere.mLine];
301 line.insert(line.begin() + aWhere.mColumn, Glyph(chr, PaletteIndex::Default));
302 ++aWhere.mColumn;
303 }
304 chr = *(++aValue);
305
306 mTextChanged = true;
307 }
308
309 return totalLines;
310 }
311
AddUndo(UndoRecord & aValue)312 void TextEditor::AddUndo(UndoRecord& aValue)
313 {
314 assert(!mReadOnly);
315
316 mUndoBuffer.resize(mUndoIndex + 1);
317 mUndoBuffer.back() = aValue;
318 ++mUndoIndex;
319 }
320
ScreenPosToCoordinates(const ImVec2 & aPosition) const321 TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition) const
322 {
323 ImVec2 origin = ImGui::GetCursorScreenPos();
324 ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y);
325
326 int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y));
327 int columnCoord = std::max(0, (int)floor(local.x / mCharAdvance.x) - cTextStart);
328
329 int column = 0;
330 if (lineNo >= 0 && lineNo < (int)mLines.size())
331 {
332 auto& line = mLines[lineNo];
333 auto distance = 0;
334 while (distance < columnCoord && column < (int)line.size())
335 {
336 if (line[column].mChar == '\t')
337 distance = (distance / mTabSize) * mTabSize + mTabSize;
338 else
339 ++distance;
340 ++column;
341 }
342 }
343 return Coordinates(lineNo, column);
344 }
345
FindWordStart(const Coordinates & aFrom) const346 TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates & aFrom) const
347 {
348 Coordinates at = aFrom;
349 if (at.mLine >= (int)mLines.size())
350 return at;
351
352 auto& line = mLines[at.mLine];
353
354 if (at.mColumn >= (int)line.size())
355 return at;
356
357 auto cstart = (PaletteIndex)line[at.mColumn].mColorIndex;
358 while (at.mColumn > 0)
359 {
360 if (cstart != (PaletteIndex)line[at.mColumn - 1].mColorIndex)
361 break;
362 --at.mColumn;
363 }
364 return at;
365 }
366
FindWordEnd(const Coordinates & aFrom) const367 TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates & aFrom) const
368 {
369 Coordinates at = aFrom;
370 if (at.mLine >= (int)mLines.size())
371 return at;
372
373 auto& line = mLines[at.mLine];
374
375 if (at.mColumn >= (int)line.size())
376 return at;
377
378 auto cstart = (PaletteIndex)line[at.mColumn].mColorIndex;
379 while (at.mColumn < (int)line.size())
380 {
381 if (cstart != (PaletteIndex)line[at.mColumn].mColorIndex)
382 break;
383 ++at.mColumn;
384 }
385 return at;
386 }
387
IsOnWordBoundary(const Coordinates & aAt) const388 bool TextEditor::IsOnWordBoundary(const Coordinates & aAt) const
389 {
390 if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0)
391 return true;
392
393 auto& line = mLines[aAt.mLine];
394 if (aAt.mColumn >= (int)line.size())
395 return true;
396
397 return line[aAt.mColumn].mColorIndex != line[aAt.mColumn - 1].mColorIndex;
398 }
399
RemoveLine(int aStart,int aEnd)400 void TextEditor::RemoveLine(int aStart, int aEnd)
401 {
402 assert(!mReadOnly);
403
404 ErrorMarkers etmp;
405 for (auto& i : mErrorMarkers)
406 {
407 ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second);
408 if (e.first >= aStart && e.first <= aEnd)
409 continue;
410 etmp.insert(e);
411 }
412 mErrorMarkers = std::move(etmp);
413
414 Breakpoints btmp;
415 for (auto i : mBreakpoints)
416 {
417 if (i >= aStart && i <= aEnd)
418 continue;
419 btmp.insert(i >= aStart ? i - 1 : i);
420 }
421 mBreakpoints = std::move(btmp);
422
423 mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd);
424
425 mTextChanged = true;
426 }
427
RemoveLine(int aIndex)428 void TextEditor::RemoveLine(int aIndex)
429 {
430 assert(!mReadOnly);
431
432 ErrorMarkers etmp;
433 for (auto& i : mErrorMarkers)
434 {
435 ErrorMarkers::value_type e(i.first >= aIndex ? i.first - 1 : i.first, i.second);
436 if (e.first == aIndex)
437 continue;
438 etmp.insert(e);
439 }
440 mErrorMarkers = std::move(etmp);
441
442 Breakpoints btmp;
443 for (auto i : mBreakpoints)
444 {
445 if (i == aIndex)
446 continue;
447 btmp.insert(i >= aIndex ? i - 1 : i);
448 }
449 mBreakpoints = std::move(btmp);
450
451 mLines.erase(mLines.begin() + aIndex);
452
453 mTextChanged = true;
454 }
455
InsertLine(int aIndex)456 TextEditor::Line& TextEditor::InsertLine(int aIndex)
457 {
458 assert(!mReadOnly);
459
460 auto& result = *mLines.insert(mLines.begin() + aIndex, Line());
461
462 ErrorMarkers etmp;
463 for (auto& i : mErrorMarkers)
464 etmp.insert(ErrorMarkers::value_type(i.first >= aIndex ? i.first + 1 : i.first, i.second));
465 mErrorMarkers = std::move(etmp);
466
467 Breakpoints btmp;
468 for (auto i : mBreakpoints)
469 btmp.insert(i >= aIndex ? i + 1 : i);
470 mBreakpoints = std::move(btmp);
471
472 return result;
473 }
474
GetWordUnderCursor() const475 std::string TextEditor::GetWordUnderCursor() const
476 {
477 auto c = GetCursorPosition();
478 return GetWordAt(c);
479 }
480
GetWordAt(const Coordinates & aCoords) const481 std::string TextEditor::GetWordAt(const Coordinates & aCoords) const
482 {
483 auto start = FindWordStart(aCoords);
484 auto end = FindWordEnd(aCoords);
485
486 std::string r;
487
488 for (auto it = start; it < end; Advance(it))
489 r.push_back(mLines[it.mLine][it.mColumn].mChar);
490
491 return r;
492 }
493
Render(const char * aTitle,const ImVec2 & aSize,bool aBorder)494 void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder)
495 {
496 mWithinRender = true;
497 mTextChanged = false;
498
499 ImGuiIO& io = ImGui::GetIO();
500
501 // [Bruno Levy] read font size from current font (instead of default font)
502 ImGuiContext& g = *GImGui;
503 auto xadv = (g.Font->IndexAdvanceX['X']);
504
505 // [Bruno Levy] apply highdpi scaling
506 float s = 1.0f / float(pixel_ratio());
507 mCharAdvance = ImVec2(s*xadv, s*(g.Font->FontSize + mLineSpacing)); // TODO: apply pixel scaling for HiDPI displays.
508
509 //[Bruno Levy] commented-out (I prefer to use default style)
510 // ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background]));
511 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
512 //[Bruno Levy] added 'NoNav' flag
513 ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
514
515 ImGui::PushAllowKeyboardFocus(true);
516
517 auto shift = io.KeyShift;
518 auto ctrl = io.KeyCtrl;
519 auto alt = io.KeyAlt;
520
521 if (ImGui::IsWindowFocused())
522 {
523 if (ImGui::IsWindowHovered()) {
524 ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput);
525 //ImGui::CaptureKeyboardFromApp(true); // [Bruno Levy] seems to be needed (to be checked)
526 ImGui::CaptureMouseFromApp(true);
527 }
528
529 // [Bruno Levy] IsKeyPressed() supposes QWERTY !
530 if (!IsReadOnly() && (ImGui::IsKeyPressed('Z') || ImGui::IsKeyPressed('W')))
531 if (ctrl && !shift && !alt)
532 Undo();
533 if (!IsReadOnly() && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace)))
534 if (!ctrl && !shift && alt)
535 Undo();
536 if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed('Y'))
537 Redo();
538
539 if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)))
540 MoveUp(1, shift);
541 else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)))
542 MoveDown(1, shift);
543 else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)))
544 MoveLeft(1, shift, ctrl);
545 else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)))
546 MoveRight(1, shift, ctrl);
547 else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp)))
548 MoveUp(GetPageSize() - 4, shift);
549 else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown)))
550 MoveDown(GetPageSize() - 4, shift);
551 else if (!alt && ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
552 MoveTop(shift);
553 else if (ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
554 MoveBottom(shift);
555 else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
556 MoveHome(shift);
557 else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
558 MoveEnd(shift);
559 else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)))
560 Delete();
561 else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace)))
562 BackSpace();
563 /* // [Bruno Levy] commented out, because it is switched on when I do not expect it
564 else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(45))
565 mOverwrite ^= true;
566 */
567 else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(45))
568 Copy();
569 else if (ctrl && !shift && !alt && ImGui::IsKeyPressed('C'))
570 Copy();
571 else if (!IsReadOnly() && !ctrl && shift && !alt && ImGui::IsKeyPressed(45))
572 Paste();
573 else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed('V'))
574 Paste();
575 else if (ctrl && !shift && !alt && ImGui::IsKeyPressed('X'))
576 Cut();
577 else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)))
578 Cut();
579 else if(ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter))) { // [Bruno Levy] Seems that this was missing.
580 EnterCharacter('\n');
581 } else if(ctrl && !shift && !alt && (ImGui::IsKeyPressed('A') || ImGui::IsKeyPressed('Q'))) {
582 // [Bruno Levy] select all
583 SetSelection(Coordinates(0,0), Coordinates(GetTotalLines(),0), SelectionMode::Normal);
584 SetCursorPosition(Coordinates(GetTotalLines(),0));
585 }
586
587 // [Bruno Levy] additional callback
588 if(callback_ != nullptr){
589 if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(GLFW_KEY_F2)) {
590 callback_(TEXT_EDITOR_SAVE, callback_client_data_);
591 }
592 if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(GLFW_KEY_F5)) {
593 callback_(TEXT_EDITOR_RUN, callback_client_data_);
594 }
595 if (ctrl && !shift && !alt && ImGui::IsKeyPressed('F')) {
596 callback_(TEXT_EDITOR_FIND, callback_client_data_);
597 }
598 if (ctrl && !shift && !alt && ImGui::IsKeyPressed('C') && !HasSelection()) {
599 callback_(TEXT_EDITOR_STOP, callback_client_data_);
600 }
601 if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(GLFW_KEY_TAB)) {
602 callback_(TEXT_EDITOR_COMPLETION, callback_client_data_);
603 }
604 }
605
606 // [Bruno Levy] ported to ImGui 1.69
607 if(!IsReadOnly()) {
608 for(int i=0; i<io.InputQueueCharacters.size(); ++i) {
609 char c = char(io.InputQueueCharacters[i]);
610 if(c != '\0') {
611 if (isprint(c) || isspace(c)) {
612 if(c == '\r') {
613 c = '\n';
614 }
615 EnterCharacter(c);
616 }
617 }
618 }
619 }
620
621
622 }
623
624 if (ImGui::IsWindowHovered())
625 {
626 static float lastClick = -1.0f;
627 if (!shift && !alt)
628 {
629 auto click = ImGui::IsMouseClicked(0);
630 auto doubleClick = ImGui::IsMouseDoubleClicked(0);
631 auto t = ImGui::GetTime();
632 auto tripleClick = click && !doubleClick && t - lastClick < io.MouseDoubleClickTime;
633 if (tripleClick)
634 {
635 if (!ctrl)
636 {
637 mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = SanitizeCoordinates(ScreenPosToCoordinates(ImGui::GetMousePos()));
638 mSelectionMode = SelectionMode::Line;
639 SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
640 }
641
642 lastClick = -1.0f;
643 }
644 else if (doubleClick)
645 {
646 if (!ctrl)
647 {
648 mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = SanitizeCoordinates(ScreenPosToCoordinates(ImGui::GetMousePos()));
649 if (mSelectionMode == SelectionMode::Line)
650 mSelectionMode = SelectionMode::Normal;
651 else
652 mSelectionMode = SelectionMode::Word;
653 SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
654 }
655
656 lastClick = float(ImGui::GetTime());
657 }
658 else if (click)
659 {
660 mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = SanitizeCoordinates(ScreenPosToCoordinates(ImGui::GetMousePos()));
661 if (ctrl)
662 mSelectionMode = SelectionMode::Word;
663 else
664 mSelectionMode = SelectionMode::Normal;
665 SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
666
667 lastClick = float(ImGui::GetTime());
668 }
669 else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0))
670 {
671 io.WantCaptureMouse = true;
672 mState.mCursorPosition = mInteractiveEnd = SanitizeCoordinates(ScreenPosToCoordinates(ImGui::GetMousePos()));
673 SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
674 }
675 else
676 {
677 }
678 }
679
680 //if (!ImGui::IsMouseDown(0))
681 // mWordSelectionMode = false;
682 }
683
684 ColorizeInternal();
685
686 static std::string buffer;
687 auto contentSize = ImGui::GetWindowContentRegionMax();
688 auto drawList = ImGui::GetWindowDrawList();
689 int appendIndex = 0;
690 int longest = cTextStart;
691
692 ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos();
693 auto scrollX = ImGui::GetScrollX();
694 auto scrollY = ImGui::GetScrollY();
695
696 auto lineNo = (int)floor(scrollY / mCharAdvance.y);
697 auto lineMax = std::max(0, std::min((int)mLines.size() - 1, lineNo + (int)floor((scrollY + contentSize.y) / mCharAdvance.y)));
698 if (!mLines.empty())
699 {
700 while (lineNo <= lineMax)
701 {
702 ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y);
703 ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mCharAdvance.x * cTextStart, lineStartScreenPos.y);
704
705 auto& line = mLines[lineNo];
706 longest = std::max(cTextStart + TextDistanceToLineStart(Coordinates(lineNo, (int) line.size())), longest);
707 auto columnNo = 0;
708 Coordinates lineStartCoord(lineNo, 0);
709 Coordinates lineEndCoord(lineNo, (int)line.size());
710
711 int sstart = -1;
712 int ssend = -1;
713
714 assert(mState.mSelectionStart <= mState.mSelectionEnd);
715 if (mState.mSelectionStart <= lineEndCoord)
716 sstart = mState.mSelectionStart > lineStartCoord ? TextDistanceToLineStart(mState.mSelectionStart) : 0;
717 if (mState.mSelectionEnd > lineStartCoord)
718 ssend = TextDistanceToLineStart(mState.mSelectionEnd < lineEndCoord ? mState.mSelectionEnd : lineEndCoord);
719
720 if (mState.mSelectionEnd.mLine > lineNo)
721 ++ssend;
722
723 if (sstart != -1 && ssend != -1 && sstart < ssend)
724 {
725 ImVec2 vstart(lineStartScreenPos.x + (mCharAdvance.x) * (sstart + cTextStart), lineStartScreenPos.y);
726 ImVec2 vend(lineStartScreenPos.x + (mCharAdvance.x) * (ssend + cTextStart), lineStartScreenPos.y + mCharAdvance.y);
727 drawList->AddRectFilled(vstart, vend, mPalette[(int)PaletteIndex::Selection]);
728 }
729
730 static char buf[16];
731 auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y);
732
733 if (mBreakpoints.find(lineNo + 1) != mBreakpoints.end())
734 {
735 auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y);
736 drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::Breakpoint]);
737 }
738
739 auto errorIt = mErrorMarkers.find(lineNo + 1);
740 if (errorIt != mErrorMarkers.end())
741 {
742 auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y);
743 drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::ErrorMarker]);
744
745 if (ImGui::IsMouseHoveringRect(lineStartScreenPos, end))
746 {
747 // [Bruno Levy] changed font and color for error messages.
748 ImGui::BeginTooltip();
749 ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]);
750 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f));
751 ImGui::Text("Error at line %d:", errorIt->first);
752 ImGui::Separator();
753 ImGui::Text("%s", errorIt->second.c_str());
754 ImGui::PopStyleColor();
755 ImGui::PopFont();
756 ImGui::EndTooltip();
757 }
758 }
759
760 auto chars = snprintf(buf, 16, "%6d", lineNo + 1);
761 (void)chars; // [Bruno] silence warning
762 assert(chars >= 0 && chars < 16);
763 drawList->AddText(ImVec2(lineStartScreenPos.x /*+ mCharAdvance.x * 1*/, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], buf);
764
765 if (mState.mCursorPosition.mLine == lineNo)
766 {
767 auto focused = ImGui::IsWindowFocused();
768
769 if (!HasSelection())
770 {
771 auto end = ImVec2(start.x + contentSize.x + scrollX, start.y + mCharAdvance.y);
772 drawList->AddRectFilled(start, end, mPalette[(int)(focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]);
773 drawList->AddRect(start, end, mPalette[(int)PaletteIndex::CurrentLineEdge], 1.0f);
774 }
775
776 int cx = TextDistanceToLineStart(mState.mCursorPosition);
777
778 if (focused)
779 {
780 // [Bruno Levy] replaced chrono with
781 // geogram stopwatch for now (some
782 // of our older compilers do not support
783 // chrono yet).
784
785 static double timeStart = GEO::SystemStopwatch::now();
786 double timeEnd = GEO::SystemStopwatch::now();
787 int elapsed = int((timeEnd - timeStart)*1000);
788
789 if (elapsed > 400)
790 {
791 ImVec2 cstart(lineStartScreenPos.x + mCharAdvance.x * (cx + cTextStart), lineStartScreenPos.y);
792 ImVec2 cend(lineStartScreenPos.x + mCharAdvance.x * (cx + cTextStart) + (mOverwrite ? mCharAdvance.x : 1.0f), lineStartScreenPos.y + mCharAdvance.y);
793 drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]);
794 if (elapsed > 800)
795 timeStart = timeEnd;
796 }
797 }
798 }
799
800 appendIndex = 0;
801 auto prevColor = line.empty() ? PaletteIndex::Default : (line[0].mMultiLineComment ? PaletteIndex::MultiLineComment : line[0].mColorIndex);
802
803 for (auto& glyph : line)
804 {
805 auto color = glyph.mMultiLineComment ? PaletteIndex::MultiLineComment : glyph.mColorIndex;
806
807 if (color != prevColor && !buffer.empty())
808 {
809 drawList->AddText(textScreenPos, mPalette[(uint8_t)prevColor], buffer.c_str());
810 textScreenPos.x += mCharAdvance.x * buffer.length();
811 buffer.clear();
812 prevColor = color;
813 }
814 appendIndex = AppendBuffer(buffer, glyph.mChar, appendIndex);
815 ++columnNo;
816 }
817
818 if (!buffer.empty())
819 {
820 drawList->AddText(textScreenPos, mPalette[(uint8_t)prevColor], buffer.c_str());
821 buffer.clear();
822 }
823 appendIndex = 0;
824 lineStartScreenPos.y += mCharAdvance.y;
825 textScreenPos.x = lineStartScreenPos.x + mCharAdvance.x * cTextStart;
826 textScreenPos.y = lineStartScreenPos.y;
827 ++lineNo;
828 }
829
830 // [Bruno Levy] My tooltips (work in progress)
831 if(ImGui::IsWindowHovered() && callback_ != nullptr) {
832 ImVec2 mouse = ImGui::GetMousePos();
833 ImVec2 origin = ImGui::GetCursorScreenPos();
834 if(mouse.y > origin.y) {
835 Coordinates coord = ScreenPosToCoordinates(ImGui::GetMousePos());
836 // Display tooltip only if there is not already an error tooltip
837 // to display.
838 if(mErrorMarkers.find(coord.mLine + 1) == mErrorMarkers.end()) {
839 word_context_ = GetWordContextAt(coord);
840 if(!word_context_.empty() && word_context_ != " ") {
841 callback_(TEXT_EDITOR_TOOLTIP, callback_client_data_);
842 }
843 }
844 }
845 }
846
847 /* // [Bruno Levy] Commented-out (just get 'built-in function', not very interesting).
848 // TODO: try getting meta-information from GOM and displaying it in tooltips.
849 auto id = GetWordAt(ScreenPosToCoordinates(ImGui::GetMousePos()));
850 if (!id.empty())
851 {
852 auto it = mLanguageDefinition.mIdentifiers.find(id);
853 if (it != mLanguageDefinition.mIdentifiers.end())
854 {
855 ImGui::BeginTooltip();
856 ImGui::TextUnformatted(it->second.mDeclaration.c_str());
857 ImGui::EndTooltip();
858 }
859 else
860 {
861 auto pi = mLanguageDefinition.mPreprocIdentifiers.find(id);
862 if (pi != mLanguageDefinition.mPreprocIdentifiers.end())
863 {
864 ImGui::BeginTooltip();
865 ImGui::TextUnformatted(pi->second.mDeclaration.c_str());
866 ImGui::EndTooltip();
867 }
868 }
869 }
870 */
871 }
872
873
874 ImGui::Dummy(ImVec2((longest + 2) * mCharAdvance.x, mLines.size() * mCharAdvance.y));
875
876 if (mScrollToCursor)
877 {
878 EnsureCursorVisible();
879 // ImGui::SetWindowFocus(); // [Bruno Levy] commented-out because this breaks my <control><S>
880 mScrollToCursor = false;
881 }
882
883 ImGui::PopAllowKeyboardFocus();
884 ImGui::EndChild();
885 ImGui::PopStyleVar();
886 // [Bruno Levy] Commented out because I'd rather use the default style.
887 // ImGui::PopStyleColor();
888
889 mWithinRender = false;
890 }
891
SetText(const std::string & aText)892 void TextEditor::SetText(const std::string & aText)
893 {
894 mLines.clear();
895 for (auto chr : aText)
896 {
897 if (mLines.empty())
898 mLines.push_back(Line());
899 if (chr == '\n')
900 mLines.push_back(Line());
901 else
902 {
903 mLines.back().push_back(Glyph(chr, PaletteIndex::Default));
904 }
905
906 mTextChanged = true;
907 }
908
909 mUndoBuffer.clear();
910
911 Colorize();
912
913 // [Bruno Levy] additional callback
914 if(callback_ != nullptr) {
915 callback_(TEXT_EDITOR_TEXT_CHANGED, callback_client_data_);
916 }
917 }
918
EnterCharacter(Char aChar)919 void TextEditor::EnterCharacter(Char aChar)
920 {
921 assert(!mReadOnly);
922
923 // [Bruno Levy] clear error markers whenever text is entered.
924 mErrorMarkers.clear();
925
926 UndoRecord u;
927
928 u.mBefore = mState;
929
930 if (HasSelection())
931 {
932 u.mRemoved = GetSelectedText();
933 u.mRemovedStart = mState.mSelectionStart;
934 u.mRemovedEnd = mState.mSelectionEnd;
935 DeleteSelection();
936 }
937
938 auto coord = GetActualCursorCoordinates();
939 u.mAddedStart = coord;
940
941 if (mLines.empty())
942 mLines.push_back(Line());
943
944 if (aChar == '\n')
945 {
946 InsertLine(coord.mLine + 1);
947 auto& line = mLines[coord.mLine];
948 auto& newLine = mLines[coord.mLine + 1];
949 newLine.insert(newLine.begin(), line.begin() + coord.mColumn, line.end());
950 line.erase(line.begin() + coord.mColumn, line.begin() + line.size());
951 mState.mCursorPosition = Coordinates(coord.mLine + 1, 0);
952 }
953 else
954 {
955 auto& line = mLines[coord.mLine];
956 if (mOverwrite && (int)line.size() > coord.mColumn)
957 line[coord.mColumn] = Glyph(aChar, PaletteIndex::Default);
958 else
959 line.insert(line.begin() + coord.mColumn, Glyph(aChar, PaletteIndex::Default));
960 mState.mCursorPosition = coord;
961 ++mState.mCursorPosition.mColumn;
962 }
963
964 mTextChanged = true;
965
966 u.mAdded = aChar;
967 u.mAddedEnd = GetActualCursorCoordinates();
968 u.mAfter = mState;
969
970 AddUndo(u);
971
972 Colorize(coord.mLine - 1, 3);
973 EnsureCursorVisible();
974
975 // [Bruno Levy] additional callback
976 if(callback_ != nullptr) {
977 callback_(TEXT_EDITOR_TEXT_CHANGED, callback_client_data_);
978 }
979 }
980
SetReadOnly(bool aValue)981 void TextEditor::SetReadOnly(bool aValue)
982 {
983 mReadOnly = aValue;
984 }
985
SetCursorPosition(const Coordinates & aPosition)986 void TextEditor::SetCursorPosition(const Coordinates & aPosition)
987 {
988 if (mState.mCursorPosition != aPosition)
989 {
990 mState.mCursorPosition = aPosition;
991 EnsureCursorVisible();
992 }
993 }
994
SetSelectionStart(const Coordinates & aPosition)995 void TextEditor::SetSelectionStart(const Coordinates & aPosition)
996 {
997 mState.mSelectionStart = SanitizeCoordinates(aPosition);
998 if (mState.mSelectionStart > mState.mSelectionEnd)
999 std::swap(mState.mSelectionStart, mState.mSelectionEnd);
1000 }
1001
SetSelectionEnd(const Coordinates & aPosition)1002 void TextEditor::SetSelectionEnd(const Coordinates & aPosition)
1003 {
1004 mState.mSelectionEnd = SanitizeCoordinates(aPosition);
1005 if (mState.mSelectionStart > mState.mSelectionEnd)
1006 std::swap(mState.mSelectionStart, mState.mSelectionEnd);
1007 }
1008
SetSelection(const Coordinates & aStart,const Coordinates & aEnd,SelectionMode aMode)1009 void TextEditor::SetSelection(const Coordinates & aStart, const Coordinates & aEnd, SelectionMode aMode)
1010 {
1011 mState.mSelectionStart = SanitizeCoordinates(aStart);
1012 mState.mSelectionEnd = SanitizeCoordinates(aEnd);
1013 if (aStart > aEnd)
1014 std::swap(mState.mSelectionStart, mState.mSelectionEnd);
1015
1016 switch (aMode)
1017 {
1018 case TextEditor::SelectionMode::Normal:
1019 break;
1020 case TextEditor::SelectionMode::Word:
1021 {
1022 mState.mSelectionStart = FindWordStart(mState.mSelectionStart);
1023 if (!IsOnWordBoundary(mState.mSelectionEnd))
1024 mState.mSelectionEnd = FindWordEnd(FindWordStart(mState.mSelectionEnd));
1025 break;
1026 }
1027 case TextEditor::SelectionMode::Line:
1028 {
1029 const auto lineNo = mState.mSelectionEnd.mLine;
1030 const auto lineSize = lineNo < int(mLines.size()) ? mLines[lineNo].size() : 0;
1031 mState.mSelectionStart = Coordinates(mState.mSelectionStart.mLine, 0);
1032 mState.mSelectionEnd = Coordinates(lineNo, (int) lineSize);
1033 break;
1034 }
1035 default:
1036 break;
1037 }
1038 }
1039
InsertText(const std::string & aValue)1040 void TextEditor::InsertText(const std::string & aValue)
1041 {
1042 InsertText(aValue.c_str());
1043 }
1044
InsertText(const char * aValue)1045 void TextEditor::InsertText(const char * aValue)
1046 {
1047 if (aValue == nullptr)
1048 return;
1049
1050 auto pos = GetActualCursorCoordinates();
1051 auto start = std::min(pos, mState.mSelectionStart);
1052 int totalLines = pos.mLine - start.mLine;
1053
1054 totalLines += InsertTextAt(pos, aValue);
1055
1056 SetSelection(pos, pos);
1057 SetCursorPosition(pos);
1058 Colorize(start.mLine - 1, totalLines + 2);
1059 }
1060
DeleteSelection()1061 void TextEditor::DeleteSelection()
1062 {
1063 assert(mState.mSelectionEnd >= mState.mSelectionStart);
1064
1065 if (mState.mSelectionEnd == mState.mSelectionStart)
1066 return;
1067
1068 DeleteRange(mState.mSelectionStart, mState.mSelectionEnd);
1069
1070 SetSelection(mState.mSelectionStart, mState.mSelectionStart);
1071 SetCursorPosition(mState.mSelectionStart);
1072 Colorize(mState.mSelectionStart.mLine, 1);
1073
1074 // [Bruno Levy] additional callback
1075 if(callback_ != nullptr) {
1076 callback_(TEXT_EDITOR_TEXT_CHANGED, callback_client_data_);
1077 }
1078 }
1079
MoveUp(int aAmount,bool aSelect)1080 void TextEditor::MoveUp(int aAmount, bool aSelect)
1081 {
1082 auto oldPos = mState.mCursorPosition;
1083 mState.mCursorPosition.mLine = std::max(0, mState.mCursorPosition.mLine - aAmount);
1084 if (oldPos != mState.mCursorPosition)
1085 {
1086 if (aSelect)
1087 {
1088 if (oldPos == mInteractiveStart)
1089 mInteractiveStart = mState.mCursorPosition;
1090 else if (oldPos == mInteractiveEnd)
1091 mInteractiveEnd = mState.mCursorPosition;
1092 else
1093 {
1094 mInteractiveStart = mState.mCursorPosition;
1095 mInteractiveEnd = oldPos;
1096 }
1097 }
1098 else
1099 mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
1100 SetSelection(mInteractiveStart, mInteractiveEnd);
1101
1102 EnsureCursorVisible();
1103 }
1104 }
1105
MoveDown(int aAmount,bool aSelect)1106 void TextEditor::MoveDown(int aAmount, bool aSelect)
1107 {
1108 assert(mState.mCursorPosition.mColumn >= 0);
1109 auto oldPos = mState.mCursorPosition;
1110 mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + aAmount));
1111
1112 if (mState.mCursorPosition != oldPos)
1113 {
1114 if (aSelect)
1115 {
1116 if (oldPos == mInteractiveEnd)
1117 mInteractiveEnd = mState.mCursorPosition;
1118 else if (oldPos == mInteractiveStart)
1119 mInteractiveStart = mState.mCursorPosition;
1120 else
1121 {
1122 mInteractiveStart = oldPos;
1123 mInteractiveEnd = mState.mCursorPosition;
1124 }
1125 }
1126 else
1127 mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
1128 SetSelection(mInteractiveStart, mInteractiveEnd);
1129
1130 EnsureCursorVisible();
1131 }
1132 }
1133
MoveLeft(int aAmount,bool aSelect,bool aWordMode)1134 void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode)
1135 {
1136 if (mLines.empty())
1137 return;
1138
1139 auto oldPos = mState.mCursorPosition;
1140 mState.mCursorPosition = GetActualCursorCoordinates();
1141
1142 while (aAmount-- > 0)
1143 {
1144 if (mState.mCursorPosition.mColumn == 0)
1145 {
1146 if (mState.mCursorPosition.mLine > 0)
1147 {
1148 --mState.mCursorPosition.mLine;
1149 mState.mCursorPosition.mColumn = (int)mLines[mState.mCursorPosition.mLine].size();
1150 }
1151 }
1152 else
1153 {
1154 mState.mCursorPosition.mColumn = std::max(0, mState.mCursorPosition.mColumn - 1);
1155 if (aWordMode)
1156 mState.mCursorPosition = FindWordStart(mState.mCursorPosition);
1157 }
1158 }
1159
1160 assert(mState.mCursorPosition.mColumn >= 0);
1161 if (aSelect)
1162 {
1163 if (oldPos == mInteractiveStart)
1164 mInteractiveStart = mState.mCursorPosition;
1165 else if (oldPos == mInteractiveEnd)
1166 mInteractiveEnd = mState.mCursorPosition;
1167 else
1168 {
1169 mInteractiveStart = mState.mCursorPosition;
1170 mInteractiveEnd = oldPos;
1171 }
1172 }
1173 else
1174 mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
1175 SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal);
1176
1177 EnsureCursorVisible();
1178 }
1179
MoveRight(int aAmount,bool aSelect,bool aWordMode)1180 void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode)
1181 {
1182 auto oldPos = mState.mCursorPosition;
1183
1184 if (mLines.empty())
1185 return;
1186
1187 while (aAmount-- > 0)
1188 {
1189 auto& line = mLines[mState.mCursorPosition.mLine];
1190 if (mState.mCursorPosition.mColumn >= (int)line.size())
1191 {
1192 if (mState.mCursorPosition.mLine < (int)mLines.size() - 1)
1193 {
1194 mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + 1));
1195 mState.mCursorPosition.mColumn = 0;
1196 }
1197 }
1198 else
1199 {
1200 mState.mCursorPosition.mColumn = std::max(0, std::min((int)line.size(), mState.mCursorPosition.mColumn + 1));
1201 if (aWordMode)
1202 mState.mCursorPosition = FindWordEnd(mState.mCursorPosition);
1203 }
1204 }
1205
1206 if (aSelect)
1207 {
1208 if (oldPos == mInteractiveEnd)
1209 mInteractiveEnd = SanitizeCoordinates(mState.mCursorPosition);
1210 else if (oldPos == mInteractiveStart)
1211 mInteractiveStart = mState.mCursorPosition;
1212 else
1213 {
1214 mInteractiveStart = oldPos;
1215 mInteractiveEnd = mState.mCursorPosition;
1216 }
1217 }
1218 else
1219 mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
1220 SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal);
1221
1222 EnsureCursorVisible();
1223 }
1224
MoveTop(bool aSelect)1225 void TextEditor::MoveTop(bool aSelect)
1226 {
1227 auto oldPos = mState.mCursorPosition;
1228 SetCursorPosition(Coordinates(0, 0));
1229
1230 if (mState.mCursorPosition != oldPos)
1231 {
1232 if (aSelect)
1233 {
1234 mInteractiveEnd = oldPos;
1235 mInteractiveStart = mState.mCursorPosition;
1236 }
1237 else
1238 mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
1239 SetSelection(mInteractiveStart, mInteractiveEnd);
1240 }
1241 }
1242
MoveBottom(bool aSelect)1243 void TextEditor::MoveBottom(bool aSelect)
1244 {
1245 auto oldPos = GetCursorPosition();
1246 auto newPos = Coordinates((int)mLines.size() - 1, 0);
1247 SetCursorPosition(newPos);
1248 if (aSelect)
1249 {
1250 mInteractiveStart = oldPos;
1251 mInteractiveEnd = newPos;
1252 }
1253 else
1254 mInteractiveStart = mInteractiveEnd = newPos;
1255 SetSelection(mInteractiveStart, mInteractiveEnd);
1256 }
1257
MoveHome(bool aSelect)1258 void TextEditor::MoveHome(bool aSelect)
1259 {
1260 auto oldPos = mState.mCursorPosition;
1261 SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, 0));
1262
1263 if (mState.mCursorPosition != oldPos)
1264 {
1265 if (aSelect)
1266 {
1267 if (oldPos == mInteractiveStart)
1268 mInteractiveStart = mState.mCursorPosition;
1269 else if (oldPos == mInteractiveEnd)
1270 mInteractiveEnd = mState.mCursorPosition;
1271 else
1272 {
1273 mInteractiveStart = mState.mCursorPosition;
1274 mInteractiveEnd = oldPos;
1275 }
1276 }
1277 else
1278 mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
1279 SetSelection(mInteractiveStart, mInteractiveEnd);
1280 }
1281 }
1282
MoveEnd(bool aSelect)1283 void TextEditor::MoveEnd(bool aSelect)
1284 {
1285 auto oldPos = mState.mCursorPosition;
1286 SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, (int)mLines[oldPos.mLine].size()));
1287
1288 if (mState.mCursorPosition != oldPos)
1289 {
1290 if (aSelect)
1291 {
1292 if (oldPos == mInteractiveEnd)
1293 mInteractiveEnd = mState.mCursorPosition;
1294 else if (oldPos == mInteractiveStart)
1295 mInteractiveStart = mState.mCursorPosition;
1296 else
1297 {
1298 mInteractiveStart = oldPos;
1299 mInteractiveEnd = mState.mCursorPosition;
1300 }
1301 }
1302 else
1303 mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
1304 SetSelection(mInteractiveStart, mInteractiveEnd);
1305 }
1306 }
1307
Delete()1308 void TextEditor::Delete()
1309 {
1310 assert(!mReadOnly);
1311
1312 // [Bruno Levy] clear error markers whenever text is entered.
1313 mErrorMarkers.clear();
1314
1315 if (mLines.empty())
1316 return;
1317
1318 UndoRecord u;
1319 u.mBefore = mState;
1320
1321 if (HasSelection())
1322 {
1323 u.mRemoved = GetSelectedText();
1324 u.mRemovedStart = mState.mSelectionStart;
1325 u.mRemovedEnd = mState.mSelectionEnd;
1326
1327 DeleteSelection();
1328 }
1329 else
1330 {
1331 auto pos = GetActualCursorCoordinates();
1332 SetCursorPosition(pos);
1333 auto& line = mLines[pos.mLine];
1334
1335 if (pos.mColumn == (int)line.size())
1336 {
1337 if (pos.mLine == (int)mLines.size() - 1)
1338 return;
1339
1340 u.mRemoved = '\n';
1341 u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
1342 Advance(u.mRemovedEnd);
1343
1344 auto& nextLine = mLines[pos.mLine + 1];
1345 line.insert(line.end(), nextLine.begin(), nextLine.end());
1346 RemoveLine(pos.mLine + 1);
1347 }
1348 else
1349 {
1350 u.mRemoved = line[pos.mColumn].mChar;
1351 u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
1352 u.mRemovedEnd.mColumn++;
1353
1354 line.erase(line.begin() + pos.mColumn);
1355 }
1356
1357 mTextChanged = true;
1358
1359 Colorize(pos.mLine, 1);
1360 }
1361
1362 u.mAfter = mState;
1363 AddUndo(u);
1364 }
1365
BackSpace()1366 void TextEditor::BackSpace()
1367 {
1368 assert(!mReadOnly);
1369
1370 // [Bruno Levy] clear error markers whenever text is entered.
1371 mErrorMarkers.clear();
1372
1373 if (mLines.empty())
1374 return;
1375
1376
1377 UndoRecord u;
1378 u.mBefore = mState;
1379
1380 if (HasSelection())
1381 {
1382 u.mRemoved = GetSelectedText();
1383 u.mRemovedStart = mState.mSelectionStart;
1384 u.mRemovedEnd = mState.mSelectionEnd;
1385
1386 DeleteSelection();
1387 }
1388 else
1389 {
1390 auto pos = GetActualCursorCoordinates();
1391 SetCursorPosition(pos);
1392
1393 if (mState.mCursorPosition.mColumn == 0)
1394 {
1395 if (mState.mCursorPosition.mLine == 0)
1396 return;
1397
1398 u.mRemoved = '\n';
1399 u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
1400 Advance(u.mRemovedEnd);
1401
1402 auto& line = mLines[mState.mCursorPosition.mLine];
1403 auto& prevLine = mLines[mState.mCursorPosition.mLine - 1];
1404 auto prevSize = (int)prevLine.size();
1405 prevLine.insert(prevLine.end(), line.begin(), line.end());
1406 RemoveLine(mState.mCursorPosition.mLine);
1407 --mState.mCursorPosition.mLine;
1408 mState.mCursorPosition.mColumn = prevSize;
1409 }
1410 else
1411 {
1412 auto& line = mLines[mState.mCursorPosition.mLine];
1413
1414 u.mRemoved = line[pos.mColumn - 1].mChar;
1415 u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
1416 --u.mRemovedStart.mColumn;
1417
1418 --mState.mCursorPosition.mColumn;
1419 if (mState.mCursorPosition.mColumn < (int)line.size())
1420 line.erase(line.begin() + mState.mCursorPosition.mColumn);
1421 }
1422
1423 mTextChanged = true;
1424
1425 EnsureCursorVisible();
1426 Colorize(mState.mCursorPosition.mLine, 1);
1427 }
1428
1429 u.mAfter = mState;
1430 AddUndo(u);
1431 }
1432
SelectWordUnderCursor()1433 void TextEditor::SelectWordUnderCursor()
1434 {
1435 auto c = GetCursorPosition();
1436 SetSelection(FindWordStart(c), FindWordEnd(c));
1437 }
1438
SelectAll()1439 void TextEditor::SelectAll()
1440 {
1441 SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), 0));
1442 }
1443
HasSelection() const1444 bool TextEditor::HasSelection() const
1445 {
1446 return mState.mSelectionEnd > mState.mSelectionStart;
1447 }
1448
Copy()1449 void TextEditor::Copy()
1450 {
1451 if (HasSelection())
1452 {
1453 ImGui::SetClipboardText(GetSelectedText().c_str());
1454 }
1455 else
1456 {
1457 if (!mLines.empty())
1458 {
1459 std::string str;
1460 auto& line = mLines[GetActualCursorCoordinates().mLine];
1461 for (auto& g : line)
1462 str.push_back(g.mChar);
1463 ImGui::SetClipboardText(str.c_str());
1464 }
1465 }
1466 }
1467
Cut()1468 void TextEditor::Cut()
1469 {
1470 if (IsReadOnly())
1471 {
1472 Copy();
1473 }
1474 else
1475 {
1476 if (HasSelection())
1477 {
1478 UndoRecord u;
1479 u.mBefore = mState;
1480 u.mRemoved = GetSelectedText();
1481 u.mRemovedStart = mState.mSelectionStart;
1482 u.mRemovedEnd = mState.mSelectionEnd;
1483
1484 Copy();
1485 DeleteSelection();
1486
1487 u.mAfter = mState;
1488 AddUndo(u);
1489 }
1490 }
1491 }
1492
Paste()1493 void TextEditor::Paste()
1494 {
1495 auto clipText = ImGui::GetClipboardText();
1496 if (clipText != nullptr && strlen(clipText) > 0)
1497 {
1498 UndoRecord u;
1499 u.mBefore = mState;
1500
1501 if (HasSelection())
1502 {
1503 u.mRemoved = GetSelectedText();
1504 u.mRemovedStart = mState.mSelectionStart;
1505 u.mRemovedEnd = mState.mSelectionEnd;
1506 DeleteSelection();
1507 }
1508
1509 u.mAdded = clipText;
1510 u.mAddedStart = GetActualCursorCoordinates();
1511
1512 InsertText(clipText);
1513
1514 u.mAddedEnd = GetActualCursorCoordinates();
1515 u.mAfter = mState;
1516 AddUndo(u);
1517 }
1518
1519 // [Bruno Levy] additional callback
1520 if(callback_ != nullptr) {
1521 callback_(TEXT_EDITOR_TEXT_CHANGED, callback_client_data_);
1522 }
1523 }
1524
CanUndo() const1525 bool TextEditor::CanUndo() const
1526 {
1527 return mUndoIndex > 0;
1528 }
1529
CanRedo() const1530 bool TextEditor::CanRedo() const
1531 {
1532 return mUndoIndex < (int)mUndoBuffer.size();
1533 }
1534
Undo(int aSteps)1535 void TextEditor::Undo(int aSteps)
1536 {
1537 while (CanUndo() && aSteps-- > 0)
1538 mUndoBuffer[--mUndoIndex].Undo(this);
1539 }
1540
Redo(int aSteps)1541 void TextEditor::Redo(int aSteps)
1542 {
1543 while (CanRedo() && aSteps-- > 0)
1544 mUndoBuffer[mUndoIndex++].Redo(this);
1545 }
1546
GetDarkPalette()1547 const TextEditor::Palette & TextEditor::GetDarkPalette()
1548 {
1549 // [Bruno Levy] separated declaration of values
1550 // for older compilers.
1551 static unsigned int data[] = {
1552 0xffffffff, // None
1553 0xffd69c56, // Keyword
1554 0xff00ff00, // Number
1555 0xff7070e0, // String
1556 0xff70a0e0, // Char literal
1557 0xffffffff, // Punctuation
1558 0xff409090, // Preprocessor
1559 0xffaaaaaa, // Identifier
1560 0xff9bc64d, // Known identifier
1561 0xffc040a0, // Preproc identifier
1562 0xff20A020, // Comment (single line) [BL]: A0 instead of 60, else it's too dark.
1563 0xff40A020, // Comment (multi line) [BL]: A0 instead of 60, else it's too dark.
1564 0xff101010, // Background
1565 0xffe0e0e0, // Cursor
1566 0x80a06020, // Selection
1567 0x800020ff, // ErrorMarker
1568 0x40f08000, // Breakpoint
1569 0xff707000, // Line number
1570 0x40000000, // Current line fill
1571 0x40808080, // Current line fill (inactive)
1572 0x40a0a0a0, // Current line edge
1573 };
1574 static Palette p;
1575 for(unsigned int i=0; i<(unsigned)PaletteIndex::Max; ++i) {
1576 p[i] = data[i];
1577 }
1578 return p;
1579 }
1580
GetLightPalette()1581 const TextEditor::Palette & TextEditor::GetLightPalette()
1582 {
1583 static Palette p = { {
1584 0xff000000, // None
1585 0xffff0c06, // Keyword
1586 0xff008000, // Number
1587 0xff2020a0, // String
1588 0xff304070, // Char literal
1589 0xff000000, // Punctuation
1590 0xff409090, // Preprocessor
1591 0xff404040, // Identifier
1592 0xff606010, // Known identifier
1593 0xffc040a0, // Preproc identifier
1594 0xff205020, // Comment (single line)
1595 0xff405020, // Comment (multi line)
1596 0xffffffff, // Background
1597 0xff000000, // Cursor
1598 0x80600000, // Selection
1599 0xa00010ff, // ErrorMarker
1600 0x80f08000, // Breakpoint
1601 0xff505000, // Line number
1602 0x40000000, // Current line fill
1603 0x40808080, // Current line fill (inactive)
1604 0x40000000, // Current line edge
1605 } };
1606 return p;
1607 }
1608
GetRetroBluePalette()1609 const TextEditor::Palette & TextEditor::GetRetroBluePalette()
1610 {
1611 static Palette p = { {
1612 0xff00ffff, // None
1613 0xffffff00, // Keyword
1614 0xff00ff00, // Number
1615 0xff808000, // String
1616 0xff808000, // Char literal
1617 0xffffffff, // Punctuation
1618 0xff008000, // Preprocessor
1619 0xff00ffff, // Identifier
1620 0xffffffff, // Known identifier
1621 0xffff00ff, // Preproc identifier
1622 0xff808080, // Comment (single line)
1623 0xff404040, // Comment (multi line)
1624 0xff800000, // Background
1625 0xff0080ff, // Cursor
1626 0x80ffff00, // Selection
1627 0xa00000ff, // ErrorMarker
1628 0x80ff8000, // Breakpoint
1629 0xff808000, // Line number
1630 0x40000000, // Current line fill
1631 0x40808080, // Current line fill (inactive)
1632 0x40000000, // Current line edge
1633 } };
1634 return p;
1635 }
1636
1637
GetText() const1638 std::string TextEditor::GetText() const
1639 {
1640 return GetText(Coordinates(), Coordinates((int)mLines.size(), 0));
1641 }
1642
GetSelectedText() const1643 std::string TextEditor::GetSelectedText() const
1644 {
1645 return GetText(mState.mSelectionStart, mState.mSelectionEnd);
1646 }
1647
ProcessInputs()1648 void TextEditor::ProcessInputs()
1649 {
1650 }
1651
Colorize(int aFromLine,int aLines)1652 void TextEditor::Colorize(int aFromLine, int aLines)
1653 {
1654 int toLine = aLines == -1 ? (int)mLines.size() : std::min((int)mLines.size(), aFromLine + aLines);
1655 mColorRangeMin = std::min(mColorRangeMin, aFromLine);
1656 mColorRangeMax = std::max(mColorRangeMax, toLine);
1657 mColorRangeMin = std::max(0, mColorRangeMin);
1658 mColorRangeMax = std::max(mColorRangeMin, mColorRangeMax);
1659 mCheckMultilineComments = true;
1660 }
1661
ColorizeRange(int aFromLine,int aToLine)1662 void TextEditor::ColorizeRange(int aFromLine, int aToLine)
1663 {
1664 if (mLines.empty() || aFromLine >= aToLine)
1665 return;
1666
1667 std::string buffer;
1668 int endLine = std::max(0, std::min((int)mLines.size(), aToLine));
1669 for (int i = aFromLine; i < endLine; ++i)
1670 {
1671 bool preproc = false;
1672 auto& line = mLines[i];
1673 buffer.clear();
1674 for (auto g : mLines[i])
1675 {
1676 buffer.push_back(g.mChar);
1677 g.mColorIndex = PaletteIndex::Default;
1678 }
1679
1680 std::match_results<std::string::const_iterator> results;
1681 auto last = buffer.cend();
1682 for (auto first = buffer.cbegin(); first != last; ++first)
1683 {
1684 for (auto& p : mRegexList)
1685 {
1686 if (std::regex_search<std::string::const_iterator>(first, last, results, p.first, std::regex_constants::match_continuous))
1687 {
1688 auto v = *results.begin();
1689 auto start = v.first - buffer.begin();
1690 auto end = v.second - buffer.begin();
1691 auto id = buffer.substr(start, end - start);
1692 auto color = p.second;
1693 if (color == PaletteIndex::Identifier)
1694 {
1695 if (!mLanguageDefinition.mCaseSensitive)
1696 std::transform(id.begin(), id.end(), id.begin(), ::toupper);
1697
1698 if (!preproc)
1699 {
1700 if (mLanguageDefinition.mKeywords.find(id) != mLanguageDefinition.mKeywords.end())
1701 color = PaletteIndex::Keyword;
1702 else if (mLanguageDefinition.mIdentifiers.find(id) != mLanguageDefinition.mIdentifiers.end())
1703 color = PaletteIndex::KnownIdentifier;
1704 else if (mLanguageDefinition.mPreprocIdentifiers.find(id) != mLanguageDefinition.mPreprocIdentifiers.end())
1705 color = PaletteIndex::PreprocIdentifier;
1706 }
1707 else
1708 {
1709 if (mLanguageDefinition.mPreprocIdentifiers.find(id) != mLanguageDefinition.mPreprocIdentifiers.end())
1710 color = PaletteIndex::PreprocIdentifier;
1711 else
1712 color = PaletteIndex::Identifier;
1713 }
1714 }
1715 else if (color == PaletteIndex::Preprocessor)
1716 {
1717 preproc = true;
1718 }
1719 for (int j = (int)start; j < (int)end; ++j)
1720 line[j].mColorIndex = color;
1721 first += end - start - 1;
1722 break;
1723 }
1724 }
1725 }
1726 }
1727 }
1728
ColorizeInternal()1729 void TextEditor::ColorizeInternal()
1730 {
1731 if (mLines.empty())
1732 return;
1733
1734 if (mCheckMultilineComments)
1735 {
1736 auto end = Coordinates((int)mLines.size(), 0);
1737 auto commentStart = end;
1738 auto withinString = false;
1739 for (auto i = Coordinates(0, 0); i < end; Advance(i))
1740 {
1741 auto& line = mLines[i.mLine];
1742 if (!line.empty())
1743 {
1744 auto g = line[i.mColumn];
1745 auto c = g.mChar;
1746
1747 bool inComment = commentStart <= i;
1748
1749 if (withinString)
1750 {
1751 line[i.mColumn].mMultiLineComment = inComment;
1752
1753 if (c == '\"')
1754 {
1755 if (i.mColumn + 1 < (int)line.size() && line[i.mColumn + 1].mChar == '\"')
1756 {
1757 Advance(i);
1758 if (i.mColumn < (int)line.size())
1759 line[i.mColumn].mMultiLineComment = inComment;
1760 }
1761 else
1762 withinString = false;
1763 }
1764 else if (c == '\\')
1765 {
1766 Advance(i);
1767 if (i.mColumn < (int)line.size())
1768 line[i.mColumn].mMultiLineComment = inComment;
1769 }
1770 }
1771 else
1772 {
1773 if (c == '\"')
1774 {
1775 withinString = true;
1776 line[i.mColumn].mMultiLineComment = inComment;
1777 }
1778 else
1779 {
1780 auto pred = [](const char& a, const Glyph& b) { return a == b.mChar; };
1781 auto from = line.begin() + i.mColumn;
1782 auto& startStr = mLanguageDefinition.mCommentStart;
1783 if (i.mColumn + startStr.size() <= line.size() &&
1784 equals(startStr.begin(), startStr.end(), from, from + startStr.size(), pred))
1785 commentStart = i;
1786
1787 inComment = commentStart <= i;
1788
1789 line[i.mColumn].mMultiLineComment = inComment;
1790
1791 auto& endStr = mLanguageDefinition.mCommentEnd;
1792 if (i.mColumn + 1 >= (int)endStr.size() &&
1793 equals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred))
1794 commentStart = end;
1795 }
1796 }
1797 }
1798 }
1799 mCheckMultilineComments = false;
1800 return;
1801 }
1802
1803 if (mColorRangeMin < mColorRangeMax)
1804 {
1805 int to = std::min(mColorRangeMin + 10, mColorRangeMax);
1806 ColorizeRange(mColorRangeMin, to);
1807 mColorRangeMin = to;
1808
1809 if (mColorRangeMax == mColorRangeMin)
1810 {
1811 mColorRangeMin = std::numeric_limits<int>::max();
1812 mColorRangeMax = 0;
1813 }
1814 return;
1815 }
1816 }
1817
TextDistanceToLineStart(const Coordinates & aFrom) const1818 int TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const
1819 {
1820 auto& line = mLines[aFrom.mLine];
1821 auto len = 0;
1822 for (size_t it = 0u; it < line.size() && it < (unsigned)aFrom.mColumn; ++it)
1823 len = line[it].mChar == '\t' ? (len / mTabSize) * mTabSize + mTabSize : len + 1;
1824 return len;
1825 }
1826
EnsureCursorVisible()1827 void TextEditor::EnsureCursorVisible()
1828 {
1829 if (!mWithinRender)
1830 {
1831 mScrollToCursor = true;
1832 return;
1833 }
1834
1835 float scrollX = ImGui::GetScrollX();
1836 float scrollY = ImGui::GetScrollY();
1837
1838 auto height = ImGui::GetWindowHeight();
1839 auto width = ImGui::GetWindowWidth();
1840
1841 auto top = 1 + (int)ceil(scrollY / mCharAdvance.y);
1842 auto bottom = (int)ceil((scrollY + height) / mCharAdvance.y);
1843
1844 auto left = (int)ceil(scrollX / mCharAdvance.x);
1845 auto right = (int)ceil((scrollX + width) / mCharAdvance.x);
1846
1847 auto pos = GetActualCursorCoordinates();
1848 auto len = TextDistanceToLineStart(pos);
1849
1850 if (pos.mLine < top)
1851 ImGui::SetScrollY(std::max(0.0f, (pos.mLine - 1) * mCharAdvance.y));
1852 if (pos.mLine > bottom - 4)
1853 ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 4) * mCharAdvance.y - height));
1854 if (len + cTextStart < left + 4)
1855 ImGui::SetScrollX(std::max(0.0f, (len + cTextStart - 4) * mCharAdvance.x));
1856 if (len + cTextStart > right - 4)
1857 ImGui::SetScrollX(std::max(0.0f, (len + cTextStart + 4) * mCharAdvance.x - width));
1858 }
1859
GetPageSize() const1860 int TextEditor::GetPageSize() const
1861 {
1862 auto height = ImGui::GetWindowHeight() - 20.0f;
1863 return (int)floor(height / mCharAdvance.y);
1864 }
1865
UndoRecord(const std::string & aAdded,const TextEditor::Coordinates aAddedStart,const TextEditor::Coordinates aAddedEnd,const std::string & aRemoved,const TextEditor::Coordinates aRemovedStart,const TextEditor::Coordinates aRemovedEnd,TextEditor::EditorState & aBefore,TextEditor::EditorState & aAfter)1866 TextEditor::UndoRecord::UndoRecord(
1867 const std::string& aAdded,
1868 const TextEditor::Coordinates aAddedStart,
1869 const TextEditor::Coordinates aAddedEnd,
1870 const std::string& aRemoved,
1871 const TextEditor::Coordinates aRemovedStart,
1872 const TextEditor::Coordinates aRemovedEnd,
1873 TextEditor::EditorState& aBefore,
1874 TextEditor::EditorState& aAfter)
1875 : mAdded(aAdded)
1876 , mAddedStart(aAddedStart)
1877 , mAddedEnd(aAddedEnd)
1878 , mRemoved(aRemoved)
1879 , mRemovedStart(aRemovedStart)
1880 , mRemovedEnd(aRemovedEnd)
1881 , mBefore(aBefore)
1882 , mAfter(aAfter)
1883 {
1884 assert(mAddedStart <= mAddedEnd);
1885 assert(mRemovedStart <= mRemovedEnd);
1886 }
1887
Undo(TextEditor * aEditor)1888 void TextEditor::UndoRecord::Undo(TextEditor * aEditor)
1889 {
1890 if (!mAdded.empty())
1891 {
1892 aEditor->DeleteRange(mAddedStart, mAddedEnd);
1893 aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 2);
1894 }
1895
1896 if (!mRemoved.empty())
1897 {
1898 auto start = mRemovedStart;
1899 aEditor->InsertTextAt(start, mRemoved.c_str());
1900 aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 2);
1901 }
1902
1903 aEditor->mState = mBefore;
1904 aEditor->EnsureCursorVisible();
1905
1906 }
1907
Redo(TextEditor * aEditor)1908 void TextEditor::UndoRecord::Redo(TextEditor * aEditor)
1909 {
1910 if (!mRemoved.empty())
1911 {
1912 aEditor->DeleteRange(mRemovedStart, mRemovedEnd);
1913 aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 1);
1914 }
1915
1916 if (!mAdded.empty())
1917 {
1918 auto start = mAddedStart;
1919 aEditor->InsertTextAt(start, mAdded.c_str());
1920 aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 1);
1921 }
1922
1923 aEditor->mState = mAfter;
1924 aEditor->EnsureCursorVisible();
1925 }
1926
CPlusPlus()1927 TextEditor::LanguageDefinition TextEditor::LanguageDefinition::CPlusPlus()
1928 {
1929 static bool inited = false;
1930 static LanguageDefinition langDef;
1931 if (!inited)
1932 {
1933 static const char* const cppKeywords[] = {
1934 "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",
1935 "compl", "concept", "const", "constexpr", "const_cast", "continue", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float",
1936 "for", "friend", "goto", "if", "import", "inline", "int", "long", "module", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public",
1937 "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this", "thread_local",
1938 "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq"
1939 };
1940 for (auto& k : cppKeywords)
1941 langDef.mKeywords.insert(k);
1942
1943 static const char* const identifiers[] = {
1944 "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph",
1945 "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",
1946 "std", "string", "vector", "map", "unordered_map", "set", "unordered_set", "min", "max"
1947 };
1948 for (auto& k : identifiers)
1949 {
1950 Identifier id;
1951 id.mDeclaration = "Built-in function";
1952 langDef.mIdentifiers.insert(std::make_pair(std::string(k), id));
1953 }
1954
1955 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("//.*", PaletteIndex::Comment));
1956 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor));
1957 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
1958 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral));
1959 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
1960 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
1961 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
1962 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
1963 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
1964 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation));
1965
1966 langDef.mCommentStart = "/*";
1967 langDef.mCommentEnd = "*/";
1968
1969 langDef.mCaseSensitive = true;
1970
1971 langDef.mName = "C++";
1972
1973 inited = true;
1974 }
1975 return langDef;
1976 }
1977
HLSL()1978 TextEditor::LanguageDefinition TextEditor::LanguageDefinition::HLSL()
1979 {
1980 static bool inited = false;
1981 static LanguageDefinition langDef;
1982 if (!inited)
1983 {
1984 static const char* const keywords[] = {
1985 "AppendStructuredBuffer", "asm", "asm_fragment", "BlendState", "bool", "break", "Buffer", "ByteAddressBuffer", "case", "cbuffer", "centroid", "class", "column_major", "compile", "compile_fragment",
1986 "CompileShader", "const", "continue", "ComputeShader", "ConsumeStructuredBuffer", "default", "DepthStencilState", "DepthStencilView", "discard", "do", "double", "DomainShader", "dword", "else",
1987 "export", "extern", "false", "float", "for", "fxgroup", "GeometryShader", "groupshared", "half", "Hullshader", "if", "in", "inline", "inout", "InputPatch", "int", "interface", "line", "lineadj",
1988 "linear", "LineStream", "matrix", "min16float", "min10float", "min16int", "min12int", "min16uint", "namespace", "nointerpolation", "noperspective", "NULL", "out", "OutputPatch", "packoffset",
1989 "pass", "pixelfragment", "PixelShader", "point", "PointStream", "precise", "RasterizerState", "RenderTargetView", "return", "register", "row_major", "RWBuffer", "RWByteAddressBuffer", "RWStructuredBuffer",
1990 "RWTexture1D", "RWTexture1DArray", "RWTexture2D", "RWTexture2DArray", "RWTexture3D", "sample", "sampler", "SamplerState", "SamplerComparisonState", "shared", "snorm", "stateblock", "stateblock_state",
1991 "static", "string", "struct", "switch", "StructuredBuffer", "tbuffer", "technique", "technique10", "technique11", "texture", "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", "Texture2DMS",
1992 "Texture2DMSArray", "Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle", "triangleadj", "TriangleStream", "uint", "uniform", "unorm", "unsigned", "vector", "vertexfragment",
1993 "VertexShader", "void", "volatile", "while",
1994 "bool1","bool2","bool3","bool4","double1","double2","double3","double4", "float1", "float2", "float3", "float4", "int1", "int2", "int3", "int4", "in", "out", "inout",
1995 "uint1", "uint2", "uint3", "uint4", "dword1", "dword2", "dword3", "dword4", "half1", "half2", "half3", "half4",
1996 "float1x1","float2x1","float3x1","float4x1","float1x2","float2x2","float3x2","float4x2",
1997 "float1x3","float2x3","float3x3","float4x3","float1x4","float2x4","float3x4","float4x4",
1998 "half1x1","half2x1","half3x1","half4x1","half1x2","half2x2","half3x2","half4x2",
1999 "half1x3","half2x3","half3x3","half4x3","half1x4","half2x4","half3x4","half4x4",
2000 };
2001 for (auto& k : keywords)
2002 langDef.mKeywords.insert(k);
2003
2004 static const char* const identifiers[] = {
2005 "abort", "abs", "acos", "all", "AllMemoryBarrier", "AllMemoryBarrierWithGroupSync", "any", "asdouble", "asfloat", "asin", "asint", "asint", "asuint",
2006 "asuint", "atan", "atan2", "ceil", "CheckAccessFullyMapped", "clamp", "clip", "cos", "cosh", "countbits", "cross", "D3DCOLORtoUBYTE4", "ddx",
2007 "ddx_coarse", "ddx_fine", "ddy", "ddy_coarse", "ddy_fine", "degrees", "determinant", "DeviceMemoryBarrier", "DeviceMemoryBarrierWithGroupSync",
2008 "distance", "dot", "dst", "errorf", "EvaluateAttributeAtCentroid", "EvaluateAttributeAtSample", "EvaluateAttributeSnapped", "exp", "exp2",
2009 "f16tof32", "f32tof16", "faceforward", "firstbithigh", "firstbitlow", "floor", "fma", "fmod", "frac", "frexp", "fwidth", "GetRenderTargetSampleCount",
2010 "GetRenderTargetSamplePosition", "GroupMemoryBarrier", "GroupMemoryBarrierWithGroupSync", "InterlockedAdd", "InterlockedAnd", "InterlockedCompareExchange",
2011 "InterlockedCompareStore", "InterlockedExchange", "InterlockedMax", "InterlockedMin", "InterlockedOr", "InterlockedXor", "isfinite", "isinf", "isnan",
2012 "ldexp", "length", "lerp", "lit", "log", "log10", "log2", "mad", "max", "min", "modf", "msad4", "mul", "noise", "normalize", "pow", "printf",
2013 "Process2DQuadTessFactorsAvg", "Process2DQuadTessFactorsMax", "Process2DQuadTessFactorsMin", "ProcessIsolineTessFactors", "ProcessQuadTessFactorsAvg",
2014 "ProcessQuadTessFactorsMax", "ProcessQuadTessFactorsMin", "ProcessTriTessFactorsAvg", "ProcessTriTessFactorsMax", "ProcessTriTessFactorsMin",
2015 "radians", "rcp", "reflect", "refract", "reversebits", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "sqrt", "step",
2016 "tan", "tanh", "tex1D", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", "tex2D", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj",
2017 "tex3D", "tex3D", "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", "texCUBE", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose", "trunc"
2018 };
2019 for (auto& k : identifiers)
2020 {
2021 Identifier id;
2022 id.mDeclaration = "Built-in function";
2023 langDef.mIdentifiers.insert(std::make_pair(std::string(k), id));
2024 }
2025
2026 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("//.*", PaletteIndex::Comment));
2027 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor));
2028 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
2029 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral));
2030 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
2031 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2032 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2033 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
2034 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
2035 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation));
2036
2037 langDef.mCommentStart = "/*";
2038 langDef.mCommentEnd = "*/";
2039
2040 langDef.mCaseSensitive = true;
2041
2042 langDef.mName = "HLSL";
2043
2044 inited = true;
2045 }
2046 return langDef;
2047 }
2048
GLSL()2049 TextEditor::LanguageDefinition TextEditor::LanguageDefinition::GLSL()
2050 {
2051 static bool inited = false;
2052 static LanguageDefinition langDef;
2053 if (!inited)
2054 {
2055 static const char* const keywords[] = {
2056 "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short",
2057 "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary",
2058 "_Noreturn", "_Static_assert", "_Thread_local"
2059 };
2060 for (auto& k : keywords)
2061 langDef.mKeywords.insert(k);
2062
2063 static const char* const identifiers[] = {
2064 "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph",
2065 "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"
2066 };
2067 for (auto& k : identifiers)
2068 {
2069 Identifier id;
2070 id.mDeclaration = "Built-in function";
2071 langDef.mIdentifiers.insert(std::make_pair(std::string(k), id));
2072 }
2073
2074 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("//.*", PaletteIndex::Comment));
2075 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor));
2076 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
2077 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral));
2078 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
2079 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2080 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2081 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
2082 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
2083 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation));
2084
2085 langDef.mCommentStart = "/*";
2086 langDef.mCommentEnd = "*/";
2087
2088 langDef.mCaseSensitive = true;
2089
2090 langDef.mName = "GLSL";
2091
2092 inited = true;
2093 }
2094 return langDef;
2095 }
2096
C()2097 TextEditor::LanguageDefinition TextEditor::LanguageDefinition::C()
2098 {
2099 static bool inited = false;
2100 static LanguageDefinition langDef;
2101 if (!inited)
2102 {
2103 static const char* const keywords[] = {
2104 "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short",
2105 "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary",
2106 "_Noreturn", "_Static_assert", "_Thread_local"
2107 };
2108 for (auto& k : keywords)
2109 langDef.mKeywords.insert(k);
2110
2111 static const char* const identifiers[] = {
2112 "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph",
2113 "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"
2114 };
2115 for (auto& k : identifiers)
2116 {
2117 Identifier id;
2118 id.mDeclaration = "Built-in function";
2119 langDef.mIdentifiers.insert(std::make_pair(std::string(k), id));
2120 }
2121
2122 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("//.*", PaletteIndex::Comment));
2123 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor));
2124 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
2125 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral));
2126 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
2127 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2128 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2129 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
2130 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
2131 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation));
2132
2133 langDef.mCommentStart = "/*";
2134 langDef.mCommentEnd = "*/";
2135
2136 langDef.mCaseSensitive = true;
2137
2138 langDef.mName = "C";
2139
2140 inited = true;
2141 }
2142 return langDef;
2143 }
2144
SQL()2145 TextEditor::LanguageDefinition TextEditor::LanguageDefinition::SQL()
2146 {
2147 static bool inited = false;
2148 static LanguageDefinition langDef;
2149 if (!inited)
2150 {
2151 static const char* const keywords[] = {
2152 "ADD", "EXCEPT", "PERCENT", "ALL", "EXEC", "PLAN", "ALTER", "EXECUTE", "PRECISION", "AND", "EXISTS", "PRIMARY", "ANY", "EXIT", "PRINT", "AS", "FETCH", "PROC", "ASC", "FILE", "PROCEDURE",
2153 "AUTHORIZATION", "FILLFACTOR", "PUBLIC", "BACKUP", "FOR", "RAISERROR", "BEGIN", "FOREIGN", "READ", "BETWEEN", "FREETEXT", "READTEXT", "BREAK", "FREETEXTTABLE", "RECONFIGURE",
2154 "BROWSE", "FROM", "REFERENCES", "BULK", "FULL", "REPLICATION", "BY", "FUNCTION", "RESTORE", "CASCADE", "GOTO", "RESTRICT", "CASE", "GRANT", "RETURN", "CHECK", "GROUP", "REVOKE",
2155 "CHECKPOINT", "HAVING", "RIGHT", "CLOSE", "HOLDLOCK", "ROLLBACK", "CLUSTERED", "IDENTITY", "ROWCOUNT", "COALESCE", "IDENTITY_INSERT", "ROWGUIDCOL", "COLLATE", "IDENTITYCOL", "RULE",
2156 "COLUMN", "IF", "SAVE", "COMMIT", "IN", "SCHEMA", "COMPUTE", "INDEX", "SELECT", "CONSTRAINT", "INNER", "SESSION_USER", "CONTAINS", "INSERT", "SET", "CONTAINSTABLE", "INTERSECT", "SETUSER",
2157 "CONTINUE", "INTO", "SHUTDOWN", "CONVERT", "IS", "SOME", "CREATE", "JOIN", "STATISTICS", "CROSS", "KEY", "SYSTEM_USER", "CURRENT", "KILL", "TABLE", "CURRENT_DATE", "LEFT", "TEXTSIZE",
2158 "CURRENT_TIME", "LIKE", "THEN", "CURRENT_TIMESTAMP", "LINENO", "TO", "CURRENT_USER", "LOAD", "TOP", "CURSOR", "NATIONAL", "TRAN", "DATABASE", "NOCHECK", "TRANSACTION",
2159 "DBCC", "NONCLUSTERED", "TRIGGER", "DEALLOCATE", "NOT", "TRUNCATE", "DECLARE", "NULL", "TSEQUAL", "DEFAULT", "NULLIF", "UNION", "DELETE", "OF", "UNIQUE", "DENY", "OFF", "UPDATE",
2160 "DESC", "OFFSETS", "UPDATETEXT", "DISK", "ON", "USE", "DISTINCT", "OPEN", "USER", "DISTRIBUTED", "OPENDATASOURCE", "VALUES", "DOUBLE", "OPENQUERY", "VARYING","DROP", "OPENROWSET", "VIEW",
2161 "DUMMY", "OPENXML", "WAITFOR", "DUMP", "OPTION", "WHEN", "ELSE", "OR", "WHERE", "END", "ORDER", "WHILE", "ERRLVL", "OUTER", "WITH", "ESCAPE", "OVER", "WRITETEXT"
2162 };
2163
2164 for (auto& k : keywords)
2165 langDef.mKeywords.insert(k);
2166
2167 static const char* const identifiers[] = {
2168 "ABS", "ACOS", "ADD_MONTHS", "ASCII", "ASCIISTR", "ASIN", "ATAN", "ATAN2", "AVG", "BFILENAME", "BIN_TO_NUM", "BITAND", "CARDINALITY", "CASE", "CAST", "CEIL",
2169 "CHARTOROWID", "CHR", "COALESCE", "COMPOSE", "CONCAT", "CONVERT", "CORR", "COS", "COSH", "COUNT", "COVAR_POP", "COVAR_SAMP", "CUME_DIST", "CURRENT_DATE",
2170 "CURRENT_TIMESTAMP", "DBTIMEZONE", "DECODE", "DECOMPOSE", "DENSE_RANK", "DUMP", "EMPTY_BLOB", "EMPTY_CLOB", "EXP", "EXTRACT", "FIRST_VALUE", "FLOOR", "FROM_TZ", "GREATEST",
2171 "GROUP_ID", "HEXTORAW", "INITCAP", "INSTR", "INSTR2", "INSTR4", "INSTRB", "INSTRC", "LAG", "LAST_DAY", "LAST_VALUE", "LEAD", "LEAST", "LENGTH", "LENGTH2", "LENGTH4",
2172 "LENGTHB", "LENGTHC", "LISTAGG", "LN", "LNNVL", "LOCALTIMESTAMP", "LOG", "LOWER", "LPAD", "LTRIM", "MAX", "MEDIAN", "MIN", "MOD", "MONTHS_BETWEEN", "NANVL", "NCHR",
2173 "NEW_TIME", "NEXT_DAY", "NTH_VALUE", "NULLIF", "NUMTODSINTERVAL", "NUMTOYMINTERVAL", "NVL", "NVL2", "POWER", "RANK", "RAWTOHEX", "REGEXP_COUNT", "REGEXP_INSTR",
2174 "REGEXP_REPLACE", "REGEXP_SUBSTR", "REMAINDER", "REPLACE", "ROUND", "ROWNUM", "RPAD", "RTRIM", "SESSIONTIMEZONE", "SIGN", "SIN", "SINH",
2175 "SOUNDEX", "SQRT", "STDDEV", "SUBSTR", "SUM", "SYS_CONTEXT", "SYSDATE", "SYSTIMESTAMP", "TAN", "TANH", "TO_CHAR", "TO_CLOB", "TO_DATE", "TO_DSINTERVAL", "TO_LOB",
2176 "TO_MULTI_BYTE", "TO_NCLOB", "TO_NUMBER", "TO_SINGLE_BYTE", "TO_TIMESTAMP", "TO_TIMESTAMP_TZ", "TO_YMINTERVAL", "TRANSLATE", "TRIM", "TRUNC", "TZ_OFFSET", "UID", "UPPER",
2177 "USER", "USERENV", "VAR_POP", "VAR_SAMP", "VARIANCE", "VSIZE "
2178 };
2179 for (auto& k : identifiers)
2180 {
2181 Identifier id;
2182 id.mDeclaration = "Built-in function";
2183 langDef.mIdentifiers.insert(std::make_pair(std::string(k), id));
2184 }
2185
2186 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\-\\-.*", PaletteIndex::Comment));
2187 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
2188 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\\'[^\\\']*\\\'", PaletteIndex::String));
2189 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
2190 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2191 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2192 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
2193 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
2194 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation));
2195
2196 langDef.mCommentStart = "/*";
2197 langDef.mCommentEnd = "*/";
2198
2199 langDef.mCaseSensitive = false;
2200
2201 langDef.mName = "SQL";
2202
2203 inited = true;
2204 }
2205 return langDef;
2206 }
2207
AngelScript()2208 TextEditor::LanguageDefinition TextEditor::LanguageDefinition::AngelScript()
2209 {
2210 static bool inited = false;
2211 static LanguageDefinition langDef;
2212 if (!inited)
2213 {
2214 static const char* const keywords[] = {
2215 "and", "abstract", "auto", "bool", "break", "case", "cast", "class", "const", "continue", "default", "do", "double", "else", "enum", "false", "final", "float", "for",
2216 "from", "funcdef", "function", "get", "if", "import", "in", "inout", "int", "interface", "int8", "int16", "int32", "int64", "is", "mixin", "namespace", "not",
2217 "null", "or", "out", "override", "private", "protected", "return", "set", "shared", "super", "switch", "this ", "true", "typedef", "uint", "uint8", "uint16", "uint32",
2218 "uint64", "void", "while", "xor"
2219 };
2220
2221 for (auto& k : keywords)
2222 langDef.mKeywords.insert(k);
2223
2224 static const char* const identifiers[] = {
2225 "cos", "sin", "tab", "acos", "asin", "atan", "atan2", "cosh", "sinh", "tanh", "log", "log10", "pow", "sqrt", "abs", "ceil", "floor", "fraction", "closeTo", "fpFromIEEE", "fpToIEEE",
2226 "complex", "opEquals", "opAddAssign", "opSubAssign", "opMulAssign", "opDivAssign", "opAdd", "opSub", "opMul", "opDiv"
2227 };
2228 for (auto& k : identifiers)
2229 {
2230 Identifier id;
2231 id.mDeclaration = "Built-in function";
2232 langDef.mIdentifiers.insert(std::make_pair(std::string(k), id));
2233 }
2234
2235 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("//.*", PaletteIndex::Comment));
2236 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
2237 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::String));
2238 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
2239 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2240 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2241 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
2242 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
2243 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation));
2244
2245 langDef.mCommentStart = "/*";
2246 langDef.mCommentEnd = "*/";
2247
2248 langDef.mCaseSensitive = true;
2249
2250 langDef.mName = "AngelScript";
2251
2252 inited = true;
2253 }
2254 return langDef;
2255 }
2256
Lua()2257 TextEditor::LanguageDefinition TextEditor::LanguageDefinition::Lua()
2258 {
2259 static bool inited = false;
2260 static LanguageDefinition langDef;
2261 if (!inited)
2262 {
2263 static const char* const keywords[] = {
2264 "and", "break", "do", "", "else", "elseif", "end", "false", "for", "function", "if", "in", "", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"
2265 };
2266
2267 for (auto& k : keywords)
2268 langDef.mKeywords.insert(k);
2269
2270 static const char* const identifiers[] = {
2271 "assert", "collectgarbage", "dofile", "error", "getmetatable", "ipairs", "loadfile", "load", "loadstring", "next", "pairs", "pcall", "print", "rawequal", "rawlen", "rawget", "rawset",
2272 "select", "setmetatable", "tonumber", "tostring", "type", "xpcall", "_G", "_VERSION","arshift", "band", "bnot", "bor", "bxor", "btest", "extract", "lrotate", "lshift", "replace",
2273 "rrotate", "rshift", "create", "resume", "running", "status", "wrap", "yield", "isyieldable", "debug","getuservalue", "gethook", "getinfo", "getlocal", "getregistry", "getmetatable",
2274 "getupvalue", "upvaluejoin", "upvalueid", "setuservalue", "sethook", "setlocal", "setmetatable", "setupvalue", "traceback", "close", "flush", "input", "lines", "open", "output", "popen",
2275 "read", "tmpfile", "type", "write", "close", "flush", "lines", "read", "seek", "setvbuf", "write", "__gc", "__tostring", "abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", "tointeger",
2276 "floor", "fmod", "ult", "log", "max", "min", "modf", "rad", "random", "randomseed", "sin", "sqrt", "string", "tan", "type", "atan2", "cosh", "sinh", "tanh",
2277 "pow", "frexp", "ldexp", "log10", "pi", "huge", "maxinteger", "mininteger", "loadlib", "searchpath", "seeall", "preload", "cpath", "path", "searchers", "loaded", "module", "require", "clock",
2278 "date", "difftime", "execute", "exit", "getenv", "remove", "rename", "setlocale", "time", "tmpname", "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep",
2279 "reverse", "sub", "upper", "pack", "packsize", "unpack", "concat", "maxn", "insert", "pack", "unpack", "remove", "move", "sort", "offset", "codepoint", "char", "len", "codes", "charpattern",
2280 "coroutine", "table", "io", "os", "string", "utf8", "bit32", "math", "debug", "package"
2281 };
2282 for (auto& k : identifiers)
2283 {
2284 Identifier id;
2285 id.mDeclaration = "Built-in function";
2286 langDef.mIdentifiers.insert(std::make_pair(std::string(k), id));
2287 }
2288
2289 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\-\\-.*", PaletteIndex::Comment));
2290 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
2291 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\\'[^\\\']*\\\'", PaletteIndex::String));
2292 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
2293 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
2294 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
2295 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
2296 langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation));
2297
2298 langDef.mCommentStart = "\\-\\-\\[\\[";
2299 langDef.mCommentEnd = "\\]\\]";
2300
2301 langDef.mCaseSensitive = true;
2302
2303 langDef.mName = "Lua";
2304
2305 inited = true;
2306 }
2307 return langDef;
2308 }
2309
2310 /****************************************************/
2311
2312 // [Bruno Levy] additional function for finding the context for tooltips
2313 //
2314 // TODO: more subtle detection of function parameters versus result of
2315 // function call, depending of where the mouse pointer is.
2316
IsWordContextBoundary(char c) const2317 bool TextEditor::IsWordContextBoundary(char c) const {
2318 return
2319 c == ' ' ||
2320 c == '\t' ||
2321 c == ',' ||
2322 c == '(' ||
2323 c == ')' ||
2324 c == '=' ;
2325 ;
2326 }
2327
FindWordContextStart(const Coordinates & aFrom) const2328 TextEditor::Coordinates TextEditor::FindWordContextStart(const Coordinates& aFrom) const {
2329 Coordinates at = aFrom;
2330 if (at.mLine >= (int)mLines.size()) {
2331 return at;
2332 }
2333
2334 auto& line = mLines[at.mLine];
2335
2336 if (at.mColumn >= (int)line.size()) {
2337 return at;
2338 }
2339
2340 while (at.mColumn > 0) {
2341 if(IsWordContextBoundary(line[at.mColumn - 1].mChar)) {
2342 break;
2343 }
2344 --at.mColumn;
2345 }
2346 return at;
2347 }
2348
FindWordContextEnd(const Coordinates & aFrom) const2349 TextEditor::Coordinates TextEditor::FindWordContextEnd(const Coordinates& aFrom) const {
2350 Coordinates at = aFrom;
2351 if (at.mLine >= (int)mLines.size()) {
2352 return at;
2353 }
2354
2355 auto& line = mLines[at.mLine];
2356
2357 if (at.mColumn >= (int)line.size()) {
2358 return at;
2359 }
2360
2361 while (at.mColumn < (int)line.size()) {
2362 if(IsWordContextBoundary(line[at.mColumn].mChar)) {
2363 break;
2364 }
2365 ++at.mColumn;
2366 }
2367 return at;
2368 }
2369
GetWordContextAt(const Coordinates & aCoords) const2370 std::string TextEditor::GetWordContextAt(const Coordinates & aCoords) const {
2371 std::string r;
2372
2373 if(aCoords.mLine >= int(mLines.size())) {
2374 return r;
2375 }
2376
2377 // Test if we are inside a comment
2378 {
2379 int pos = -1;
2380 const Line& L = mLines[aCoords.mLine];
2381 for(int i=0; i+1 < int(L.size()); ++i) {
2382 if(L[i].mChar == '-' && L[i+1].mChar == '-') {
2383 pos = i;
2384 break;
2385 }
2386 }
2387 if(pos != -1 && aCoords.mColumn > pos) {
2388 return r;
2389 }
2390 }
2391
2392 auto start = FindWordContextStart(aCoords);
2393 auto end = FindWordContextEnd(aCoords);
2394 for (auto it = start; it < end; Advance(it)) {
2395 r.push_back(mLines[it.mLine][it.mColumn].mChar);
2396 }
2397 return r;
2398 }
2399
2400
2401
2402
2403
2404