| #include <algorithm>
|
| #include <chrono>
|
| #include <string>
|
| #include <regex>
|
| #include <cmath>
|
|
|
| #include "TextEditor.h"
|
|
|
| #define IMGUI_DEFINE_MATH_OPERATORS
|
| #include "imgui.h" // for imGui::GetCurrentWindow()
|
|
|
| // TODO
|
| // - multiline comments vs single-line: latter is blocking start of a ML
|
| // - handle unicode/utf
|
| // - testing
|
|
|
| template<class InputIt1, class InputIt2, class BinaryPredicate>
|
| bool equals(InputIt1 first1, InputIt1 last1,
|
| InputIt2 first2, InputIt2 last2, BinaryPredicate p)
|
| {
|
| for (; first1 != last1 && first2 != last2; ++first1, ++first2)
|
| {
|
| if (!p(*first1, *first2))
|
| return false;
|
| }
|
| return first1 == last1 && first2 == last2;
|
| }
|
|
|
| TextEditor::TextEditor()
|
| : mLineSpacing(1.0f)
|
| , mUndoIndex(0)
|
| , mTabSize(4)
|
| , mOverwrite(false)
|
| , mReadOnly(false)
|
| , mWithinRender(false)
|
| , mScrollToCursor(false)
|
| , mTextChanged(false)
|
| , mTextStart(20.0f)
|
| , mLeftMargin(10)
|
| , mColorRangeMin(0)
|
| , mColorRangeMax(0)
|
| , mSelectionMode(SelectionMode::Normal)
|
| , mCheckMultilineComments(true)
|
| {
|
| SetPalette(GetDarkPalette());
|
| SetLanguageDefinition(LanguageDefinition::HLSL());
|
| mLines.push_back(Line());
|
| }
|
|
|
|
|
| TextEditor::~TextEditor()
|
| {
|
| }
|
|
|
| void TextEditor::SetLanguageDefinition(const LanguageDefinition & aLanguageDef)
|
| {
|
| mLanguageDefinition = aLanguageDef;
|
| mRegexList.clear();
|
|
|
| for (auto& r : mLanguageDefinition.mTokenRegexStrings)
|
| mRegexList.push_back(std::make_pair(std::regex(r.first, std::regex_constants::optimize), r.second));
|
| }
|
|
|
| void TextEditor::SetPalette(const Palette & aValue)
|
| {
|
| mPalette = aValue;
|
| }
|
|
|
| int TextEditor::AppendBuffer(std::string& aBuffer, char chr, int aIndex)
|
| {
|
| if (chr != '\t')
|
| {
|
| aBuffer.push_back(chr);
|
| return aIndex + 1;
|
| }
|
| else
|
| {
|
| //auto num = mTabSize - aIndex % mTabSize;
|
| //for (int j = num; j > 0; --j)
|
| // aBuffer.push_back(' ');
|
| //return aIndex + num;
|
| return aIndex;
|
| }
|
| }
|
|
|
| std::string TextEditor::GetText(const Coordinates & aStart, const Coordinates & aEnd) const
|
| {
|
| std::string result;
|
|
|
| int prevLineNo = aStart.mLine;
|
| for (auto it = aStart; it <= aEnd; Advance(it))
|
| {
|
| if (prevLineNo != it.mLine && it.mLine < (int) mLines.size())
|
| result.push_back('\n');
|
|
|
| if (it == aEnd)
|
| break;
|
|
|
| prevLineNo = it.mLine;
|
| const auto& line = mLines[it.mLine];
|
| if (!line.empty() && it.mColumn < (int)line.size())
|
| result.push_back(line[it.mColumn].mChar);
|
| }
|
|
|
| return result;
|
| }
|
|
|
| TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const
|
| {
|
| return SanitizeCoordinates(mState.mCursorPosition);
|
| }
|
|
|
| TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates & aValue) const
|
| {
|
| auto line = aValue.mLine;
|
| auto column = aValue.mColumn;
|
|
|
| if (line >= (int)mLines.size())
|
| {
|
| if (mLines.empty())
|
| {
|
| line = 0;
|
| column = 0;
|
| }
|
| else
|
| {
|
| line = (int)mLines.size() - 1;
|
| column = (int)mLines[line].size();
|
| }
|
| }
|
| else
|
| {
|
| column = mLines.empty() ? 0 : std::min((int)mLines[line].size(), aValue.mColumn);
|
| }
|
|
|
| return Coordinates(line, column);
|
| }
|
|
|
| void TextEditor::Advance(Coordinates & aCoordinates) const
|
| {
|
| if (aCoordinates.mLine < (int)mLines.size())
|
| {
|
| auto& line = mLines[aCoordinates.mLine];
|
|
|
| if (aCoordinates.mColumn + 1 < (int)line.size())
|
| ++aCoordinates.mColumn;
|
| else
|
| {
|
| ++aCoordinates.mLine;
|
| aCoordinates.mColumn = 0;
|
| }
|
| }
|
| }
|
|
|
| void TextEditor::DeleteRange(const Coordinates & aStart, const Coordinates & aEnd)
|
| {
|
| assert(aEnd >= aStart);
|
| assert(!mReadOnly);
|
|
|
| if (aEnd == aStart)
|
| return;
|
|
|
| if (aStart.mLine == aEnd.mLine)
|
| {
|
| auto& line = mLines[aStart.mLine];
|
| if (aEnd.mColumn >= (int)line.size())
|
| line.erase(line.begin() + aStart.mColumn, line.end());
|
| else
|
| line.erase(line.begin() + aStart.mColumn, line.begin() + aEnd.mColumn);
|
| }
|
| else
|
| {
|
| auto& firstLine = mLines[aStart.mLine];
|
| auto& lastLine = mLines[aEnd.mLine];
|
|
|
| firstLine.erase(firstLine.begin() + aStart.mColumn, firstLine.end());
|
| lastLine.erase(lastLine.begin(), lastLine.begin() + aEnd.mColumn);
|
|
|
| if (aStart.mLine < aEnd.mLine)
|
| firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end());
|
|
|
| if (aStart.mLine < aEnd.mLine)
|
| RemoveLine(aStart.mLine + 1, aEnd.mLine + 1);
|
| }
|
|
|
| mTextChanged = true;
|
| }
|
|
|
| int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char * aValue)
|
| {
|
| assert(!mReadOnly);
|
|
|
| int totalLines = 0;
|
| auto chr = *aValue;
|
| while (chr != '\0')
|
| {
|
| assert(!mLines.empty());
|
|
|
| if (chr == '\r')
|
| {
|
| // skip
|
| }
|
| else if (chr == '\n')
|
| {
|
| if (aWhere.mColumn < (int)mLines[aWhere.mLine].size())
|
| {
|
| auto& newLine = InsertLine(aWhere.mLine + 1);
|
| auto& line = mLines[aWhere.mLine];
|
| newLine.insert(newLine.begin(), line.begin() + aWhere.mColumn, line.end());
|
| line.erase(line.begin() + aWhere.mColumn, line.end());
|
| }
|
| else
|
| {
|
| InsertLine(aWhere.mLine + 1);
|
| }
|
| ++aWhere.mLine;
|
| aWhere.mColumn = 0;
|
| ++totalLines;
|
| }
|
| else
|
| {
|
| auto& line = mLines[aWhere.mLine];
|
| line.insert(line.begin() + aWhere.mColumn, Glyph(chr, PaletteIndex::Default));
|
| ++aWhere.mColumn;
|
| }
|
| chr = *(++aValue);
|
|
|
| mTextChanged = true;
|
| }
|
|
|
| return totalLines;
|
| }
|
|
|
| void TextEditor::AddUndo(UndoRecord& aValue)
|
| {
|
| assert(!mReadOnly);
|
|
|
| mUndoBuffer.resize(mUndoIndex + 1);
|
| mUndoBuffer.back() = aValue;
|
| ++mUndoIndex;
|
| }
|
|
|
| TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition) const
|
| {
|
| ImVec2 origin = ImGui::GetCursorScreenPos();
|
| ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y);
|
|
|
| int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y));
|
|
|
| /*
|
| Compute columnCoord according to text size
|
| */
|
|
|
| int columnCoord = 0;
|
| float columnWidth = 0.0f;
|
| std::string cumulatedString = "";
|
| float cumulatedStringWidth[2] = {0.0f, 0.0f}; //( [0] is the lastest, [1] is the previous. I use that trick to check where cursor is exactly (important for tabs)
|
|
|
| if (lineNo >= 0 && lineNo < (int)mLines.size())
|
| {
|
| auto& line = mLines.at(lineNo);
|
|
|
| // First we find the hovered column coord.
|
| while ( mTextStart + cumulatedStringWidth[0] < local.x &&
|
| columnCoord < line.size())
|
| {
|
| cumulatedStringWidth[1] = cumulatedStringWidth[0];
|
| cumulatedString += line[columnCoord].mChar;
|
| cumulatedStringWidth[0] = ImGui::CalcTextSize(cumulatedString.c_str()).x ;
|
| columnWidth = (cumulatedStringWidth[0] - cumulatedStringWidth[1]);
|
| columnCoord++;
|
| }
|
|
|
| // Then we reduce by 1 column coord if cursor is on the left side of the hovered column.
|
| if( mTextStart + cumulatedStringWidth[0] - columnWidth / 2.0f > local.x)
|
| columnCoord = std::max(0, --columnCoord);
|
| }
|
|
|
| return SanitizeCoordinates(Coordinates(lineNo, columnCoord));
|
| }
|
|
|
| TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates & aFrom) const
|
| {
|
| Coordinates at = aFrom;
|
| if (at.mLine >= (int)mLines.size())
|
| return at;
|
|
|
| auto& line = mLines[at.mLine];
|
|
|
| if (at.mColumn >= (int)line.size())
|
| return at;
|
|
|
| auto cstart = (PaletteIndex)line[at.mColumn].mColorIndex;
|
| while (at.mColumn > 0)
|
| {
|
| if (cstart != (PaletteIndex)line[at.mColumn - 1].mColorIndex)
|
| break;
|
| --at.mColumn;
|
| }
|
| return at;
|
| }
|
|
|
| TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates & aFrom) const
|
| {
|
| Coordinates at = aFrom;
|
| if (at.mLine >= (int)mLines.size())
|
| return at;
|
|
|
| auto& line = mLines[at.mLine];
|
|
|
| if (at.mColumn >= (int)line.size())
|
| return at;
|
|
|
| auto cstart = (PaletteIndex)line[at.mColumn].mColorIndex;
|
| while (at.mColumn < (int)line.size())
|
| {
|
| if (cstart != (PaletteIndex)line[at.mColumn].mColorIndex)
|
| break;
|
| ++at.mColumn;
|
| }
|
| return at;
|
| }
|
|
|
| bool TextEditor::IsOnWordBoundary(const Coordinates & aAt) const
|
| {
|
| if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0)
|
| return true;
|
|
|
| auto& line = mLines[aAt.mLine];
|
| if (aAt.mColumn >= (int)line.size())
|
| return true;
|
|
|
| return line[aAt.mColumn].mColorIndex != line[aAt.mColumn - 1].mColorIndex;
|
| }
|
|
|
| void TextEditor::RemoveLine(int aStart, int aEnd)
|
| {
|
| assert(!mReadOnly);
|
| assert(aEnd >= aStart);
|
| assert(mLines.size() > aEnd - aStart);
|
|
|
| ErrorMarkers etmp;
|
| for (auto& i : mErrorMarkers)
|
| {
|
| ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second);
|
| if (e.first >= aStart && e.first <= aEnd)
|
| continue;
|
| etmp.insert(e);
|
| }
|
| mErrorMarkers = std::move(etmp);
|
|
|
| Breakpoints btmp;
|
| for (auto i : mBreakpoints)
|
| {
|
| if (i >= aStart && i <= aEnd)
|
| continue;
|
| btmp.insert(i >= aStart ? i - 1 : i);
|
| }
|
| mBreakpoints = std::move(btmp);
|
|
|
| mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd);
|
| assert(!mLines.empty());
|
|
|
| mTextChanged = true;
|
| }
|
|
|
| void TextEditor::RemoveLine(int aIndex)
|
| {
|
| assert(!mReadOnly);
|
| assert(mLines.size() > 1);
|
|
|
| ErrorMarkers etmp;
|
| for (auto& i : mErrorMarkers)
|
| {
|
| ErrorMarkers::value_type e(i.first > aIndex ? i.first - 1 : i.first, i.second);
|
| if (e.first == aIndex)
|
| continue;
|
| etmp.insert(e);
|
| }
|
| mErrorMarkers = std::move(etmp);
|
|
|
| Breakpoints btmp;
|
| for (auto i : mBreakpoints)
|
| {
|
| if (i == aIndex)
|
| continue;
|
| btmp.insert(i >= aIndex ? i - 1 : i);
|
| }
|
| mBreakpoints = std::move(btmp);
|
|
|
| mLines.erase(mLines.begin() + aIndex);
|
| assert(!mLines.empty());
|
|
|
| mTextChanged = true;
|
| }
|
|
|
| TextEditor::Line& TextEditor::InsertLine(int aIndex)
|
| {
|
| assert(!mReadOnly);
|
|
|
| auto& result = *mLines.insert(mLines.begin() + aIndex, Line());
|
|
|
| ErrorMarkers etmp;
|
| for (auto& i : mErrorMarkers)
|
| etmp.insert(ErrorMarkers::value_type(i.first >= aIndex ? i.first + 1 : i.first, i.second));
|
| mErrorMarkers = std::move(etmp);
|
|
|
| Breakpoints btmp;
|
| for (auto i : mBreakpoints)
|
| btmp.insert(i >= aIndex ? i + 1 : i);
|
| mBreakpoints = std::move(btmp);
|
|
|
| return result;
|
| }
|
|
|
| std::string TextEditor::GetWordUnderCursor() const
|
| {
|
| auto c = GetCursorPosition();
|
| return GetWordAt(c);
|
| }
|
|
|
| std::string TextEditor::GetWordAt(const Coordinates & aCoords) const
|
| {
|
| auto start = FindWordStart(aCoords);
|
| auto end = FindWordEnd(aCoords);
|
|
|
| std::string r;
|
|
|
| for (auto it = start; it < end; Advance(it))
|
| r.push_back(mLines[it.mLine][it.mColumn].mChar);
|
|
|
| return r;
|
| }
|
|
|
| void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder)
|
| {
|
| mWithinRender = true;
|
| mTextChanged = false;
|
| mCursorPositionChanged = false;
|
|
|
| ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background]));
|
| ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
|
| ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove);
|
|
|
| /* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/
|
| const float fontSize = ImGui::CalcTextSize("#").x;
|
| mCharAdvance = ImVec2(fontSize , ImGui::GetTextLineHeightWithSpacing() * mLineSpacing);
|
|
|
| /*
|
| Keyboard inputs
|
| */
|
|
|
| ImGui::PushAllowKeyboardFocus(true);
|
| ImGuiIO& io = ImGui::GetIO();
|
| auto shift = io.KeyShift;
|
| auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
|
| auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
|
|
|
| if (ImGui::IsWindowFocused())
|
| {
|
| if (ImGui::IsWindowHovered())
|
| ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput);
|
| //ImGui::CaptureKeyboardFromApp(true);
|
|
|
| io.WantCaptureKeyboard = true;
|
| io.WantTextInput = true;
|
|
|
| if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed('Z'))
|
| Undo();
|
| else if (!IsReadOnly() && !ctrl && !shift && alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace)))
|
| Undo();
|
| else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed('Y'))
|
| Redo();
|
| else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)))
|
| MoveUp(1, shift);
|
| else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)))
|
| MoveDown(1, shift);
|
| else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)))
|
| MoveLeft(1, shift, ctrl);
|
| else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)))
|
| MoveRight(1, shift, ctrl);
|
| else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp)))
|
| MoveUp(GetPageSize() - 4, shift);
|
| else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown)))
|
| MoveDown(GetPageSize() - 4, shift);
|
| else if (!alt && ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
|
| MoveTop(shift);
|
| else if (ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
|
| MoveBottom(shift);
|
| else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
|
| MoveHome(shift);
|
| else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
|
| MoveEnd(shift);
|
| else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)))
|
| Delete();
|
| else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace)))
|
| BackSpace();
|
| else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(45))
|
| mOverwrite ^= true;
|
| else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(45))
|
| Copy();
|
| else if (ctrl && !shift && !alt && ImGui::IsKeyPressed('C'))
|
| Copy();
|
| else if (!IsReadOnly() && !ctrl && shift && !alt && ImGui::IsKeyPressed(45))
|
| Paste();
|
| else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed('V'))
|
| Paste();
|
| else if (ctrl && !shift && !alt && ImGui::IsKeyPressed('X'))
|
| Cut();
|
| else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)))
|
| Cut();
|
| else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A)))
|
| SelectAll();
|
| else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter)) )
|
| EnterCharacter('\n');
|
| else if (!IsReadOnly() && !ctrl && !alt)
|
| {
|
| for (size_t i = 0; i < sizeof(io.InputCharacters) / sizeof(io.InputCharacters[0]); i++)
|
| {
|
| auto c = (unsigned char)io.InputCharacters[i];
|
| if (c != 0)
|
| {
|
| if (isprint(c) || isspace(c))
|
| {
|
| EnterCharacter((char)c);
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
| /*
|
| Mouse inputs
|
| */
|
|
|
| if (ImGui::IsWindowHovered())
|
| {
|
| static float lastClick = -1.0f;
|
| if (!shift && !alt)
|
| {
|
| auto click = ImGui::IsMouseClicked(0);
|
| auto doubleClick = ImGui::IsMouseDoubleClicked(0);
|
| auto t = ImGui::GetTime();
|
| auto tripleClick = click && !doubleClick && t - lastClick < io.MouseDoubleClickTime;
|
|
|
| /*
|
| Left mouse button triple click
|
| */
|
|
|
| if (tripleClick)
|
| {
|
| if (!ctrl)
|
| {
|
| mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = SanitizeCoordinates(ScreenPosToCoordinates(ImGui::GetMousePos()));
|
| mSelectionMode = SelectionMode::Line;
|
| SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
|
| }
|
|
|
| lastClick = -1.0f;
|
| }
|
|
|
| /*
|
| Left mouse button double click
|
| */
|
|
|
| else if (doubleClick)
|
| {
|
| if (!ctrl)
|
| {
|
| mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = SanitizeCoordinates(ScreenPosToCoordinates(ImGui::GetMousePos()));
|
| if (mSelectionMode == SelectionMode::Line)
|
| mSelectionMode = SelectionMode::Normal;
|
| else
|
| mSelectionMode = SelectionMode::Word;
|
| SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
|
| }
|
|
|
| lastClick = (float)ImGui::GetTime();
|
| }
|
|
|
| /*
|
| Left mouse button click
|
| */
|
|
|
| else if (click)
|
| {
|
| mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = SanitizeCoordinates(ScreenPosToCoordinates(ImGui::GetMousePos()));
|
| if (ctrl)
|
| mSelectionMode = SelectionMode::Word;
|
| else
|
| mSelectionMode = SelectionMode::Normal;
|
| SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
|
|
|
| lastClick = (float)ImGui::GetTime();
|
| }
|
| // Mouse left button dragging (=> update selection)
|
| else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0))
|
| {
|
| io.WantCaptureMouse = true;
|
| mState.mCursorPosition = mInteractiveEnd = SanitizeCoordinates(ScreenPosToCoordinates(ImGui::GetMousePos()));
|
| SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
|
| }
|
| }
|
| }
|
|
|
| ColorizeInternal();
|
|
|
| static std::string buffer;
|
| auto contentSize = ImGui::GetWindowContentRegionMax();
|
| auto drawList = ImGui::GetWindowDrawList();
|
| float longest(mTextStart);
|
|
|
| ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos();
|
| auto scrollX = ImGui::GetScrollX();
|
| auto scrollY = ImGui::GetScrollY();
|
|
|
| auto lineNo = (int)floor(scrollY / mCharAdvance.y);
|
| auto globalLineMax = (int)mLines.size();
|
| auto lineMax = std::max(0, std::min((int)mLines.size() - 1, lineNo + (int)floor((scrollY + contentSize.y) / mCharAdvance.y)));
|
|
|
| // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width
|
| char buf[16];
|
| snprintf(buf, 16, " %d ", globalLineMax);
|
| mTextStart = ImGui::CalcTextSize(buf).x + mLeftMargin;
|
|
|
| if (!mLines.empty())
|
| {
|
| auto fontScale = ImGui::GetFontSize() / ImGui::GetFont()->FontSize;
|
| float spaceSize = ImGui::CalcTextSize(" ").x + 1.0f * fontScale;
|
|
|
| while (lineNo <= lineMax)
|
| {
|
| ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y);
|
| ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y);
|
|
|
| auto& line = mLines[lineNo];
|
| longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, (int) line.size())), longest);
|
| auto columnNo = 0;
|
| Coordinates lineStartCoord(lineNo, 0);
|
| Coordinates lineEndCoord(lineNo, (int)line.size());
|
|
|
| /*
|
| Draw Selected area
|
| */
|
|
|
| float sstart = -1.0f;
|
| float ssend = -1.0f;
|
|
|
| assert(mState.mSelectionStart <= mState.mSelectionEnd);
|
| if (mState.mSelectionStart <= lineEndCoord)
|
| sstart = mState.mSelectionStart > lineStartCoord ? TextDistanceToLineStart(mState.mSelectionStart) : 0.0f;
|
| if (mState.mSelectionEnd > lineStartCoord)
|
| ssend = TextDistanceToLineStart(mState.mSelectionEnd < lineEndCoord ? mState.mSelectionEnd : lineEndCoord);
|
|
|
| if (mState.mSelectionEnd.mLine > lineNo)
|
| ssend += mCharAdvance.x;
|
|
|
| if (sstart != -1 && ssend != -1 && sstart < ssend)
|
| {
|
| ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, lineStartScreenPos.y);
|
| ImVec2 vend(lineStartScreenPos.x + mTextStart + ssend, lineStartScreenPos.y + mCharAdvance.y);
|
| drawList->AddRectFilled(vstart, vend, mPalette[(int)PaletteIndex::Selection]);
|
| }
|
|
|
| /*
|
| Draw break point
|
| */
|
|
|
| auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y);
|
|
|
| if (mBreakpoints.count(lineNo + 1) != 0)
|
| {
|
| auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y);
|
| drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::Breakpoint]);
|
| }
|
|
|
| /*
|
| Draw error marker
|
| */
|
|
|
| auto errorIt = mErrorMarkers.find(lineNo + 1);
|
| if (errorIt != mErrorMarkers.end())
|
| {
|
| auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y);
|
| drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::ErrorMarker]);
|
|
|
| if (ImGui::IsMouseHoveringRect(lineStartScreenPos, end))
|
| {
|
| ImGui::BeginTooltip();
|
| ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f));
|
| ImGui::Text("Error at line %d:", errorIt->first);
|
| ImGui::PopStyleColor();
|
| ImGui::Separator();
|
| ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.2f, 1.0f));
|
| ImGui::Text("%s", errorIt->second.c_str());
|
| ImGui::PopStyleColor();
|
| ImGui::EndTooltip();
|
| }
|
| }
|
|
|
| /*
|
| Draw line number (right aligned)
|
| */
|
|
|
| snprintf(buf, 16, "%d ", lineNo + 1);
|
| auto lineNoWidth = ImGui::CalcTextSize(buf).x;
|
| drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], buf);
|
|
|
| /*
|
| Highlight the current line (where the cursor is).
|
| */
|
|
|
| if (mState.mCursorPosition.mLine == lineNo)
|
| {
|
| auto focused = ImGui::IsWindowFocused();
|
|
|
| if (!HasSelection())
|
| {
|
| auto end = ImVec2(start.x + contentSize.x + scrollX, start.y + mCharAdvance.y);
|
| drawList->AddRectFilled(start, end, mPalette[(int)(focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]);
|
| drawList->AddRect(start, end, mPalette[(int)PaletteIndex::CurrentLineEdge], 1.0f);
|
| }
|
|
|
| float cx = TextDistanceToLineStart(mState.mCursorPosition);
|
|
|
| if (focused)
|
| {
|
| static auto timeStart = std::chrono::system_clock::now();
|
| auto timeEnd = std::chrono::system_clock::now();
|
| auto diff = timeEnd - timeStart;
|
| auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(diff).count();
|
| if (elapsed > 400)
|
| {
|
| ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y);
|
| ImVec2 cend(textScreenPos.x + cx + (mOverwrite ? mCharAdvance.x : 1.0f), lineStartScreenPos.y + mCharAdvance.y);
|
| drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]);
|
| if (elapsed > 800)
|
| timeStart = timeEnd;
|
| }
|
| }
|
| }
|
|
|
| /*
|
| Draw Text
|
| */
|
|
|
| auto prevColor = line.empty() ? PaletteIndex::Default : (line[0].mMultiLineComment ? PaletteIndex::MultiLineComment : line[0].mColorIndex);
|
| ImVec2 bufferOffset;
|
|
|
| for (auto& glyph : line)
|
| {
|
| auto color = glyph.mMultiLineComment ? PaletteIndex::MultiLineComment : glyph.mColorIndex;
|
|
|
| if ((color != prevColor || glyph.mChar == '\t') && !buffer.empty())
|
| {
|
| const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y);
|
| drawList->AddText(newOffset, mPalette[(uint8_t)prevColor], buffer.c_str());
|
| auto textSize = ImGui::CalcTextSize(buffer.c_str());
|
| bufferOffset.x += textSize.x + 1.0f * fontScale;
|
| buffer.clear();
|
| }
|
| prevColor = color;
|
|
|
| if (glyph.mChar == '\t')
|
| bufferOffset.x = (1.0f * fontScale + std::floor((1.0f + bufferOffset.x)) / (float(mTabSize) * spaceSize)) * (float(mTabSize) * spaceSize);
|
| else
|
| AppendBuffer(buffer, glyph.mChar, 0);
|
| ++columnNo;
|
| }
|
|
|
| if (!buffer.empty())
|
| {
|
| const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y);
|
| drawList->AddText(newOffset, mPalette[(uint8_t)prevColor], buffer.c_str());
|
| buffer.clear();
|
| }
|
|
|
| ++lineNo;
|
| }
|
|
|
| if (ImGui::IsMousePosValid())
|
| {
|
| auto id = GetWordAt(ScreenPosToCoordinates(ImGui::GetMousePos()));
|
| if (!id.empty())
|
| {
|
| auto it = mLanguageDefinition.mIdentifiers.find(id);
|
| if (it != mLanguageDefinition.mIdentifiers.end())
|
| {
|
| ImGui::BeginTooltip();
|
| ImGui::TextUnformatted(it->second.mDeclaration.c_str());
|
| ImGui::EndTooltip();
|
| }
|
| else
|
| {
|
| auto pi = mLanguageDefinition.mPreprocIdentifiers.find(id);
|
| if (pi != mLanguageDefinition.mPreprocIdentifiers.end())
|
| {
|
| ImGui::BeginTooltip();
|
| ImGui::TextUnformatted(pi->second.mDeclaration.c_str());
|
| ImGui::EndTooltip();
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
| ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y));
|
|
|
| if (mScrollToCursor)
|
| {
|
| EnsureCursorVisible();
|
| ImGui::SetWindowFocus();
|
| mScrollToCursor = false;
|
| }
|
|
|
| ImGui::PopAllowKeyboardFocus();
|
| ImGui::EndChild();
|
| ImGui::PopStyleVar();
|
| ImGui::PopStyleColor();
|
|
|
| mWithinRender = false;
|
| }
|
|
|
| void TextEditor::SetText(const std::string & aText)
|
| {
|
| mLines.clear();
|
| mLines.push_back(Line());
|
| for (auto chr : aText)
|
| {
|
| if (chr == '\r')
|
| {
|
| // ignore the carriage return character
|
| }
|
| else if (chr == '\n')
|
| mLines.push_back(Line());
|
| else
|
| {
|
| mLines.back().push_back(Glyph(chr, PaletteIndex::Default));
|
| }
|
|
|
| mTextChanged = true;
|
| }
|
|
|
| mUndoBuffer.clear();
|
|
|
| Colorize();
|
| }
|
|
|
| void TextEditor::EnterCharacter(Char aChar)
|
| {
|
| assert(!mReadOnly);
|
|
|
| UndoRecord u;
|
|
|
| u.mBefore = mState;
|
|
|
| if (HasSelection())
|
| {
|
| u.mRemoved = GetSelectedText();
|
| u.mRemovedStart = mState.mSelectionStart;
|
| u.mRemovedEnd = mState.mSelectionEnd;
|
| DeleteSelection();
|
| }
|
|
|
| auto coord = GetActualCursorCoordinates();
|
| u.mAddedStart = coord;
|
|
|
| assert(!mLines.empty());
|
|
|
| if (aChar == '\n')
|
| {
|
| InsertLine(coord.mLine + 1);
|
| auto& line = mLines[coord.mLine];
|
| auto& newLine = mLines[coord.mLine + 1]; |
| |
| if (mLanguageDefinition.mAutoIndentation) |
| {
|
| for (size_t it = 0; it < line.size() && isblank(line[it].mChar); ++it)
|
| newLine.push_back(line[it]); |
| } |
|
|
| const size_t whitespaceSize = newLine.size();
|
| newLine.insert(newLine.end(), line.begin() + coord.mColumn, line.end());
|
| line.erase(line.begin() + coord.mColumn, line.begin() + line.size());
|
| SetCursorPosition(Coordinates(coord.mLine + 1, (int)whitespaceSize));
|
| }
|
| else
|
| {
|
| auto& line = mLines[coord.mLine];
|
| if (mOverwrite && (int)line.size() > coord.mColumn)
|
| line[coord.mColumn] = Glyph(aChar, PaletteIndex::Default);
|
| else
|
| line.insert(line.begin() + coord.mColumn, Glyph(aChar, PaletteIndex::Default));
|
| SetCursorPosition(Coordinates(coord.mLine, coord.mColumn + 1));
|
| }
|
|
|
| mTextChanged = true;
|
|
|
| u.mAdded = aChar;
|
| u.mAddedEnd = GetActualCursorCoordinates();
|
| u.mAfter = mState;
|
|
|
| AddUndo(u);
|
|
|
| Colorize(coord.mLine - 1, 3);
|
| EnsureCursorVisible();
|
| }
|
|
|
| void TextEditor::SetReadOnly(bool aValue)
|
| {
|
| mReadOnly = aValue;
|
| }
|
|
|
| void TextEditor::SetCursorPosition(const Coordinates & aPosition)
|
| {
|
| if (mState.mCursorPosition != aPosition)
|
| {
|
| mState.mCursorPosition = aPosition;
|
| mCursorPositionChanged = true;
|
| EnsureCursorVisible();
|
| }
|
| }
|
|
|
| void TextEditor::SetSelectionStart(const Coordinates & aPosition)
|
| {
|
| mState.mSelectionStart = SanitizeCoordinates(aPosition);
|
| if (mState.mSelectionStart > mState.mSelectionEnd)
|
| std::swap(mState.mSelectionStart, mState.mSelectionEnd);
|
| }
|
|
|
| void TextEditor::SetSelectionEnd(const Coordinates & aPosition)
|
| {
|
| mState.mSelectionEnd = SanitizeCoordinates(aPosition);
|
| if (mState.mSelectionStart > mState.mSelectionEnd)
|
| std::swap(mState.mSelectionStart, mState.mSelectionEnd);
|
| }
|
|
|
| void TextEditor::SetSelection(const Coordinates & aStart, const Coordinates & aEnd, SelectionMode aMode)
|
| {
|
| auto oldSelStart = mState.mSelectionStart;
|
| auto oldSelEnd = mState.mSelectionEnd;
|
|
|
| mState.mSelectionStart = SanitizeCoordinates(aStart);
|
| mState.mSelectionEnd = SanitizeCoordinates(aEnd);
|
| if (aStart > aEnd)
|
| std::swap(mState.mSelectionStart, mState.mSelectionEnd);
|
|
|
| switch (aMode)
|
| {
|
| case TextEditor::SelectionMode::Normal:
|
| break;
|
| case TextEditor::SelectionMode::Word:
|
| {
|
| mState.mSelectionStart = FindWordStart(mState.mSelectionStart);
|
| if (!IsOnWordBoundary(mState.mSelectionEnd))
|
| mState.mSelectionEnd = FindWordEnd(FindWordStart(mState.mSelectionEnd));
|
| break;
|
| }
|
| case TextEditor::SelectionMode::Line:
|
| {
|
| const auto lineNo = mState.mSelectionEnd.mLine;
|
| const auto lineSize = lineNo < mLines.size() ? mLines[lineNo].size() : 0;
|
| mState.mSelectionStart = Coordinates(mState.mSelectionStart.mLine, 0);
|
| mState.mSelectionEnd = Coordinates(lineNo, (int) lineSize);
|
| break;
|
| }
|
| default:
|
| break;
|
| }
|
|
|
| if (mState.mSelectionStart != oldSelStart ||
|
| mState.mSelectionEnd != oldSelEnd)
|
| mCursorPositionChanged = true;
|
| }
|
|
|
| void TextEditor::InsertText(const std::string & aValue)
|
| {
|
| InsertText(aValue.c_str());
|
| }
|
|
|
| void TextEditor::InsertText(const char * aValue)
|
| {
|
| if (aValue == nullptr)
|
| return;
|
|
|
| auto pos = GetActualCursorCoordinates();
|
| auto start = std::min(pos, mState.mSelectionStart);
|
| int totalLines = pos.mLine - start.mLine;
|
|
|
| totalLines += InsertTextAt(pos, aValue);
|
|
|
| SetSelection(pos, pos);
|
| SetCursorPosition(pos);
|
| Colorize(start.mLine - 1, totalLines + 2);
|
| }
|
|
|
| void TextEditor::DeleteSelection()
|
| {
|
| assert(mState.mSelectionEnd >= mState.mSelectionStart);
|
|
|
| if (mState.mSelectionEnd == mState.mSelectionStart)
|
| return;
|
|
|
| DeleteRange(mState.mSelectionStart, mState.mSelectionEnd);
|
|
|
| SetSelection(mState.mSelectionStart, mState.mSelectionStart);
|
| SetCursorPosition(mState.mSelectionStart);
|
| Colorize(mState.mSelectionStart.mLine, 1);
|
| }
|
|
|
| void TextEditor::MoveUp(int aAmount, bool aSelect)
|
| {
|
| auto oldPos = mState.mCursorPosition;
|
| mState.mCursorPosition.mLine = std::max(0, mState.mCursorPosition.mLine - aAmount);
|
| if (oldPos != mState.mCursorPosition)
|
| {
|
| if (aSelect)
|
| {
|
| if (oldPos == mInteractiveStart)
|
| mInteractiveStart = mState.mCursorPosition;
|
| else if (oldPos == mInteractiveEnd)
|
| mInteractiveEnd = mState.mCursorPosition;
|
| else
|
| {
|
| mInteractiveStart = mState.mCursorPosition;
|
| mInteractiveEnd = oldPos;
|
| }
|
| }
|
| else
|
| mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
|
| SetSelection(mInteractiveStart, mInteractiveEnd);
|
|
|
| EnsureCursorVisible();
|
| }
|
| }
|
|
|
| void TextEditor::MoveDown(int aAmount, bool aSelect)
|
| {
|
| assert(mState.mCursorPosition.mColumn >= 0);
|
| auto oldPos = mState.mCursorPosition;
|
| mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + aAmount));
|
|
|
| if (mState.mCursorPosition != oldPos)
|
| {
|
| if (aSelect)
|
| {
|
| if (oldPos == mInteractiveEnd)
|
| mInteractiveEnd = mState.mCursorPosition;
|
| else if (oldPos == mInteractiveStart)
|
| mInteractiveStart = mState.mCursorPosition;
|
| else
|
| {
|
| mInteractiveStart = oldPos;
|
| mInteractiveEnd = mState.mCursorPosition;
|
| }
|
| }
|
| else
|
| mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
|
| SetSelection(mInteractiveStart, mInteractiveEnd);
|
|
|
| EnsureCursorVisible();
|
| }
|
| }
|
|
|
| void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode)
|
| {
|
| if (mLines.empty())
|
| return;
|
|
|
| auto oldPos = mState.mCursorPosition;
|
| mState.mCursorPosition = GetActualCursorCoordinates();
|
|
|
| while (aAmount-- > 0)
|
| {
|
| if (mState.mCursorPosition.mColumn == 0)
|
| {
|
| if (mState.mCursorPosition.mLine > 0)
|
| {
|
| --mState.mCursorPosition.mLine;
|
| mState.mCursorPosition.mColumn = (int)mLines[mState.mCursorPosition.mLine].size();
|
| }
|
| }
|
| else
|
| {
|
| mState.mCursorPosition.mColumn = std::max(0, mState.mCursorPosition.mColumn - 1);
|
| if (aWordMode)
|
| mState.mCursorPosition = FindWordStart(mState.mCursorPosition);
|
| }
|
| }
|
|
|
| assert(mState.mCursorPosition.mColumn >= 0);
|
| if (aSelect)
|
| {
|
| if (oldPos == mInteractiveStart)
|
| mInteractiveStart = mState.mCursorPosition;
|
| else if (oldPos == mInteractiveEnd)
|
| mInteractiveEnd = mState.mCursorPosition;
|
| else
|
| {
|
| mInteractiveStart = mState.mCursorPosition;
|
| mInteractiveEnd = oldPos;
|
| }
|
| }
|
| else
|
| mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
|
| SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal);
|
|
|
| EnsureCursorVisible();
|
| }
|
|
|
| void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode)
|
| {
|
| auto oldPos = mState.mCursorPosition;
|
|
|
| if (mLines.empty())
|
| return;
|
|
|
| while (aAmount-- > 0)
|
| {
|
| auto& line = mLines[mState.mCursorPosition.mLine];
|
| if (mState.mCursorPosition.mColumn >= (int)line.size())
|
| {
|
| if (mState.mCursorPosition.mLine < (int)mLines.size() - 1)
|
| {
|
| mState.mCursorPosition.mLine = std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + 1));
|
| mState.mCursorPosition.mColumn = 0;
|
| }
|
| }
|
| else
|
| {
|
| mState.mCursorPosition.mColumn = std::max(0, std::min((int)line.size(), mState.mCursorPosition.mColumn + 1));
|
| if (aWordMode)
|
| mState.mCursorPosition = FindWordEnd(mState.mCursorPosition);
|
| }
|
| }
|
|
|
| if (aSelect)
|
| {
|
| if (oldPos == mInteractiveEnd)
|
| mInteractiveEnd = SanitizeCoordinates(mState.mCursorPosition);
|
| else if (oldPos == mInteractiveStart)
|
| mInteractiveStart = mState.mCursorPosition;
|
| else
|
| {
|
| mInteractiveStart = oldPos;
|
| mInteractiveEnd = mState.mCursorPosition;
|
| }
|
| }
|
| else
|
| mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
|
| SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal);
|
|
|
| EnsureCursorVisible();
|
| }
|
|
|
| void TextEditor::MoveTop(bool aSelect)
|
| {
|
| auto oldPos = mState.mCursorPosition;
|
| SetCursorPosition(Coordinates(0, 0));
|
|
|
| if (mState.mCursorPosition != oldPos)
|
| {
|
| if (aSelect)
|
| {
|
| mInteractiveEnd = oldPos;
|
| mInteractiveStart = mState.mCursorPosition;
|
| }
|
| else
|
| mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
|
| SetSelection(mInteractiveStart, mInteractiveEnd);
|
| }
|
| }
|
|
|
| void TextEditor::TextEditor::MoveBottom(bool aSelect)
|
| {
|
| auto oldPos = GetCursorPosition();
|
| auto newPos = Coordinates((int)mLines.size() - 1, 0);
|
| SetCursorPosition(newPos);
|
| if (aSelect)
|
| {
|
| mInteractiveStart = oldPos;
|
| mInteractiveEnd = newPos;
|
| }
|
| else
|
| mInteractiveStart = mInteractiveEnd = newPos;
|
| SetSelection(mInteractiveStart, mInteractiveEnd);
|
| }
|
|
|
| void TextEditor::MoveHome(bool aSelect)
|
| {
|
| auto oldPos = mState.mCursorPosition;
|
| SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, 0));
|
|
|
| if (mState.mCursorPosition != oldPos)
|
| {
|
| if (aSelect)
|
| {
|
| if (oldPos == mInteractiveStart)
|
| mInteractiveStart = mState.mCursorPosition;
|
| else if (oldPos == mInteractiveEnd)
|
| mInteractiveEnd = mState.mCursorPosition;
|
| else
|
| {
|
| mInteractiveStart = mState.mCursorPosition;
|
| mInteractiveEnd = oldPos;
|
| }
|
| }
|
| else
|
| mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
|
| SetSelection(mInteractiveStart, mInteractiveEnd);
|
| }
|
| }
|
|
|
| void TextEditor::MoveEnd(bool aSelect)
|
| {
|
| auto oldPos = mState.mCursorPosition;
|
| SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, (int)mLines[oldPos.mLine].size()));
|
|
|
| if (mState.mCursorPosition != oldPos)
|
| {
|
| if (aSelect)
|
| {
|
| if (oldPos == mInteractiveEnd)
|
| mInteractiveEnd = mState.mCursorPosition;
|
| else if (oldPos == mInteractiveStart)
|
| mInteractiveStart = mState.mCursorPosition;
|
| else
|
| {
|
| mInteractiveStart = oldPos;
|
| mInteractiveEnd = mState.mCursorPosition;
|
| }
|
| }
|
| else
|
| mInteractiveStart = mInteractiveEnd = mState.mCursorPosition;
|
| SetSelection(mInteractiveStart, mInteractiveEnd);
|
| }
|
| }
|
|
|
| void TextEditor::Delete()
|
| {
|
| assert(!mReadOnly);
|
|
|
| if (mLines.empty())
|
| return;
|
|
|
| UndoRecord u;
|
| u.mBefore = mState;
|
|
|
| if (HasSelection())
|
| {
|
| u.mRemoved = GetSelectedText();
|
| u.mRemovedStart = mState.mSelectionStart;
|
| u.mRemovedEnd = mState.mSelectionEnd;
|
|
|
| DeleteSelection();
|
| }
|
| else
|
| {
|
| auto pos = GetActualCursorCoordinates();
|
| SetCursorPosition(pos);
|
| auto& line = mLines[pos.mLine];
|
|
|
| if (pos.mColumn == (int)line.size())
|
| {
|
| if (pos.mLine == (int)mLines.size() - 1)
|
| return;
|
|
|
| u.mRemoved = '\n';
|
| u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
|
| Advance(u.mRemovedEnd);
|
|
|
| auto& nextLine = mLines[pos.mLine + 1];
|
| line.insert(line.end(), nextLine.begin(), nextLine.end());
|
| RemoveLine(pos.mLine + 1);
|
| }
|
| else
|
| {
|
| u.mRemoved = line[pos.mColumn].mChar;
|
| u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
|
| u.mRemovedEnd.mColumn++;
|
|
|
| line.erase(line.begin() + pos.mColumn);
|
| }
|
|
|
| mTextChanged = true;
|
|
|
| Colorize(pos.mLine, 1);
|
| }
|
|
|
| u.mAfter = mState;
|
| AddUndo(u);
|
| }
|
|
|
| void TextEditor::BackSpace()
|
| {
|
| assert(!mReadOnly);
|
|
|
| if (mLines.empty())
|
| return;
|
|
|
|
|
| UndoRecord u;
|
| u.mBefore = mState;
|
|
|
| if (HasSelection())
|
| {
|
| u.mRemoved = GetSelectedText();
|
| u.mRemovedStart = mState.mSelectionStart;
|
| u.mRemovedEnd = mState.mSelectionEnd;
|
|
|
| DeleteSelection();
|
| }
|
| else
|
| {
|
| auto pos = GetActualCursorCoordinates();
|
| SetCursorPosition(pos);
|
|
|
| if (mState.mCursorPosition.mColumn == 0)
|
| {
|
| if (mState.mCursorPosition.mLine == 0)
|
| return;
|
|
|
| u.mRemoved = '\n';
|
| u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
|
| Advance(u.mRemovedEnd);
|
|
|
| auto& line = mLines[mState.mCursorPosition.mLine];
|
| auto& prevLine = mLines[mState.mCursorPosition.mLine - 1];
|
| auto prevSize = (int)prevLine.size();
|
| prevLine.insert(prevLine.end(), line.begin(), line.end());
|
| RemoveLine(mState.mCursorPosition.mLine);
|
| --mState.mCursorPosition.mLine;
|
| mState.mCursorPosition.mColumn = prevSize;
|
| }
|
| else
|
| {
|
| auto& line = mLines[mState.mCursorPosition.mLine];
|
|
|
| u.mRemoved = line[pos.mColumn - 1].mChar;
|
| u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
|
| --u.mRemovedStart.mColumn;
|
|
|
| --mState.mCursorPosition.mColumn;
|
| if (mState.mCursorPosition.mColumn < (int)line.size())
|
| line.erase(line.begin() + mState.mCursorPosition.mColumn);
|
| }
|
|
|
| mTextChanged = true;
|
|
|
| EnsureCursorVisible();
|
| Colorize(mState.mCursorPosition.mLine, 1);
|
| }
|
|
|
| u.mAfter = mState;
|
| AddUndo(u);
|
| }
|
|
|
| void TextEditor::SelectWordUnderCursor()
|
| {
|
| auto c = GetCursorPosition();
|
| SetSelection(FindWordStart(c), FindWordEnd(c));
|
| }
|
|
|
| void TextEditor::SelectAll()
|
| {
|
| SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), 0));
|
| }
|
|
|
| bool TextEditor::HasSelection() const
|
| {
|
| return mState.mSelectionEnd > mState.mSelectionStart;
|
| }
|
|
|
| void TextEditor::Copy()
|
| {
|
| if (HasSelection())
|
| {
|
| ImGui::SetClipboardText(GetSelectedText().c_str());
|
| }
|
| else
|
| {
|
| if (!mLines.empty())
|
| {
|
| std::string str;
|
| auto& line = mLines[GetActualCursorCoordinates().mLine];
|
| for (auto& g : line)
|
| str.push_back(g.mChar);
|
| ImGui::SetClipboardText(str.c_str());
|
| }
|
| }
|
| }
|
|
|
| void TextEditor::Cut()
|
| {
|
| if (IsReadOnly())
|
| {
|
| Copy();
|
| }
|
| else
|
| {
|
| if (HasSelection())
|
| {
|
| UndoRecord u;
|
| u.mBefore = mState;
|
| u.mRemoved = GetSelectedText();
|
| u.mRemovedStart = mState.mSelectionStart;
|
| u.mRemovedEnd = mState.mSelectionEnd;
|
|
|
| Copy();
|
| DeleteSelection();
|
|
|
| u.mAfter = mState;
|
| AddUndo(u);
|
| }
|
| }
|
| }
|
|
|
| void TextEditor::Paste()
|
| {
|
| auto clipText = ImGui::GetClipboardText();
|
| if (clipText != nullptr && strlen(clipText) > 0)
|
| {
|
| UndoRecord u;
|
| u.mBefore = mState;
|
|
|
| if (HasSelection())
|
| {
|
| u.mRemoved = GetSelectedText();
|
| u.mRemovedStart = mState.mSelectionStart;
|
| u.mRemovedEnd = mState.mSelectionEnd;
|
| DeleteSelection();
|
| }
|
|
|
| u.mAdded = clipText;
|
| u.mAddedStart = GetActualCursorCoordinates();
|
|
|
| InsertText(clipText);
|
|
|
| u.mAddedEnd = GetActualCursorCoordinates();
|
| u.mAfter = mState;
|
| AddUndo(u);
|
| }
|
| }
|
|
|
| bool TextEditor::CanUndo() const
|
| {
|
| return mUndoIndex > 0;
|
| }
|
|
|
| bool TextEditor::CanRedo() const
|
| {
|
| return mUndoIndex < (int)mUndoBuffer.size();
|
| }
|
|
|
| void TextEditor::Undo(int aSteps)
|
| {
|
| while (CanUndo() && aSteps-- > 0)
|
| mUndoBuffer[--mUndoIndex].Undo(this);
|
| }
|
|
|
| void TextEditor::Redo(int aSteps)
|
| {
|
| while (CanRedo() && aSteps-- > 0)
|
| mUndoBuffer[mUndoIndex++].Redo(this);
|
| }
|
|
|
| const TextEditor::Palette & TextEditor::GetDarkPalette()
|
| {
|
| static Palette p = { {
|
| 0xffffffff, // None
|
| 0xffd69c56, // Keyword
|
| 0xff00ff00, // Number
|
| 0xff7070e0, // String
|
| 0xff70a0e0, // Char literal
|
| 0xffffffff, // Punctuation
|
| 0xff409090, // Preprocessor
|
| 0xffaaaaaa, // Identifier
|
| 0xff9bc64d, // Known identifier
|
| 0xffc040a0, // Preproc identifier
|
| 0xff206020, // Comment (single line)
|
| 0xff406020, // Comment (multi line)
|
| 0xff101010, // Background
|
| 0xffe0e0e0, // Cursor
|
| 0x80a06020, // Selection
|
| 0x800020ff, // ErrorMarker
|
| 0x40f08000, // Breakpoint
|
| 0xff707000, // Line number
|
| 0x40000000, // Current line fill
|
| 0x40808080, // Current line fill (inactive)
|
| 0x40a0a0a0, // Current line edge
|
| } };
|
| return p;
|
| }
|
|
|
| const TextEditor::Palette & TextEditor::GetLightPalette()
|
| {
|
| static Palette p = { {
|
| 0xff000000, // None
|
| 0xffff0c06, // Keyword
|
| 0xff008000, // Number
|
| 0xff2020a0, // String
|
| 0xff304070, // Char literal
|
| 0xff000000, // Punctuation
|
| 0xff409090, // Preprocessor
|
| 0xff404040, // Identifier
|
| 0xff606010, // Known identifier
|
| 0xffc040a0, // Preproc identifier
|
| 0xff205020, // Comment (single line)
|
| 0xff405020, // Comment (multi line)
|
| 0xffffffff, // Background
|
| 0xff000000, // Cursor
|
| 0x80600000, // Selection
|
| 0xa00010ff, // ErrorMarker
|
| 0x80f08000, // Breakpoint
|
| 0xff505000, // Line number
|
| 0x40000000, // Current line fill
|
| 0x40808080, // Current line fill (inactive)
|
| 0x40000000, // Current line edge
|
| } };
|
| return p;
|
| }
|
|
|
| const TextEditor::Palette & TextEditor::GetRetroBluePalette()
|
| {
|
| static Palette p = { {
|
| 0xff00ffff, // None
|
| 0xffffff00, // Keyword
|
| 0xff00ff00, // Number
|
| 0xff808000, // String
|
| 0xff808000, // Char literal
|
| 0xffffffff, // Punctuation
|
| 0xff008000, // Preprocessor
|
| 0xff00ffff, // Identifier
|
| 0xffffffff, // Known identifier
|
| 0xffff00ff, // Preproc identifier
|
| 0xff808080, // Comment (single line)
|
| 0xff404040, // Comment (multi line)
|
| 0xff800000, // Background
|
| 0xff0080ff, // Cursor
|
| 0x80ffff00, // Selection
|
| 0xa00000ff, // ErrorMarker
|
| 0x80ff8000, // Breakpoint
|
| 0xff808000, // Line number
|
| 0x40000000, // Current line fill
|
| 0x40808080, // Current line fill (inactive)
|
| 0x40000000, // Current line edge
|
| } };
|
| return p;
|
| }
|
|
|
|
|
| std::string TextEditor::GetText() const
|
| {
|
| return GetText(Coordinates(), Coordinates((int)mLines.size(), 0));
|
| }
|
|
|
| std::string TextEditor::GetSelectedText() const
|
| {
|
| return GetText(mState.mSelectionStart, mState.mSelectionEnd);
|
| }
|
|
|
| std::string TextEditor::GetCurrentLineText()const
|
| {
|
| auto lineLength = (int) mLines[mState.mCursorPosition.mLine].size();
|
| return GetText(Coordinates(mState.mCursorPosition.mLine, 0), Coordinates(mState.mCursorPosition.mLine, lineLength));
|
| }
|
|
|
| void TextEditor::ProcessInputs()
|
| {
|
| }
|
|
|
| void TextEditor::Colorize(int aFromLine, int aLines)
|
| {
|
| int toLine = aLines == -1 ? (int)mLines.size() : std::min((int)mLines.size(), aFromLine + aLines);
|
| mColorRangeMin = std::min(mColorRangeMin, aFromLine);
|
| mColorRangeMax = std::max(mColorRangeMax, toLine);
|
| mColorRangeMin = std::max(0, mColorRangeMin);
|
| mColorRangeMax = std::max(mColorRangeMin, mColorRangeMax);
|
| mCheckMultilineComments = true;
|
| }
|
|
|
| void TextEditor::ColorizeRange(int aFromLine, int aToLine)
|
| {
|
| if (mLines.empty() || aFromLine >= aToLine)
|
| return;
|
|
|
| std::string buffer; |
| std::cmatch results; |
| std::string id; |
|
|
| int endLine = std::max(0, std::min((int)mLines.size(), aToLine)); |
| for (int i = aFromLine; i < endLine; ++i)
|
| {
|
| bool preproc = false;
|
| auto& line = mLines[i]; |
| |
| if (line.empty()) |
| continue; |
| |
| buffer.resize(line.size());
|
| for (size_t j = 0; j < line.size(); ++j)
|
| { |
| auto& col = line[j];
|
| buffer[j] = col.mChar;
|
| col.mColorIndex = PaletteIndex::Default;
|
| } |
| |
| const char * bufferBegin = &buffer.front(); |
| const char * bufferEnd = bufferBegin + buffer.size(); |
| |
| auto last = bufferEnd; |
|
|
| for (auto first = bufferBegin; first != last; )
|
| { |
| const char * token_begin = nullptr; |
| const char * token_end = nullptr; |
| PaletteIndex token_color = PaletteIndex::Default; |
| |
| bool hasTokenizeResult = false; |
| |
| if (mLanguageDefinition.mTokenize != nullptr) |
| { |
| if (mLanguageDefinition.mTokenize(first, last, token_begin, token_end, token_color)) |
| hasTokenizeResult = true; |
| } |
| |
| if (hasTokenizeResult == false) |
| { |
| // todo : remove |
| //printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), first); |
|
|
| for (auto& p : mRegexList)
|
| { |
| if (std::regex_search(first, last, results, p.first, std::regex_constants::match_continuous))
|
| { |
| hasTokenizeResult = true; |
| |
| auto& v = *results.begin();
|
| token_begin = v.first;
|
| token_end = v.second;
|
| token_color = p.second; |
| break; |
| } |
| } |
| } |
| |
| if (hasTokenizeResult == false) |
| { |
| first++; |
| } |
| else |
| { |
| const size_t token_length = token_end - token_begin; |
|
|
| if (token_color == PaletteIndex::Identifier)
|
| { |
| id.assign(token_begin, token_end); |
| |
| // todo : allmost all language definitions use lower case to specify keywords, so shouldn't this use ::tolower ?
|
| if (!mLanguageDefinition.mCaseSensitive)
|
| std::transform(id.begin(), id.end(), id.begin(), ::toupper);
|
|
|
| if (!preproc)
|
| {
|
| if (mLanguageDefinition.mKeywords.count(id) != 0)
|
| token_color = PaletteIndex::Keyword;
|
| else if (mLanguageDefinition.mIdentifiers.count(id) != 0)
|
| token_color = PaletteIndex::KnownIdentifier;
|
| else if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0)
|
| token_color = PaletteIndex::PreprocIdentifier;
|
| }
|
| else
|
| {
|
| if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0)
|
| token_color = PaletteIndex::PreprocIdentifier;
|
| else
|
| token_color = PaletteIndex::Identifier;
|
| }
|
| }
|
| else if (token_color == PaletteIndex::Preprocessor)
|
| {
|
| preproc = true;
|
| } |
|
|
| for (int j = 0; j < token_length; ++j)
|
| line[(token_begin - bufferBegin) + j].mColorIndex = token_color; |
|
|
| first = token_end;
|
| }
|
| }
|
| }
|
| }
|
|
|
| void TextEditor::ColorizeInternal()
|
| {
|
| if (mLines.empty())
|
| return;
|
|
|
| if (mCheckMultilineComments)
|
| {
|
| auto end = Coordinates((int)mLines.size(), 0);
|
| auto commentStart = end;
|
| auto withinString = false;
|
| for (auto i = Coordinates(0, 0); i < end; Advance(i))
|
| {
|
| auto& line = mLines[i.mLine];
|
| if (!line.empty())
|
| {
|
| auto& g = line[i.mColumn];
|
| auto c = g.mChar;
|
|
|
| bool inComment = commentStart <= i;
|
|
|
| if (withinString)
|
| {
|
| line[i.mColumn].mMultiLineComment = inComment;
|
|
|
| if (c == '\"')
|
| {
|
| if (i.mColumn + 1 < (int)line.size() && line[i.mColumn + 1].mChar == '\"')
|
| {
|
| Advance(i);
|
| if (i.mColumn < (int)line.size())
|
| line[i.mColumn].mMultiLineComment = inComment;
|
| }
|
| else
|
| withinString = false;
|
| }
|
| else if (c == '\\')
|
| {
|
| Advance(i);
|
| if (i.mColumn < (int)line.size())
|
| line[i.mColumn].mMultiLineComment = inComment;
|
| }
|
| }
|
| else
|
| {
|
| if (c == '\"')
|
| {
|
| withinString = true;
|
| line[i.mColumn].mMultiLineComment = inComment;
|
| }
|
| else
|
| {
|
| auto pred = [](const char& a, const Glyph& b) { return a == b.mChar; };
|
| auto from = line.begin() + i.mColumn;
|
| auto& startStr = mLanguageDefinition.mCommentStart;
|
| if (i.mColumn + startStr.size() <= line.size() &&
|
| equals(startStr.begin(), startStr.end(), from, from + startStr.size(), pred))
|
| commentStart = i;
|
|
|
| inComment = commentStart <= i;
|
|
|
| line[i.mColumn].mMultiLineComment = inComment;
|
|
|
| auto& endStr = mLanguageDefinition.mCommentEnd;
|
| if (i.mColumn + 1 >= (int)endStr.size() &&
|
| equals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred))
|
| commentStart = end;
|
| }
|
| }
|
| }
|
| }
|
| mCheckMultilineComments = false;
|
| return;
|
| } |
| |
| if (mColorRangeMin < mColorRangeMax)
|
| { |
| const int increment = (mLanguageDefinition.mTokenize == nullptr) ? 10 : 10000;
|
| const int to = std::min(mColorRangeMin + increment, mColorRangeMax);
|
| ColorizeRange(mColorRangeMin, to);
|
| mColorRangeMin = to;
|
|
|
| if (mColorRangeMax == mColorRangeMin)
|
| {
|
| mColorRangeMin = std::numeric_limits<int>::max();
|
| mColorRangeMax = 0;
|
| }
|
| return;
|
| }
|
| }
|
|
|
| float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const
|
| {
|
| auto& line = mLines[aFrom.mLine];
|
| float distance = 0.0f;
|
| auto fontScale = ImGui::GetFontSize() / ImGui::GetFont()->FontSize;
|
| float spaceSize = ImGui::CalcTextSize(" ").x + 1.0f * fontScale;
|
| for (size_t it = 0u; it < line.size() && it < (unsigned)aFrom.mColumn; ++it)
|
| {
|
| if (line[it].mChar == '\t')
|
| {
|
| distance = (1.0f * fontScale + std::floor((1.0f + distance)) / (float(mTabSize) * spaceSize)) * (float(mTabSize) * spaceSize);
|
| }
|
| else
|
| {
|
| char tempCString[2];
|
| tempCString[0] = line[it].mChar;
|
| tempCString[1] = '\0';
|
| distance += ImGui::CalcTextSize(tempCString).x + 1.0f * fontScale;
|
| }
|
| }
|
|
|
| return distance;
|
| }
|
|
|
| void TextEditor::EnsureCursorVisible()
|
| {
|
| if (!mWithinRender)
|
| {
|
| mScrollToCursor = true;
|
| return;
|
| }
|
|
|
| float scrollX = ImGui::GetScrollX();
|
| float scrollY = ImGui::GetScrollY();
|
|
|
| auto height = ImGui::GetWindowHeight();
|
| auto width = ImGui::GetWindowWidth();
|
|
|
| auto top = 1 + (int)ceil(scrollY / mCharAdvance.y);
|
| auto bottom = (int)ceil((scrollY + height) / mCharAdvance.y);
|
|
|
| auto left = (int)ceil(scrollX / mCharAdvance.x);
|
| auto right = (int)ceil((scrollX + width) / mCharAdvance.x);
|
|
|
| auto pos = GetActualCursorCoordinates();
|
| auto len = TextDistanceToLineStart(pos);
|
|
|
| if (pos.mLine < top)
|
| ImGui::SetScrollY(std::max(0.0f, (pos.mLine - 1) * mCharAdvance.y));
|
| if (pos.mLine > bottom - 4)
|
| ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 4) * mCharAdvance.y - height));
|
| if (len + mTextStart < left + 4)
|
| ImGui::SetScrollX(std::max(0.0f, len + mTextStart - 4));
|
| if (len + mTextStart > right - 4)
|
| ImGui::SetScrollX(std::max(0.0f, len + mTextStart + 4 - width));
|
| }
|
|
|
| int TextEditor::GetPageSize() const
|
| {
|
| auto height = ImGui::GetWindowHeight() - 20.0f;
|
| return (int)floor(height / mCharAdvance.y);
|
| }
|
|
|
| TextEditor::UndoRecord::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)
|
| : mAdded(aAdded)
|
| , mAddedStart(aAddedStart)
|
| , mAddedEnd(aAddedEnd)
|
| , mRemoved(aRemoved)
|
| , mRemovedStart(aRemovedStart)
|
| , mRemovedEnd(aRemovedEnd)
|
| , mBefore(aBefore)
|
| , mAfter(aAfter)
|
| {
|
| assert(mAddedStart <= mAddedEnd);
|
| assert(mRemovedStart <= mRemovedEnd);
|
| }
|
|
|
| void TextEditor::UndoRecord::Undo(TextEditor * aEditor)
|
| {
|
| if (!mAdded.empty())
|
| {
|
| aEditor->DeleteRange(mAddedStart, mAddedEnd);
|
| aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 2);
|
| }
|
|
|
| if (!mRemoved.empty())
|
| {
|
| auto start = mRemovedStart;
|
| aEditor->InsertTextAt(start, mRemoved.c_str());
|
| aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 2);
|
| }
|
|
|
| aEditor->mState = mBefore;
|
| aEditor->EnsureCursorVisible();
|
|
|
| }
|
|
|
| void TextEditor::UndoRecord::Redo(TextEditor * aEditor)
|
| {
|
| if (!mRemoved.empty())
|
| {
|
| aEditor->DeleteRange(mRemovedStart, mRemovedEnd);
|
| aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 1);
|
| }
|
|
|
| if (!mAdded.empty())
|
| {
|
| auto start = mAddedStart;
|
| aEditor->InsertTextAt(start, mAdded.c_str());
|
| aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 1);
|
| }
|
|
|
| aEditor->mState = mAfter;
|
| aEditor->EnsureCursorVisible();
|
| } |
| |
| static bool tokenize_cstyle_comment(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) |
| { |
| if (*in_begin != '/') |
| return false; |
| |
| if (in_begin + 1 < in_end && in_begin[1] == '/') |
| { |
| out_begin = in_begin; |
| out_end = in_end; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool tokenize_cstyle_preprocessor_directive(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) |
| { |
| if (*in_begin != '#') |
| return false; |
| |
| const char * p = in_begin + 1; |
| |
| while (p < in_end && isblank(*p)) |
| p++; |
| |
| bool hasText = false; |
| |
| while (p < in_end && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_')) |
| { |
| hasText = true; |
| p++; |
| } |
| |
| if (hasText) |
| { |
| out_begin = in_begin; |
| out_end = p; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool tokenize_cstyle_string(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) |
| { |
| const char * p = in_begin; |
| |
| if (*p == '"') |
| { |
| p++; |
| |
| while (p < in_end) |
| { |
| // handle end of string |
| if (*p == '"') |
| { |
| out_begin = in_begin; |
| out_end = p + 1; |
| return true; |
| } |
| |
| // handle escape character for " |
| if (*p == '\\' && p + 1 < in_end && p[1] == '"') |
| p++; |
| |
| p++; |
| } |
| } |
| |
| return false; |
| } |
| |
| static bool tokenize_cstyle_character_literal(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) |
| { |
| const char * p = in_begin; |
| |
| if (*p == '\'') |
| { |
| p++; |
| |
| // handle escape characters |
| if (p < in_end && *p == '\\') |
| p++; |
| |
| if (p < in_end) |
| p++; |
| |
| // handle end of character literal |
| if (p < in_end && *p == '\'') |
| { |
| out_begin = in_begin; |
| out_end = p + 1; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| static bool tokenize_cstyle_identifier(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) |
| { |
| const char * p = in_begin; |
| |
| if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_') |
| { |
| p++; |
| |
| while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) |
| p++; |
| |
| out_begin = in_begin; |
| out_end = p; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool tokenize_cstyle_number(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) |
| { |
| const char * p = in_begin; |
| |
| const bool startsWithNumber = *p >= '0' && *p <= '9'; |
| |
| if (*p != '+' && *p != '-' && !startsWithNumber) |
| return false; |
| |
| p++; |
| |
| while (p < in_end && isblank(*p)) |
| p++; |
| |
| bool hasNumber = startsWithNumber; |
| |
| while (p < in_end && (*p >= '0' && *p <= '9')) |
| { |
| hasNumber = true; |
| |
| p++; |
| } |
| |
| if (hasNumber == false) |
| return false; |
| |
| bool isFloat = false; |
| bool isHex = false; |
| bool isBinary = false; |
| |
| if (p < in_end) |
| { |
| if (*p == '.') |
| { |
| isFloat = true; |
| |
| p++; |
| |
| while (p < in_end && (*p >= '0' && *p <= '9')) |
| p++; |
| } |
| else if (*p == 'x' || *p == 'X') |
| { |
| // hex formatted integer of the type 0xef80 |
| |
| isHex = true; |
| |
| p++; |
| |
| while (p < in_end && ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F'))) |
| p++; |
| } |
| else if (*p == 'b' || *p == 'B') |
| { |
| // binary formatted integer of the type 0b01011101 |
| |
| isBinary = true; |
| |
| p++; |
| |
| while (p < in_end && (*p >= '0' && *p <= '1')) |
| p++; |
| } |
| } |
| |
| if (isHex == false && isBinary == false) |
| { |
| // floating point exponent |
| if (p < in_end && (*p == 'e' || *p == 'E')) |
| { |
| isFloat = true; |
| |
| p++; |
| |
| if (p < in_end && (*p == '+' || *p == '-')) |
| p++; |
| |
| bool hasDigits = false; |
| |
| while (p < in_end && (*p >= '0' && *p <= '9')) |
| { |
| hasDigits = true; |
| |
| p++; |
| } |
| |
| if (hasDigits == false) |
| return false; |
| } |
| |
| // single precision floating point type |
| if (p < in_end && *p == 'f') |
| p++; |
| } |
| |
| if (isFloat == false) |
| { |
| // integer size type |
| while (p < in_end && (*p == 'u' || *p == 'U' || *p == 'l' || *p == 'L')) |
| p++; |
| } |
| |
| out_begin = in_begin; |
| out_end = p; |
| return true; |
| }
|
| |
| static bool tokenize_cstyle_punctuation(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end) |
| { |
| switch (*in_begin) |
| { |
| case '[': |
| case ']': |
| case '{': |
| case '}': |
| case '!': |
| case '%': |
| case '^': |
| case '&': |
| case '*': |
| case '(': |
| case ')': |
| case '-': |
| case '+': |
| case '=': |
| case '~': |
| case '|': |
| case '<': |
| case '>': |
| case '?': |
| case ':': |
| case '/': |
| case ';': |
| case ',': |
| case '.': |
| out_begin = in_begin; |
| out_end = in_begin + 1; |
| return true; |
| } |
| |
| return false; |
| } |
|
|
| const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::CPlusPlus()
|
| {
|
| static bool inited = false;
|
| static LanguageDefinition langDef;
|
| if (!inited)
|
| {
|
| static const char* const cppKeywords[] = {
|
| "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",
|
| "compl", "concept", "const", "constexpr", "const_cast", "continue", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float",
|
| "for", "friend", "goto", "if", "import", "inline", "int", "long", "module", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public",
|
| "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this", "thread_local",
|
| "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq"
|
| };
|
| for (auto& k : cppKeywords)
|
| langDef.mKeywords.insert(k);
|
|
|
| static const char* const identifiers[] = {
|
| "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph",
|
| "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",
|
| "std", "string", "vector", "map", "unordered_map", "set", "unordered_set", "min", "max"
|
| };
|
| for (auto& k : identifiers)
|
| {
|
| Identifier id;
|
| id.mDeclaration = "Built-in function";
|
| langDef.mIdentifiers.insert(std::make_pair(std::string(k), id));
|
| } |
| |
| langDef.mTokenize = [](const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex) -> bool |
| { |
| paletteIndex = PaletteIndex::Max; |
| |
| while (in_begin < in_end && isblank(*in_begin)) |
| in_begin++; |
| |
| if (in_begin == in_end) |
| { |
| out_begin = in_end; |
| out_end = in_end; |
| paletteIndex = PaletteIndex::Default; |
| } |
| else if (tokenize_cstyle_comment(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::Comment; |
| else if (tokenize_cstyle_preprocessor_directive(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::Preprocessor; |
| else if (tokenize_cstyle_string(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::String; |
| else if (tokenize_cstyle_character_literal(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::CharLiteral; |
| else if (tokenize_cstyle_identifier(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::Identifier; |
| else if (tokenize_cstyle_number(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::Number; |
| else if (tokenize_cstyle_punctuation(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::Punctuation; |
| |
| return paletteIndex != PaletteIndex::Max; |
| }; |
| |
| langDef.mCommentStart = "/*";
|
| langDef.mCommentEnd = "*/";
|
|
|
| langDef.mCaseSensitive = true; |
| langDef.mAutoIndentation = true;
|
|
|
| langDef.mName = "C++";
|
|
|
| inited = true;
|
| }
|
| return langDef;
|
| }
|
|
|
| const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::HLSL()
|
| {
|
| static bool inited = false;
|
| static LanguageDefinition langDef;
|
| if (!inited)
|
| {
|
| static const char* const keywords[] = {
|
| "AppendStructuredBuffer", "asm", "asm_fragment", "BlendState", "bool", "break", "Buffer", "ByteAddressBuffer", "case", "cbuffer", "centroid", "class", "column_major", "compile", "compile_fragment",
|
| "CompileShader", "const", "continue", "ComputeShader", "ConsumeStructuredBuffer", "default", "DepthStencilState", "DepthStencilView", "discard", "do", "double", "DomainShader", "dword", "else",
|
| "export", "extern", "false", "float", "for", "fxgroup", "GeometryShader", "groupshared", "half", "Hullshader", "if", "in", "inline", "inout", "InputPatch", "int", "interface", "line", "lineadj",
|
| "linear", "LineStream", "matrix", "min16float", "min10float", "min16int", "min12int", "min16uint", "namespace", "nointerpolation", "noperspective", "NULL", "out", "OutputPatch", "packoffset",
|
| "pass", "pixelfragment", "PixelShader", "point", "PointStream", "precise", "RasterizerState", "RenderTargetView", "return", "register", "row_major", "RWBuffer", "RWByteAddressBuffer", "RWStructuredBuffer",
|
| "RWTexture1D", "RWTexture1DArray", "RWTexture2D", "RWTexture2DArray", "RWTexture3D", "sample", "sampler", "SamplerState", "SamplerComparisonState", "shared", "snorm", "stateblock", "stateblock_state",
|
| "static", "string", "struct", "switch", "StructuredBuffer", "tbuffer", "technique", "technique10", "technique11", "texture", "Texture1D", "Texture1DArray", "Texture2D", "Texture2DArray", "Texture2DMS",
|
| "Texture2DMSArray", "Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle", "triangleadj", "TriangleStream", "uint", "uniform", "unorm", "unsigned", "vector", "vertexfragment",
|
| "VertexShader", "void", "volatile", "while",
|
| "bool1","bool2","bool3","bool4","double1","double2","double3","double4", "float1", "float2", "float3", "float4", "int1", "int2", "int3", "int4", "in", "out", "inout",
|
| "uint1", "uint2", "uint3", "uint4", "dword1", "dword2", "dword3", "dword4", "half1", "half2", "half3", "half4",
|
| "float1x1","float2x1","float3x1","float4x1","float1x2","float2x2","float3x2","float4x2",
|
| "float1x3","float2x3","float3x3","float4x3","float1x4","float2x4","float3x4","float4x4",
|
| "half1x1","half2x1","half3x1","half4x1","half1x2","half2x2","half3x2","half4x2",
|
| "half1x3","half2x3","half3x3","half4x3","half1x4","half2x4","half3x4","half4x4",
|
| };
|
| for (auto& k : keywords)
|
| langDef.mKeywords.insert(k);
|
|
|
| static const char* const identifiers[] = {
|
| "abort", "abs", "acos", "all", "AllMemoryBarrier", "AllMemoryBarrierWithGroupSync", "any", "asdouble", "asfloat", "asin", "asint", "asint", "asuint",
|
| "asuint", "atan", "atan2", "ceil", "CheckAccessFullyMapped", "clamp", "clip", "cos", "cosh", "countbits", "cross", "D3DCOLORtoUBYTE4", "ddx",
|
| "ddx_coarse", "ddx_fine", "ddy", "ddy_coarse", "ddy_fine", "degrees", "determinant", "DeviceMemoryBarrier", "DeviceMemoryBarrierWithGroupSync",
|
| "distance", "dot", "dst", "errorf", "EvaluateAttributeAtCentroid", "EvaluateAttributeAtSample", "EvaluateAttributeSnapped", "exp", "exp2",
|
| "f16tof32", "f32tof16", "faceforward", "firstbithigh", "firstbitlow", "floor", "fma", "fmod", "frac", "frexp", "fwidth", "GetRenderTargetSampleCount",
|
| "GetRenderTargetSamplePosition", "GroupMemoryBarrier", "GroupMemoryBarrierWithGroupSync", "InterlockedAdd", "InterlockedAnd", "InterlockedCompareExchange",
|
| "InterlockedCompareStore", "InterlockedExchange", "InterlockedMax", "InterlockedMin", "InterlockedOr", "InterlockedXor", "isfinite", "isinf", "isnan",
|
| "ldexp", "length", "lerp", "lit", "log", "log10", "log2", "mad", "max", "min", "modf", "msad4", "mul", "noise", "normalize", "pow", "printf",
|
| "Process2DQuadTessFactorsAvg", "Process2DQuadTessFactorsMax", "Process2DQuadTessFactorsMin", "ProcessIsolineTessFactors", "ProcessQuadTessFactorsAvg",
|
| "ProcessQuadTessFactorsMax", "ProcessQuadTessFactorsMin", "ProcessTriTessFactorsAvg", "ProcessTriTessFactorsMax", "ProcessTriTessFactorsMin",
|
| "radians", "rcp", "reflect", "refract", "reversebits", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "sqrt", "step",
|
| "tan", "tanh", "tex1D", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", "tex2D", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj",
|
| "tex3D", "tex3D", "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", "texCUBE", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose", "trunc"
|
| };
|
| for (auto& k : identifiers)
|
| {
|
| Identifier id;
|
| id.mDeclaration = "Built-in function";
|
| langDef.mIdentifiers.insert(std::make_pair(std::string(k), id));
|
| }
|
|
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("//.*", PaletteIndex::Comment));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation));
|
|
|
| langDef.mCommentStart = "/*";
|
| langDef.mCommentEnd = "*/";
|
|
|
| langDef.mCaseSensitive = true; |
| langDef.mAutoIndentation = true;
|
|
|
| langDef.mName = "HLSL";
|
|
|
| inited = true;
|
| }
|
| return langDef;
|
| }
|
|
|
| const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL()
|
| {
|
| static bool inited = false;
|
| static LanguageDefinition langDef;
|
| if (!inited)
|
| {
|
| static const char* const keywords[] = {
|
| "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short",
|
| "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary",
|
| "_Noreturn", "_Static_assert", "_Thread_local"
|
| };
|
| for (auto& k : keywords)
|
| langDef.mKeywords.insert(k);
|
|
|
| static const char* const identifiers[] = {
|
| "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph",
|
| "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"
|
| };
|
| for (auto& k : identifiers)
|
| {
|
| Identifier id;
|
| id.mDeclaration = "Built-in function";
|
| langDef.mIdentifiers.insert(std::make_pair(std::string(k), id));
|
| }
|
|
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("//.*", PaletteIndex::Comment));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier));
|
| langDef.mTokenRegexStrings.push_back(std::make_pair<std::string, PaletteIndex>("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", PaletteIndex::Punctuation));
|
|
|
| langDef.mCommentStart = "/*";
|
| langDef.mCommentEnd = "*/";
|
|
|
| langDef.mCaseSensitive = true; |
| langDef.mAutoIndentation = true;
|
|
|
| langDef.mName = "GLSL";
|
|
|
| inited = true;
|
| }
|
| return langDef;
|
| }
|
|
|
| const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::C()
|
| {
|
| static bool inited = false;
|
| static LanguageDefinition langDef;
|
| if (!inited)
|
| {
|
| static const char* const keywords[] = {
|
| "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short",
|
| "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary",
|
| "_Noreturn", "_Static_assert", "_Thread_local"
|
| };
|
| for (auto& k : keywords)
|
| langDef.mKeywords.insert(k);
|
|
|
| static const char* const identifiers[] = {
|
| "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph",
|
| "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"
|
| };
|
| for (auto& k : identifiers)
|
| {
|
| Identifier id;
|
| id.mDeclaration = "Built-in function";
|
| langDef.mIdentifiers.insert(std::make_pair(std::string(k), id));
|
| }
|
| |
| langDef.mTokenize = [](const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex) -> bool |
| { |
| paletteIndex = PaletteIndex::Max; |
| |
| while (in_begin < in_end && isblank(*in_begin)) |
| in_begin++; |
| |
| if (in_begin == in_end) |
| { |
| out_begin = in_end; |
| out_end = in_end; |
| paletteIndex = PaletteIndex::Default; |
| } |
| else if (tokenize_cstyle_comment(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::Comment; |
| else if (tokenize_cstyle_preprocessor_directive(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::Preprocessor; |
| else if (tokenize_cstyle_string(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::String; |
| else if (tokenize_cstyle_character_literal(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::CharLiteral; |
| else if (tokenize_cstyle_identifier(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::Identifier; |
| else if (tokenize_cstyle_number(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::Number; |
| else if (tokenize_cstyle_punctuation(in_begin, in_end, out_begin, out_end)) |
| paletteIndex = PaletteIndex::Punctuation; |
| |
| return paletteIndex != PaletteIndex::Max; |
| }; |
|
|
| langDef.mCommentStart = "/*";
|
| langDef.mCommentEnd = "*/";
|
|
|
| langDef.mCaseSensitive = true; |
| langDef.mAutoIndentation = true;
|
|
|
| langDef.mName = "C";
|
|
|
| inited = true;
|
| }
|
| return langDef;
|
| }
|
|
|
| const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::SQL()
|
| {
|
| static bool inited = false;
|
| static LanguageDefinition langDef;
|
| if (!inited)
|
| {
|
| static const char* const keywords[] = {
|
| "ADD", "EXCEPT", "PERCENT", "ALL", "EXEC", "PLAN", "ALTER", "EXECUTE", "PRECISION", "AND", "EXISTS", "PRIMARY", "ANY", "EXIT", "PRINT", "AS", "FETCH", "PROC", "ASC", "FILE", "PROCEDURE",
|
| "AUTHORIZATION", "FILLFACTOR", "PUBLIC", "BACKUP", "FOR", "RAISERROR", "BEGIN", "FOREIGN", "READ", "BETWEEN", "FREETEXT", "READTEXT", "BREAK", "FREETEXTTABLE", "RECONFIGURE",
|
| "BROWSE", "FROM", "REFERENCES", "BULK", "FULL", "REPLICATION", "BY", "FUNCTION", "RESTORE", "CASCADE", "GOTO", "RESTRICT", "CASE", "GRANT", "RETURN", "CHECK", "GROUP", "REVOKE",
|
| "CHECKPOINT", "HAVING", "RIGHT", "CLOSE", "HOLDLOCK", "ROLLBACK", "CLUSTERED", "IDENTITY", "ROWCOUNT", "COALESCE", "IDENTITY_INSERT", "ROWGUIDCOL", "COLLATE", "IDENTITYCOL", "RULE",
|
| "COLUMN", "IF", "SAVE", "COMMIT", "IN", "SCHEMA", "COMPUTE", "INDEX", "SELECT", "CONSTRAINT", "INNER", "SESSION_USER", "CONTAINS", "INSERT", "SET", "CONTAINSTABLE", "INTERSECT", "SETUSER",
|
| "CONTINUE", "INTO", "SHUTDOWN", "CONVERT", "IS", "SOME", "CREATE", "JOIN", "STATISTICS", "CROSS", "KEY", "SYSTEM_USER", "CURRENT", "KILL", "TABLE", "CURRENT_DATE", "LEFT", "TEXTSIZE",
|
| "CURRENT_TIME", "LIKE", "THEN", "CURRENT_TIMESTAMP", "LINENO", "TO", "CURRENT_USER", "LOAD", "TOP", "CURSOR", "NATIONAL", "TRAN", "DATABASE", "NOCHECK", "TRANSACTION",
|
| "DBCC", "NONCLUSTERED", "TRIGGER", "DEALLOCATE", "NOT", "TRUNCATE", "DECLARE", "NULL", "TSEQUAL", "DEFAULT", "NULLIF", "UNION", "DELETE", "OF", "UNIQUE", "DENY", "OFF", "UPDATE",
|
| "DESC", "OFFSETS", "UPDATETEXT", "DISK", "ON", "USE", "DISTINCT", "OPEN", "USER", "DISTRIBUTED", "OPENDATASOURCE", "VALUES", "DOUBLE", "OPENQUERY", "VARYING","DROP", "OPENROWSET", "VIEW",
|
| "DUMMY", "OPENXML", "WAITFOR", "DUMP", "OPTION", "WHEN", "ELSE", "OR", "WHERE", "END", "ORDER", "WHILE", "ERRLVL", "OUTER", "WITH", "ESCAPE", "OVER", "WRITETEXT"
|
| };
|
|
|
| for (auto& k : keywords)
|
| langDef.mKeywords.insert(k);
|
|
|
| static const char* const identifiers[] = {
|
| "ABS", "ACOS", "ADD_MONTHS", "ASCII", "ASCIISTR", "ASIN", "ATAN", "ATAN2", "AVG", "BFILENAME", "BIN_TO_NUM", "BITAND", "CARDINALITY", "CASE", "CAST", "C
|