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