diff --git a/lib/third_party/imgui/ColorTextEditor/include/TextEditor.h b/lib/third_party/imgui/ColorTextEditor/include/TextEditor.h index 85f206d5f..30d8ff210 100644 --- a/lib/third_party/imgui/ColorTextEditor/include/TextEditor.h +++ b/lib/third_party/imgui/ColorTextEditor/include/TextEditor.h @@ -25,8 +25,11 @@ public: Identifier, KnownIdentifier, PreprocIdentifier, + GlobalDocComment, + DocComment, Comment, MultiLineComment, + PreprocessorDeactivated, Background, Cursor, Selection, @@ -140,9 +143,12 @@ public: bool mComment : 1; bool mMultiLineComment : 1; bool mPreprocessor : 1; + bool mDocComment : 1; + bool mGlobalDocComment : 1; + bool mDeactivated : 1; - Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex), - mComment(false), mMultiLineComment(false), mPreprocessor(false) {} + Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex), mComment(false), + mMultiLineComment(false), mPreprocessor(false), mDocComment(false), mGlobalDocComment(false), mDeactivated(false) {} }; typedef std::vector Line; @@ -158,7 +164,7 @@ public: Keywords mKeywords; Identifiers mIdentifiers; Identifiers mPreprocIdentifiers; - std::string mCommentStart, mCommentEnd, mSingleLineComment; + std::string mCommentStart, mCommentEnd, mSingleLineComment, mGlobalDocComment, mDocComment; char mPreprocChar; bool mAutoIndentation; @@ -169,7 +175,7 @@ public: bool mCaseSensitive; LanguageDefinition() - : mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true) + : mGlobalDocComment("/*!"), mDocComment("/**"), mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true) { } @@ -203,7 +209,10 @@ public: std::string GetSelectedText() const; std::string GetCurrentLineText()const; + class FindReplaceHandler; +public: + FindReplaceHandler *GetFindReplaceHandler() { return &mFindReplaceHandler; } int GetTotalLines() const { return (int)mLines.size(); } bool IsOverwrite() const { return mOverwrite; } @@ -260,6 +269,8 @@ public: void Paste(); void Delete(); + ImVec2 &GetCharAdvance() { return mCharAdvance; } + bool CanUndo() const; bool CanRedo() const; void Undo(int aSteps = 1); @@ -279,6 +290,75 @@ private: Coordinates mCursorPosition; }; + +public: + class FindReplaceHandler { + public: + FindReplaceHandler(); + typedef std::vector Matches; + Matches &GetMatches() { return mMatches; } + bool FindNext(TextEditor *editor,bool wrapAround); + unsigned FindMatch(TextEditor *editor,bool isNex); + bool Replace(TextEditor *editor,bool right); + bool ReplaceAll(TextEditor *editor); + std::string &GetFindWord() { return mFindWord; } + void SetFindWord(TextEditor *editor, const std::string &aFindWord) { + if (aFindWord != mFindWord) { + FindAllMatches(editor, aFindWord); + mFindWord = aFindWord; + } + } + std::string &GetReplaceWord() { return mReplaceWord; } + void SetReplaceWord(const std::string &aReplaceWord) { mReplaceWord = aReplaceWord; } + void SelectFound(TextEditor *editor, int found); + void FindAllMatches(TextEditor *editor,std::string findWord); + unsigned FindPosition( TextEditor *editor, Coordinates pos, bool isNext); + bool GetMatchCase() const { return mMatchCase; } + void SetMatchCase(TextEditor *editor, bool matchCase) { + if (matchCase != mMatchCase) { + mMatchCase = matchCase; + mOptionsChanged = true; + FindAllMatches(editor, mFindWord); + } + } + bool GetWholeWord() const { return mWholeWord; } + void SetWholeWord(TextEditor *editor, bool wholeWord) { + if (wholeWord != mWholeWord) { + mWholeWord = wholeWord; + mOptionsChanged = true; + FindAllMatches(editor, mFindWord); + } + } + bool GetFindRegEx() const { return mFindRegEx; } + void SetFindRegEx(TextEditor *editor, bool findRegEx) { + if (findRegEx != mFindRegEx) { + mFindRegEx = findRegEx; + mOptionsChanged = true; + FindAllMatches(editor, mFindWord); + } + } + void resetMatches() { + mMatches.clear(); + mFindWord = ""; + } + + void SetFindWindowPos(const ImVec2 &pos) { mFindWindowPos = pos; } + void SetFindWindowSize(const ImVec2 &size) { mFindWindowSize = size; } + ImVec2 GetFindWindowPos() const { return mFindWindowPos; } + ImVec2 GetFindWindowSize() const { return mFindWindowSize; } + private: + std::string mFindWord; + std::string mReplaceWord; + bool mMatchCase; + bool mWholeWord; + bool mFindRegEx; + bool mOptionsChanged; + Matches mMatches; + ImVec2 mFindWindowPos; + ImVec2 mFindWindowSize; + }; + FindReplaceHandler mFindReplaceHandler; +private: class UndoRecord { public: @@ -335,6 +415,8 @@ private: int GetCharacterIndex(const Coordinates& aCoordinates) const; int GetCharacterColumn(int aLine, int aIndex) const; int GetLineCharacterCount(int aLine) const; + unsigned long long GetLineByteCount(int aLine) const; + int GetStringCharacterCount(std::string str) const; int GetLineMaxColumn(int aLine) const; bool IsOnWordBoundary(const Coordinates& aAt) const; void RemoveLine(int aStart, int aEnd); @@ -356,6 +438,8 @@ private: EditorState mState; UndoBuffer mUndoBuffer; int mUndoIndex; + bool mScrollToBottom; + float mTopMargin; int mTabSize; bool mOverwrite; @@ -379,14 +463,14 @@ private: Palette mPalette; LanguageDefinition mLanguageDefinition; RegexList mRegexList; - - bool mCheckComments; + bool mCheckComments; Breakpoints mBreakpoints; ErrorMarkers mErrorMarkers; ImVec2 mCharAdvance; Coordinates mInteractiveStart, mInteractiveEnd; std::string mLineBuffer; uint64_t mStartTime; + std::vector mDefines; float mLastClick; bool mShowCursor; diff --git a/lib/third_party/imgui/ColorTextEditor/source/TextEditor.cpp b/lib/third_party/imgui/ColorTextEditor/source/TextEditor.cpp index 99476de69..5b0d1ee1a 100644 --- a/lib/third_party/imgui/ColorTextEditor/source/TextEditor.cpp +++ b/lib/third_party/imgui/ColorTextEditor/source/TextEditor.cpp @@ -8,6 +8,7 @@ #define IMGUI_DEFINE_MATH_OPERATORS #include "imgui.h" // for imGui::GetCurrentWindow() +#include "imgui_internal.h" // TODO // - multiline comments vs single-line: latter is blocking start of a ML @@ -23,8 +24,10 @@ bool equals(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Bi TextEditor::Palette TextEditor::sPaletteBase = TextEditor::GetDarkPalette(); +TextEditor::FindReplaceHandler::FindReplaceHandler() : mWholeWord(false),mFindRegEx(false),mMatchCase(false) {} + TextEditor::TextEditor() - : mLineSpacing(1.0f), mUndoIndex(0), mTabSize(4), mOverwrite(false), mReadOnly(false), mWithinRender(false), mScrollToCursor(false), mScrollToTop(false), mTextChanged(false), mColorizerEnabled(true), mTextStart(20.0f), mLeftMargin(10), mCursorPositionChanged(false), mColorRangeMin(0), mColorRangeMax(0), mSelectionMode(SelectionMode::Normal), mCheckComments(true), mLastClick(-1.0f), mHandleKeyboardInputs(true), mHandleMouseInputs(true), mIgnoreImGuiChild(false), mShowWhitespaces(true), mShowCursor(true), mShowLineNumbers(true), mStartTime(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) { + : mLineSpacing(1.0f), mUndoIndex(0), mTabSize(4), mOverwrite(false), mReadOnly(false), mWithinRender(false), mScrollToCursor(false), mScrollToTop(false), mScrollToBottom(false), mTextChanged(false), mColorizerEnabled(true), mTextStart(20.0f), mLeftMargin(10), mTopMargin(0), mCursorPositionChanged(false), mColorRangeMin(0), mColorRangeMax(0), mSelectionMode(SelectionMode::Normal), mCheckComments(true), mLastClick(-1.0f), mHandleKeyboardInputs(true), mHandleMouseInputs(true), mIgnoreImGuiChild(false), mShowWhitespaces(true), mShowCursor(true), mShowLineNumbers(true),mStartTime(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) { SetLanguageDefinition(LanguageDefinition::HLSL()); mLines.push_back(Line()); } @@ -442,6 +445,15 @@ int TextEditor::GetCharacterColumn(int aLine, int aIndex) const { return col; } +int TextEditor::GetStringCharacterCount(std::string str) const { + if (str.empty()) + return 0; + int c = 0; + for (unsigned i = 0; i < str.size(); c++) + i += UTF8CharLength(str[i]); + return c; +} + int TextEditor::GetLineCharacterCount(int aLine) const { if (aLine >= mLines.size()) return 0; @@ -452,6 +464,13 @@ int TextEditor::GetLineCharacterCount(int aLine) const { return c; } +unsigned long long TextEditor::GetLineByteCount(int aLine) const { + if (aLine >= mLines.size()) + return 0; + auto &line = mLines[aLine]; + return line.size(); +} + int TextEditor::GetLineMaxColumn(int aLine) const { if (aLine >= mLines.size()) return 0; @@ -577,10 +596,16 @@ std::string TextEditor::GetWordAt(const Coordinates &aCoords) const { ImU32 TextEditor::GetGlyphColor(const Glyph &aGlyph) const { if (!mColorizerEnabled) return mPalette[(int)PaletteIndex::Default]; + if (aGlyph.mGlobalDocComment) + return mPalette[(int)PaletteIndex::GlobalDocComment]; + if (aGlyph.mDocComment) + return mPalette[(int)PaletteIndex::DocComment]; if (aGlyph.mComment) return mPalette[(int)PaletteIndex::Comment]; if (aGlyph.mMultiLineComment) return mPalette[(int)PaletteIndex::MultiLineComment]; + if (aGlyph.mDeactivated && !aGlyph.mPreprocessor) + return mPalette[(int)PaletteIndex::PreprocessorDeactivated]; auto const color = mPalette[(int)aGlyph.mColorIndex]; if (aGlyph.mPreprocessor) { const auto ppcolor = mPalette[(int)PaletteIndex::Preprocessor]; @@ -671,6 +696,16 @@ void TextEditor::HandleKeyboardInputs() { EnterCharacter('\n', false); else if (!IsReadOnly() && !ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab))) EnterCharacter('\t', shift); + else if (!ctrl && !alt && !shift && ImGui::IsKeyPressed(ImGuiKey_F3)) + mFindReplaceHandler.FindMatch(this, true); + else if (!ctrl && !alt && shift && ImGui::IsKeyPressed(ImGuiKey_F3)) + mFindReplaceHandler.FindMatch(this, false); + else if (!ctrl && alt && !shift && ImGui::IsKeyPressed(ImGuiKey_C)) + mFindReplaceHandler.SetMatchCase(this,!mFindReplaceHandler.GetMatchCase()); + else if (!ctrl && alt && !shift && ImGui::IsKeyPressed(ImGuiKey_R)) + mFindReplaceHandler.SetFindRegEx(this,!mFindReplaceHandler.GetFindRegEx()); + else if (!ctrl && alt && !shift && ImGui::IsKeyPressed(ImGuiKey_W)) + mFindReplaceHandler.SetWholeWord(this,!mFindReplaceHandler.GetWholeWord()); if (!IsReadOnly() && !io.InputQueueCharacters.empty()) { for (int i = 0; i < io.InputQueueCharacters.Size; i++) { @@ -691,7 +726,7 @@ void TextEditor::HandleMouseInputs() { auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; if (ImGui::IsWindowHovered()) { - if (!shift && !alt) { + if (!alt) { auto click = ImGui::IsMouseClicked(0); auto doubleClick = ImGui::IsMouseDoubleClicked(0); auto t = ImGui::GetTime(); @@ -732,11 +767,16 @@ void TextEditor::HandleMouseInputs() { Left mouse button click */ else if (click) { - mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); - if (ctrl) + if (ctrl) { + mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); mSelectionMode = SelectionMode::Word; - else + } else if (shift) { mSelectionMode = SelectionMode::Normal; + mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); + } else { + mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); + mSelectionMode = SelectionMode::Normal; + } SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); mLastClick = (float)ImGui::GetTime(); @@ -752,7 +792,7 @@ void TextEditor::HandleMouseInputs() { } void TextEditor::Render() { - /* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/ + /* Compute mCharAdvance regarding scaled font size (Ctrl + mouse wheel)*/ const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x; mCharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * mLineSpacing); @@ -765,7 +805,7 @@ void TextEditor::Render() { assert(mLineBuffer.empty()); - auto contentSize = ImGui::GetWindowContentRegionMax(); + auto contentSize = ImGui::GetWindowContentRegionMax() - ImVec2(0,mTopMargin); auto drawList = ImGui::GetWindowDrawList(); float longest(mTextStart); @@ -774,13 +814,18 @@ void TextEditor::Render() { ImGui::SetScrollY(0.f); } - ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos(); + if ( mScrollToBottom && ImGui::GetScrollMaxY() > ImGui::GetScrollY()) { + mScrollToBottom = false; + ImGui::SetScrollY(ImGui::GetScrollMaxY()); + } + + ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos() + ImVec2(0, mTopMargin); auto scrollX = ImGui::GetScrollX(); auto scrollY = ImGui::GetScrollY(); - auto lineNo = (int)floor(scrollY / mCharAdvance.y); + auto lineNo = (int)(std::floor(scrollY / mCharAdvance.y));// + linesAdded); auto globalLineMax = (int)mLines.size(); - auto lineMax = std::max(0, std::min((int)mLines.size() - 1, lineNo + (int)floor((scrollY + contentSize.y) / mCharAdvance.y))); + auto lineMax = std::max(0, std::min((int)mLines.size() - 1, lineNo + (int)std::ceil((scrollY + contentSize.y) / mCharAdvance.y))); // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width char buf[16]; @@ -959,6 +1004,8 @@ void TextEditor::Render() { ++lineNo; } + if (lineNo < mLines.size() && ImGui::GetScrollMaxX() > 0.0f) + longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), longest); // Draw a tooltip on known identifiers/preprocessor symbols if (ImGui::IsMousePosValid()) { @@ -981,13 +1028,96 @@ void TextEditor::Render() { } } - ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y)); if (mScrollToCursor) { EnsureCursorVisible(); mScrollToCursor = false; } + + ImGuiPopupFlags_ popup_flags = ImGuiPopupFlags_None; + ImGuiContext& g = *GImGui; + auto oldTopMargin = mTopMargin; + auto popupStack = g.OpenPopupStack; + if (popupStack.Size > 0) { + for (int n = 0; n < popupStack.Size; n++){ + auto window = popupStack[n].Window; + if (window != nullptr) { + if (window->Size.x == mFindReplaceHandler.GetFindWindowSize().x && + window->Size.y == mFindReplaceHandler.GetFindWindowSize().y && + window->Pos.x == mFindReplaceHandler.GetFindWindowPos().x && + window->Pos.y == mFindReplaceHandler.GetFindWindowPos().y) { + mTopMargin = mFindReplaceHandler.GetFindWindowSize().y; + } + } + } + } else { + mTopMargin = 0; + } + + static float linesAdded = 0; + static float pixelsAdded = 0; + static float savedScrollY = 0; + static float shiftedScrollY = 0; + if (mTopMargin != oldTopMargin) { + if (oldTopMargin == 0) + savedScrollY = ImGui::GetScrollY(); + auto window = ImGui::GetCurrentWindow(); + auto maxScroll = window->ScrollMax.y; + if (maxScroll > 0) { + float lineCount; + float pixelCount; + if (mTopMargin > oldTopMargin) { + pixelCount = mTopMargin - oldTopMargin; + lineCount = pixelCount / mCharAdvance.y; + } else if (mTopMargin > 0) { + pixelCount = oldTopMargin - mTopMargin; + lineCount = pixelCount / mCharAdvance.y; + } else { + pixelCount = oldTopMargin; + lineCount = std::round(linesAdded); + } + auto state = mState; + auto oldScrollY = ImGui::GetScrollY(); + int lineCountInt; + + if (mTopMargin > oldTopMargin) { + lineCountInt = std::round(lineCount + linesAdded - std::floor(linesAdded)); + } else + lineCountInt = std::round(lineCount); + for (int i = 0; i < lineCountInt; i++) { + if (mTopMargin > oldTopMargin) + mLines.insert(mLines.begin() + mLines.size(), Line()); + else + mLines.erase(mLines.begin() + mLines.size() - 1); + } + if (mTopMargin > oldTopMargin) { + linesAdded += lineCount; + pixelsAdded += pixelCount; + } else if (mTopMargin > 0) { + linesAdded -= lineCount; + pixelsAdded -= pixelCount; + } else { + linesAdded = 0; + pixelsAdded = 0; + } + if (oldScrollY + pixelCount < maxScroll) { + if (mTopMargin > oldTopMargin) + shiftedScrollY = oldScrollY + pixelCount; + else if (mTopMargin > 0) + shiftedScrollY = oldScrollY - pixelCount; + else if (ImGui::GetScrollY() == shiftedScrollY) + shiftedScrollY = savedScrollY; + else + shiftedScrollY = ImGui::GetScrollY() - pixelCount; + ImGui::SetScrollY(shiftedScrollY); + } else { + if (mTopMargin > oldTopMargin) + mScrollToBottom = true; + } + mState = state; + } + } } void TextEditor::Render(const char *aTitle, const ImVec2 &aSize, bool aBorder) { @@ -1088,16 +1218,13 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) { if (start > end) std::swap(start, end); start.mColumn = 0; - // end.mColumn = end.mLine < mLines.size() ? mLines[end.mLine].size() : 0; + if (end.mColumn == 0 && end.mLine > 0) --end.mLine; if (end.mLine >= (int)mLines.size()) end.mLine = mLines.empty() ? 0 : (int)mLines.size() - 1; end.mColumn = GetLineMaxColumn(end.mLine); - // if (end.mColumn >= GetLineMaxColumn(end.mLine)) - // end.mColumn = GetLineMaxColumn(end.mLine) - 1; - u.mRemovedStart = start; u.mRemovedEnd = end; u.mRemoved = GetText(start, end); @@ -1176,10 +1303,19 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) { newLine.push_back(line[it]); const size_t whitespaceSize = newLine.size(); + int cstart = 0; + int cpos = 0; auto cindex = GetCharacterIndex(coord); - newLine.insert(newLine.end(), line.begin() + cindex, line.end()); - line.erase(line.begin() + cindex, line.begin() + line.size()); - SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (int)whitespaceSize))); + if (cindex < whitespaceSize && mLanguageDefinition.mAutoIndentation) { + cstart = (int) whitespaceSize; + cpos = cindex; + } else { + cstart = cindex; + cpos = (int) whitespaceSize; + } + newLine.insert(newLine.end(), line.begin() + cstart, line.end()); + line.erase(line.begin() + cstart, line.begin() + line.size()); + SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, cpos))); u.mAdded = (char)aChar; } else if (aChar == '\t') { auto &line = mLines[coord.mLine]; @@ -1651,9 +1787,6 @@ void TextEditor::Backspace() { while (cindex > 0 && IsUTFSequence(line[cindex].mChar)) --cindex; - // if (cindex > 0 && UTF8CharLength(line[cindex].mChar) > 1) - // --cindex; - u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); --u.mRemovedStart.mColumn; mState.mCursorPosition.mColumn = GetCharacterColumn(mState.mCursorPosition.mLine, cindex); @@ -1766,6 +1899,360 @@ void TextEditor::Redo(int aSteps) { mUndoBuffer[mUndoIndex++].Redo(this); } +// the index here is array index so zero based +void TextEditor::FindReplaceHandler::SelectFound(TextEditor *editor, int index) { + assert(index >= 0 && index < mMatches.size()); + auto selectionStart = mMatches[index].mSelectionStart; + auto selectionEnd = mMatches[index].mSelectionEnd; + editor->SetSelection(selectionStart, selectionEnd); + editor->SetCursorPosition(selectionEnd); + editor->mScrollToCursor = true; +} + +// The returned index is shown in the form +// 'index of count' so 1 based +unsigned TextEditor::FindReplaceHandler::FindMatch(TextEditor *editor, bool isNext) { + + if ( editor->mTextChanged || mOptionsChanged) { + std::string findWord = GetFindWord(); + if (findWord.empty()) + return 0; + resetMatches(); + FindAllMatches(editor, findWord); + } + + auto targetPos = editor->mState.mCursorPosition; + auto count = mMatches.size(); + + if (count == 0) { + editor->SetCursorPosition(targetPos); + return 0; + } + + for (unsigned i=0; i < count; i++) { + if (targetPos >= mMatches[i].mSelectionStart && targetPos <= mMatches[i].mSelectionEnd) { + if (isNext) { + if (i == count - 1) { + SelectFound(editor, 0); + return 1; + } else { + SelectFound(editor, i + 1); + return (i + 2); + } + } else { + if (i == 0) { + SelectFound(editor, count - 1); + return count; + } else { + SelectFound(editor, i - 1); + return i; + } + } + } + } + + if ((targetPos > mMatches[count - 1].mSelectionEnd) || (targetPos < mMatches[0].mSelectionStart)) { + if (isNext) { + SelectFound(editor,0); + return 1; + } else { + SelectFound(editor,count - 1); + return count; + } + } + + for (unsigned i=1;i < count;i++) { + + if (mMatches[i - 1].mSelectionEnd <= targetPos && + mMatches[i].mSelectionStart >= targetPos ) { + if (isNext) { + SelectFound(editor,i); + return i + 1; + } else { + SelectFound(editor,i - 1); + return i; + } + } + } + + return 0; +} + +// returns 1 based index +unsigned TextEditor::FindReplaceHandler::FindPosition( TextEditor *editor, TextEditor::Coordinates targetPos, bool isNext) { + if ( editor->mTextChanged || mOptionsChanged) { + std::string findWord = GetFindWord(); + if (findWord.empty()) + return 0; + resetMatches(); + FindAllMatches(editor,findWord); + } + + int count = mMatches.size(); + if (count == 0) + return 0; + if( isNext) { + if (targetPos > mMatches[count - 1].mSelectionEnd || targetPos <= mMatches[0].mSelectionEnd) + return 1; + for (unsigned i = 1; i < count; i++) { + if (targetPos > mMatches[i-1].mSelectionEnd && targetPos <= mMatches[i].mSelectionEnd) + return i+1; + } + } else { + if (targetPos >= mMatches[count - 1].mSelectionStart || targetPos < mMatches[0].mSelectionStart) + return count; + for (unsigned i = 1; i < count; i++) { + if (targetPos >= mMatches[i-1].mSelectionStart && targetPos < mMatches[i].mSelectionStart) + return i ; + } + } + return 0; +} + +// Create a string that escapes special characters +// and separate word from non word +std::string make_wholeWord(const std::string &s) { + static const char metacharacters[] = R"(\.^$-+()[]{}|?*)"; + std::string out; + out.reserve(s.size()); + if (s[0] == '#') + out.push_back('#'); + out.push_back('\\'); + out.push_back('b'); + for (auto ch : s) { + if (strchr(metacharacters, ch)) + out.push_back('\\'); + out.push_back(ch); + } + out.push_back('\\'); + out.push_back('b'); + return out; +} + +// Performs actual search to fill mMatches +bool TextEditor::FindReplaceHandler::FindNext(TextEditor *editor, bool wrapAround) { + auto curPos = editor->mState.mCursorPosition; + unsigned long selectionLength = editor->GetStringCharacterCount(mFindWord); + size_t byteIndex = 0; + + for (size_t ln = 0; ln < curPos.mLine; ln++) + byteIndex += editor->GetLineByteCount(ln) + 1; + byteIndex += curPos.mColumn; + + std::string wordLower = mFindWord; + if (!GetMatchCase()) + std::transform(wordLower.begin(), wordLower.end(), wordLower.begin(), ::tolower); + + std::string textSrc = editor->GetText(); + if (!GetMatchCase()) + std::transform(textSrc.begin(), textSrc.end(), textSrc.begin(), ::tolower); + + size_t textLoc; + // TODO: use regexp find iterator in all cases + // to find all matches for FindAllMatches. + // That should make things faster (no need + // to call FindNext many times) and remove + // clunky match case code + if (GetWholeWord() || GetFindRegEx()) { + std::regex regularExpression; + if (GetFindRegEx()) { + try { + regularExpression.assign(wordLower); + } catch (std::regex_error &e) { + return false; + } + } else { + try { + regularExpression.assign(make_wholeWord(wordLower)); + } catch (std::regex_error &e) { + return false; + } + } + + size_t pos=0; + std::sregex_iterator iter = std::sregex_iterator(textSrc.begin(), textSrc.end(), regularExpression); + std::sregex_iterator end; + if (!iter->ready()) + return false; + size_t firstLoc = iter->position(); + unsigned long firstLength = iter->length(); + + if(firstLoc > byteIndex) { + pos = firstLoc; + selectionLength = firstLength; + } else { + + while (iter != end) { + iter++; + if (((pos = iter->position()) > byteIndex) && ((selectionLength = iter->length()) > 0)) + break; + } + } + + if (iter == end && !wrapAround) + return false; + + textLoc = pos; + if (wrapAround) { + if (iter == end) { + pos = firstLoc; + selectionLength = firstLength; + } + } + } else { + // non regex search + textLoc = textSrc.find(wordLower, byteIndex); + if (textLoc == std::string::npos) { + if (wrapAround) + textLoc = textSrc.find(wordLower, 0); + else + return false; + } + } + if (textLoc != std::string::npos) { + curPos.mLine = curPos.mColumn = 0; + byteIndex = 0; + + for (size_t ln = 0; ln < editor->mLines.size(); ln++) { + auto byteCount = editor->GetLineByteCount(ln) + 1; + + if (byteIndex + byteCount > textLoc) { + curPos.mLine = ln; + curPos.mColumn = textLoc - byteIndex; + + auto &line = editor->mLines[curPos.mLine]; + for (int i = 0; i < line.size(); i++) + if (line[i].mChar == '\t') + curPos.mColumn += (editor->mTabSize - 1); + break; + } else {// just keep adding + byteIndex += byteCount; + } + } + } else + return false; + + auto selStart = curPos, selEnd = curPos; + selEnd.mColumn += selectionLength; + editor->SetSelection(selStart, selEnd); + editor->SetCursorPosition(selEnd); + editor->mScrollToCursor = true; + return true; +} + +void TextEditor::FindReplaceHandler::FindAllMatches(TextEditor *editor,std::string findWord) { + + if (findWord.empty()) { + editor->mScrollToCursor = true; + mFindWord = ""; + mMatches.clear(); + return; + } + + if(findWord == mFindWord && !editor->mTextChanged && !mOptionsChanged) + return; + + if (mOptionsChanged) + mOptionsChanged = false; + + mMatches.clear(); + mFindWord = findWord; + auto startingPos = editor->mState.mCursorPosition; + auto state = editor->mState; + Coordinates begin = Coordinates(0,0); + editor->mState.mCursorPosition = begin; + + if (!FindNext(editor,false)) { + editor->mState = state; + editor->mScrollToCursor = true; + return; + } + auto initialPos = editor->mState.mCursorPosition; + mMatches.push_back(editor->mState); + + while( editor->mState.mCursorPosition < startingPos) { + if (!FindNext(editor,false)) { + editor->mState = state; + editor->mScrollToCursor = true; + return; + } + mMatches.push_back(editor->mState); + } + + while (FindNext(editor,false)) + mMatches.push_back(editor->mState); + + editor->mState = state; + editor->mScrollToCursor = true; + return; +} + + +bool TextEditor::FindReplaceHandler::Replace(TextEditor *editor, bool next) { + + if (mMatches.empty() || mFindWord == mReplaceWord || mFindWord.empty()) + return false; + + + auto state = editor->mState; + + if (editor->mState.mCursorPosition <= editor->mState.mSelectionEnd && editor->mState.mSelectionEnd > editor->mState.mSelectionStart && editor->mState.mCursorPosition > editor->mState.mSelectionStart) { + + editor->mState.mCursorPosition = editor->mState.mSelectionStart; + if(editor->mState.mCursorPosition.mColumn == 0) { + editor->mState.mCursorPosition.mLine--; + editor->mState.mCursorPosition.mColumn = editor->GetLineMaxColumn(editor->mState.mCursorPosition.mLine); + } else + editor->mState.mCursorPosition.mColumn--; + } + auto matchIndex = FindMatch(editor,next); + if(matchIndex != 0) { + UndoRecord u; + u.mBefore = editor->mState; + + auto selectionEnd = editor->mState.mSelectionEnd; + + u.mRemoved = editor->GetSelectedText(); + u.mRemovedStart = editor->mState.mSelectionStart; + u.mRemovedEnd = editor->mState.mSelectionEnd; + + editor->DeleteSelection(); + if (GetFindRegEx()) { + std::string replacedText = std::regex_replace(editor->GetText(), std::regex(mFindWord), mReplaceWord, std::regex_constants::format_first_only | std::regex_constants::format_no_copy); + u.mAdded = replacedText; + } else + u.mAdded = mReplaceWord; + + u.mAddedStart = editor->GetActualCursorCoordinates(); + + editor->InsertText(u.mAdded); + editor->SetCursorPosition(editor->mState.mSelectionEnd); + + u.mAddedEnd = editor->GetActualCursorCoordinates(); + + editor->mScrollToCursor = true; + ImGui::SetKeyboardFocusHere(0); + + u.mAfter = editor->mState; + editor->AddUndo(u); + editor->mTextChanged = true; + mMatches.erase(mMatches.begin() + matchIndex - 1); + + return true; + } + editor->mState = state; + return false; +} + +bool TextEditor::FindReplaceHandler::ReplaceAll(TextEditor *editor) { + unsigned count = mMatches.size(); + + for (unsigned i = 0; i < count; i++) + Replace(editor,true); + + return true; +} + const TextEditor::Palette &TextEditor::GetDarkPalette() { const static Palette p = { { @@ -1779,8 +2266,11 @@ const TextEditor::Palette &TextEditor::GetDarkPalette() { 0xffaaaaaa, // Identifier 0xff9bc64d, // Known identifier 0xffc040a0, // Preproc identifier + 0xff708020, // Global Doc Comment + 0xff586820, // Doc Comment 0xff206020, // Comment (single line) 0xff406020, // Comment (multi line) + 0xff004545, // Preprocessor deactivated 0xff101010, // Background 0xffe0e0e0, // Cursor 0x80a06020, // Selection @@ -1808,8 +2298,11 @@ const TextEditor::Palette &TextEditor::GetLightPalette() { 0xff404040, // Identifier 0xff606010, // Known identifier 0xffc040a0, // Preproc identifier + 0xff707820, // Global Doc Comment + 0xff586020, // Doc Comment 0xff205020, // Comment (single line) 0xff405020, // Comment (multi line) + 0xffa7cccc, // Preprocessor deactivated 0xffffffff, // Background 0xff000000, // Cursor 0x80600000, // Selection @@ -1837,8 +2330,11 @@ const TextEditor::Palette &TextEditor::GetRetroBluePalette() { 0xff00ffff, // Identifier 0xffffffff, // Known identifier 0xffff00ff, // Preproc identifier + 0xff101010, // Global Doc Comment + 0xff202020, // Doc Comment 0xff808080, // Comment (single line) 0xff404040, // Comment (multi line) + 0xff004000, // Preprocessor deactivated 0xff800000, // Background 0xff0080ff, // Cursor 0x80ffff00, // Selection @@ -1999,24 +2495,33 @@ void TextEditor::ColorizeInternal() { auto endIndex = 0; auto commentStartLine = endLine; auto commentStartIndex = endIndex; + auto withinGlobalDocComment = false; + auto withinDocComment = false; + auto withinComment = false; auto withinString = false; auto withinSingleLineComment = false; auto withinPreproc = false; + auto withinNotDef = false; auto firstChar = true; // there is no other non-whitespace characters in the line before - auto concatenate = false; // '\' on the very end of the line auto currentLine = 0; auto currentIndex = 0; + auto &startStr = mLanguageDefinition.mCommentStart; + auto &singleStartStr = mLanguageDefinition.mSingleLineComment; + auto &docStartStr = mLanguageDefinition.mDocComment; + auto &globalStartStr = mLanguageDefinition.mGlobalDocComment; + + std::vector ifDefs; + ifDefs.push_back(true); + while (currentLine < endLine || currentIndex < endIndex) { auto &line = mLines[currentLine]; - if (currentIndex == 0 && !concatenate) { + if (currentIndex == 0) { withinSingleLineComment = false; withinPreproc = false; firstChar = true; } - concatenate = false; - if (!line.empty()) { auto &g = line[currentIndex]; auto c = g.mChar; @@ -2024,59 +2529,153 @@ void TextEditor::ColorizeInternal() { if (c != mLanguageDefinition.mPreprocChar && !isspace(c)) firstChar = false; - if (currentIndex == (int)line.size() - 1 && line[line.size() - 1].mChar == '\\') - concatenate = true; - bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); if (withinString) { - line[currentIndex].mMultiLineComment = inComment; - + line[currentIndex].mMultiLineComment = withinComment; + line[currentIndex].mComment = withinSingleLineComment; + line[currentIndex].mDocComment = withinDocComment; + line[currentIndex].mGlobalDocComment = withinGlobalDocComment; + line[currentIndex].mDeactivated = withinNotDef; if (c == '\"') { - if (currentIndex + 1 < (int)line.size() && line[currentIndex + 1].mChar == '\"') { + if (currentIndex > 2 && line[currentIndex - 1].mChar == '\\' && line[currentIndex - 2].mChar != '\\') { currentIndex += 1; - if (currentIndex < (int)line.size()) - line[currentIndex].mMultiLineComment = inComment; + if (currentIndex < (int)line.size()) { + line[currentIndex].mMultiLineComment = withinComment; + line[currentIndex].mComment = withinSingleLineComment; + line[currentIndex].mDocComment = withinDocComment; + line[currentIndex].mGlobalDocComment = withinGlobalDocComment; + line[currentIndex].mDeactivated = withinNotDef; + } } else withinString = false; - } else if (c == '\\') { - currentIndex += 1; - if (currentIndex < (int)line.size()) - line[currentIndex].mMultiLineComment = inComment; } } else { - if (firstChar && c == mLanguageDefinition.mPreprocChar) + if (firstChar && c == mLanguageDefinition.mPreprocChar) { withinPreproc = true; + std::string directive; + auto start = currentIndex + 1; + while (start < (int) line.size() && !isspace(line[start].mChar)) { + directive += line[start].mChar; + start++; + } + + if (start < (int) line.size()) { + + if (isspace(line[start].mChar)) { + start += 1; + if (directive == "define") { + while (start < (int) line.size() && isspace(line[start].mChar)) + start++; + std::string identifier; + while (start < (int) line.size() && !isspace(line[start].mChar)) { + identifier += line[start].mChar; + start++; + } + if (identifier.size() > 0 && !withinNotDef && std::find(mDefines.begin(),mDefines.end(),identifier) == mDefines.end()) + mDefines.push_back(identifier); + } else if (directive == "undef") { + while (start < (int) line.size() && isspace(line[start].mChar)) + start++; + std::string identifier; + while (start < (int) line.size() && !isspace(line[start].mChar)) { + identifier += line[start].mChar; + start++; + } + if (identifier.size() > 0 && !withinNotDef) + mDefines.erase(std::remove(mDefines.begin(), mDefines.end(), identifier), mDefines.end()); + } else if (directive == "ifdef") { + while (start < (int) line.size() && isspace(line[start].mChar)) + start++; + std::string identifier; + while (start < (int) line.size() && !isspace(line[start].mChar)) { + identifier += line[start].mChar; + start++; + } + if (!withinNotDef) { + bool isConditionMet = std::find(mDefines.begin(),mDefines.end(),identifier) != mDefines.end(); + withinNotDef = !isConditionMet; + ifDefs.push_back(isConditionMet); + } else + ifDefs.push_back(false); + } else if (directive == "ifndef") { + while (start < (int) line.size() && isspace(line[start].mChar)) + start++; + std::string identifier; + while (start < (int) line.size() && !isspace(line[start].mChar)) { + identifier += line[start].mChar; + start++; + } + if (!withinNotDef) { + bool isConditionMet = std::find(mDefines.begin(),mDefines.end(),identifier) == mDefines.end(); + withinNotDef = !isConditionMet; + ifDefs.push_back(isConditionMet); + } else + ifDefs.push_back(false); + } + } + } else { + if (directive == "endif") { + if (ifDefs.size() > 1) + ifDefs.pop_back(); + withinNotDef = !ifDefs.back(); + } + } + } if (c == '\"') { withinString = true; - line[currentIndex].mMultiLineComment = inComment; + line[currentIndex].mMultiLineComment = withinComment; + line[currentIndex].mComment = withinSingleLineComment; + line[currentIndex].mDocComment = withinDocComment; + line[currentIndex].mGlobalDocComment = withinGlobalDocComment; + line[currentIndex].mDeactivated = withinNotDef; } else { auto pred = [](const char &a, const Glyph &b) { return a == b.mChar; }; - auto from = line.begin() + currentIndex; - auto &startStr = mLanguageDefinition.mCommentStart; - auto &singleStartStr = mLanguageDefinition.mSingleLineComment; - if (!singleStartStr.empty() && - currentIndex + singleStartStr.size() <= line.size() && - equals(singleStartStr.begin(), singleStartStr.end(), from, from + singleStartStr.size(), pred)) { - withinSingleLineComment = true; - } else if (!startStr.empty() && !withinSingleLineComment && currentIndex + startStr.size() <= line.size() && - equals(startStr.begin(), startStr.end(), from, from + startStr.size(), pred)) { - commentStartLine = currentLine; - commentStartIndex = currentIndex; + auto compareForth = [&](const std::string &a, const std::vector &b) { + return !a.empty() && currentIndex + a.size() <= b.size() && equals(a.begin(), a.end(), + b.begin() + currentIndex, b.begin() + currentIndex + a.size(), pred); + }; + + auto compareBack = [&](const std::string &a, const std::vector &b) { + return !a.empty() && currentIndex + 1 >= (int)a.size() && equals(a.begin(), a.end(), + b.begin() + currentIndex + 1 - a.size(), b.begin() + currentIndex + 1, pred); + }; + + if (!inComment && !withinSingleLineComment && !withinPreproc) { + if (compareForth(singleStartStr, line)) { + withinSingleLineComment = !inComment; + } else { + bool isGlobalDocComment = compareForth(globalStartStr, line); + bool isDocComment = compareForth(docStartStr, line); + bool isComment = compareForth(startStr, line); + if (isGlobalDocComment || isDocComment || isComment) { + commentStartLine = currentLine; + commentStartIndex = currentIndex; + if (isGlobalDocComment) + withinGlobalDocComment = true; + else if (isDocComment) + withinDocComment = true; + else + withinComment = true; + } + } + inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); } - - inComment = inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); - - line[currentIndex].mMultiLineComment = inComment; + line[currentIndex].mGlobalDocComment = withinGlobalDocComment; + line[currentIndex].mDocComment = withinDocComment; + line[currentIndex].mMultiLineComment = withinComment; line[currentIndex].mComment = withinSingleLineComment; + line[currentIndex].mDeactivated = withinNotDef; auto &endStr = mLanguageDefinition.mCommentEnd; - if (currentIndex + 1 >= (int)endStr.size() && - equals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred)) { - commentStartIndex = endIndex; + if (compareBack(endStr, line)) { + withinComment = false; + withinDocComment = false; + withinGlobalDocComment = false; commentStartLine = endLine; + commentStartIndex = endIndex; } } } @@ -2093,6 +2692,7 @@ void TextEditor::ColorizeInternal() { ++currentLine; } } + mDefines.clear(); mCheckComments = false; } @@ -2143,30 +2743,34 @@ void TextEditor::EnsureCursorVisible() { float scrollX = ImGui::GetScrollX(); float scrollY = ImGui::GetScrollY(); - auto height = ImGui::GetWindowHeight(); - auto width = ImGui::GetWindowWidth(); + auto windowPadding = ImGui::GetStyle().WindowPadding * 2.0f; - auto top = 1 + (int)ceil(scrollY / mCharAdvance.y); + auto height = ImGui::GetWindowHeight() - mTopMargin - windowPadding.y; + auto width = ImGui::GetWindowWidth() - windowPadding.x; + + auto top = (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 left = scrollX; + auto right = scrollX + width; auto pos = GetActualCursorCoordinates(); auto len = TextDistanceToLineStart(pos); - if (pos.mLine < top) + if (pos.mLine <= top + 1) 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) + if (pos.mLine >= bottom - 2) + ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 2) * mCharAdvance.y - height)); + if (len == 0) + ImGui::SetScrollX(0); + else if (len + mTextStart <= left + 4) ImGui::SetScrollX(std::max(0.0f, len + mTextStart - 4)); - if (len + mTextStart > right - 4) + if (len + mTextStart + mCharAdvance.x * 2 >= right - 4) ImGui::SetScrollX(std::max(0.0f, len + mTextStart + 4 - width + mCharAdvance.x * 2)); } int TextEditor::GetPageSize() const { - auto height = ImGui::GetWindowHeight() - 20.0f; + auto height = ImGui::GetWindowHeight() - 20.0f - mTopMargin; return (int)floor(height / mCharAdvance.y); } @@ -2231,7 +2835,9 @@ bool TokenizeCStyleString(const char *in_begin, const char *in_end, const char * } // handle escape character for " - if (*p == '\\' && p + 1 < in_end && p[1] == '"') + if (*p == '\\' && p + 1 < in_end && p[1] == '\\') + p++; + else if (*p == '\\' && p + 1 < in_end && p[1] == '"') p++; p++; diff --git a/lib/third_party/imgui/fonts/include/fonts/blendericons_font.h b/lib/third_party/imgui/fonts/include/fonts/blendericons_font.h index 13f8b0353..3a282a4a5 100644 --- a/lib/third_party/imgui/fonts/include/fonts/blendericons_font.h +++ b/lib/third_party/imgui/fonts/include/fonts/blendericons_font.h @@ -6,14 +6,15 @@ extern const unsigned int blendericons_compressed_size; extern const unsigned int blendericons_compressed_data[]; -#define ICON_MIN_BI 0xea00 -#define ICON_MAX_BI 0xea08 +#define ICON_MIN_BI 0xea00 +#define ICON_MAX_BI 0xea09 #define ICON_BI_CUBE "\xee\xa8\x80" //< U+ea00 -#define ICON_BI_EMPTY_ARROWS "\xee\xa8\x81" //< U+ea01 -#define ICON_BI_GRID "\xee\xa8\x82" //< U+ea02 -#define ICON_BI_MESH_GRID "\xee\xa8\x83" //< U+ea03 -#define ICON_BI_MOD_SOLIDIFY "\xee\xa8\x84" //< U+ea04 -#define ICON_BI_ORIENTATION_GLOBAL "\xee\xa8\x85" //< U+ea05 -#define ICON_BI_ORIENTATION_LOCAL "\xee\xa8\x86" //< U+ea06 -#define ICON_BI_VIEW_ORTHO "\xee\xa8\x87" //< U+ea07 -#define ICON_BI_VIEW_PERSPECTIVE "\xee\xa8\x88" //< U+ea08 +#define ICON_BI_DATA_TRANSFER_BOTH "\xee\xa8\x81" //< U+ea01 +#define ICON_BI_EMPTY_ARROWS "\xee\xa8\x82" //< U+ea02 +#define ICON_BI_GRID "\xee\xa8\x83" //< U+ea03 +#define ICON_BI_MESH_GRID "\xee\xa8\x84" //< U+ea04 +#define ICON_BI_MOD_SOLIDIFY "\xee\xa8\x85" //< U+ea05 +#define ICON_BI_ORIENTATION_GLOBAL "\xee\xa8\x86" //< U+ea06 +#define ICON_BI_ORIENTATION_LOCAL "\xee\xa8\x87" //< U+ea07 +#define ICON_BI_VIEW_ORTHO "\xee\xa8\x88" //< U+ea08 +#define ICON_BI_VIEW_PERSPECTIVE "\xee\xa8\x89" //< U+ea09 diff --git a/plugins/builtin/include/content/views/view_pattern_editor.hpp b/plugins/builtin/include/content/views/view_pattern_editor.hpp index 312ba0993..691f89709 100644 --- a/plugins/builtin/include/content/views/view_pattern_editor.hpp +++ b/plugins/builtin/include/content/views/view_pattern_editor.hpp @@ -170,7 +170,7 @@ namespace hex::plugin::builtin { u32 color; }; - std::unique_ptr m_parserRuntime; + std::unique_ptr m_editorRuntime; PerProvider> m_possiblePatternFiles; bool m_runAutomatically = false; @@ -220,6 +220,13 @@ namespace hex::plugin::builtin { std::array m_accessHistory = {}; u32 m_accessHistoryIndex = 0; + bool replace = false; + static inline std::array m_findHistory; + static inline u32 m_findHistorySize = 0; + static inline u32 m_findHistoryIndex = 0; + static inline std::array m_replaceHistory; + static inline u32 m_replaceHistorySize = 0; + static inline u32 m_replaceHistoryIndex = 0; private: void drawConsole(ImVec2 size); @@ -231,6 +238,10 @@ namespace hex::plugin::builtin { void drawPatternTooltip(pl::ptrn::Pattern *pattern); + void drawFindReplaceDialog(std::string &findWord, bool &requestFocus, u64 &position, u64 &count, bool &updateCount); + + void historyInsert(std::array &history,u32 &size, u32 &index, const std::string &value); + void loadPatternFile(const std::fs::path &path, prv::Provider *provider); void parsePattern(const std::string &code, prv::Provider *provider); diff --git a/plugins/builtin/romfs/fonts/blendericons.ttf b/plugins/builtin/romfs/fonts/blendericons.ttf index 45e095c53..c354c2bd4 100644 Binary files a/plugins/builtin/romfs/fonts/blendericons.ttf and b/plugins/builtin/romfs/fonts/blendericons.ttf differ diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 2ead55a24..8e99e3a67 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -686,6 +686,7 @@ "hex.builtin.view.find.context.replace.hex": "Hex", "hex.builtin.view.find.demangled": "Demangled", "hex.builtin.view.find.name": "Find", + "hex.builtin.view.replace.name": "Replace", "hex.builtin.view.find.regex": "Regex", "hex.builtin.view.find.regex.full_match": "Require full match", "hex.builtin.view.find.regex.pattern": "Pattern", @@ -870,6 +871,8 @@ "hex.builtin.view.pattern_editor.debugger.scope.global": "Global Scope", "hex.builtin.view.pattern_editor.env_vars": "Environment Variables", "hex.builtin.view.pattern_editor.evaluating": "Evaluating...", + "hex.builtin.view.pattern_editor.find_hint": "Find", + "hex.builtin.view.pattern_editor.find_hint_history": " for history)", "hex.builtin.view.pattern_editor.menu.edit.place_pattern": "Place pattern...", "hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin": "Built-in Type", "hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.array": "Array", @@ -877,9 +880,20 @@ "hex.builtin.view.pattern_editor.menu.edit.place_pattern.custom": "Custom Type", "hex.builtin.view.pattern_editor.menu.file.load_pattern": "Load pattern...", "hex.builtin.view.pattern_editor.menu.file.save_pattern": "Save pattern...", + "hex.builtin.view.pattern_editor.menu.find": "Find ...", + "hex.builtin.view.pattern_editor.menu.find_next": "Find Next", + "hex.builtin.view.pattern_editor.menu.find_previous": "Find Previous", + "hex.builtin.view.pattern_editor.menu.replace": "Replace ...", + "hex.builtin.view.pattern_editor.menu.replace_next": "Replace Next", + "hex.builtin.view.pattern_editor.menu.replace_previous": "Replace Previous", + "hex.builtin.view.pattern_editor.menu.replace_all": "Replace All", "hex.builtin.view.pattern_editor.name": "Pattern editor", "hex.builtin.view.pattern_editor.no_in_out_vars": "Define some global variables with the 'in' or 'out' specifier for them to appear here.", + "hex.builtin.view.pattern_editor.no_results": "no results", + "hex.builtin.view.pattern_editor.of": "of", "hex.builtin.view.pattern_editor.open_pattern": "Open pattern", + "hex.builtin.view.pattern_editor.replace_hint": "Replace", + "hex.builtin.view.pattern_editor.replace_hint_history": " for history)", "hex.builtin.view.pattern_editor.section_popup": "Section", "hex.builtin.view.pattern_editor.sections": "Sections", "hex.builtin.view.pattern_editor.settings": "Settings", diff --git a/plugins/builtin/source/content/themes.cpp b/plugins/builtin/source/content/themes.cpp index 82ab444bb..1e317b77a 100644 --- a/plugins/builtin/source/content/themes.cpp +++ b/plugins/builtin/source/content/themes.cpp @@ -225,8 +225,11 @@ namespace hex::plugin::builtin { { "identifier", u32(TextEditor::PaletteIndex::Identifier) }, { "known-identifier", u32(TextEditor::PaletteIndex::KnownIdentifier) }, { "preproc-identifier", u32(TextEditor::PaletteIndex::PreprocIdentifier) }, + { "global-doc-comment", u32(TextEditor::PaletteIndex::GlobalDocComment) }, + { "doc-comment", u32(TextEditor::PaletteIndex::DocComment) }, { "comment", u32(TextEditor::PaletteIndex::Comment) }, { "multi-line-comment", u32(TextEditor::PaletteIndex::MultiLineComment) }, + { "preprocessor-deactivated", u32(TextEditor::PaletteIndex::PreprocessorDeactivated) }, { "background", u32(TextEditor::PaletteIndex::Background) }, { "cursor", u32(TextEditor::PaletteIndex::Cursor) }, { "selection", u32(TextEditor::PaletteIndex::Selection) }, diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index ff116e0ce..33cfc9676 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -1,4 +1,5 @@ #include "content/views/view_pattern_editor.hpp" +#include "fonts/blendericons_font.h" #include #include @@ -173,8 +174,8 @@ namespace hex::plugin::builtin { } ViewPatternEditor::ViewPatternEditor() : View::Window("hex.builtin.view.pattern_editor.name", ICON_VS_SYMBOL_NAMESPACE) { - m_parserRuntime = std::make_unique(); - ContentRegistry::PatternLanguage::configureRuntime(*m_parserRuntime, nullptr); + m_editorRuntime = std::make_unique(); + ContentRegistry::PatternLanguage::configureRuntime(*m_editorRuntime, nullptr); m_textEditor.SetLanguageDefinition(PatternLanguage()); m_textEditor.SetShowWhitespaces(false); @@ -216,11 +217,13 @@ namespace hex::plugin::builtin { textEditorSize.y = std::clamp(textEditorSize.y, 1.0F, std::max(1.0F, availableSize.y - ImGui::GetTextLineHeightWithSpacing() * 3)); m_textEditor.Render("hex.builtin.view.pattern_editor.name"_lang, textEditorSize, true); - + TextEditor::FindReplaceHandler *findReplaceHandler = m_textEditor.GetFindReplaceHandler(); if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { ImGui::OpenPopup("##pattern_editor_context_menu"); } + bool clickedMenuFind = false; + bool clickedMenuReplace = false; if (ImGui::BeginPopup("##pattern_editor_context_menu")) { const bool hasSelection = m_textEditor.HasSelection(); if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.cut"_lang, Shortcut(CTRLCMD + Keys::X).toString().c_str(), false, hasSelection)) { @@ -242,9 +245,140 @@ namespace hex::plugin::builtin { m_textEditor.Redo(); } + ImGui::Separator(); + // Search and replace entries + if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.find"_lang, Shortcut(CTRLCMD + Keys::F).toString().c_str(),false,this->m_textEditor.HasSelection())) + clickedMenuFind = true; + + if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.find_next"_lang, Shortcut(Keys::F3).toString().c_str(),false,!findReplaceHandler->GetFindWord().empty())) + findReplaceHandler->FindMatch(&m_textEditor,true); + + if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.find_previous"_lang, Shortcut(SHIFT + Keys::F3).toString().c_str(),false,!findReplaceHandler->GetFindWord().empty())) + findReplaceHandler->FindMatch(&m_textEditor,false); + + if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.replace"_lang, Shortcut(CTRLCMD + Keys::H).toString().c_str(),false,!findReplaceHandler->GetReplaceWord().empty())) + clickedMenuReplace = true; + + if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.replace_next"_lang,"",false,!findReplaceHandler->GetReplaceWord().empty())) + findReplaceHandler->Replace(&m_textEditor,true); + + if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.replace_previous"_lang, "",false,!findReplaceHandler->GetReplaceWord().empty())) + findReplaceHandler->Replace(&m_textEditor,false); + + if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.replace_all"_lang, "",false,!findReplaceHandler->GetReplaceWord().empty())) + findReplaceHandler->ReplaceAll(&m_textEditor); + ImGui::EndPopup(); } + // Context menu entries that open the find/replace popup + bool openFindPopup = false; + ImGui::PushID(&this->m_textEditor); + if (clickedMenuFind) { + replace = false; + openFindPopup = true; + } + + if (clickedMenuReplace) { + replace = true; + openFindPopup = true; + } + + // shortcuts to open the find/replace popup + if (ImGui::IsItemHovered()) { + if (ImGui::IsKeyPressed(ImGuiKey_F, false) && ImGui::GetIO().KeyCtrl) { + replace = false; + openFindPopup = true; + } + + if (ImGui::IsKeyPressed(ImGuiKey_H, false) && ImGui::GetIO().KeyCtrl) { + replace = true; + openFindPopup = true; + } + } + + static std::string findWord; + static bool requestFocus = false; + static u64 position = 0; + static u64 count = 0; + static bool updateCount = false; + + if (openFindPopup) { + // Place the popup at the top right of the window + auto windowSize = ImGui::GetWindowSize(); + auto style = ImGui::GetStyle(); + + // Set the scrollbar size only if it is visible + float scrollbarSize = 0; + + // Calculate the number of lines to display in the text editor + auto totalTextHeight = m_textEditor.GetTotalLines() * m_textEditor.GetCharAdvance().y; + + // Compare it to the window height + if (totalTextHeight > windowSize.y) + scrollbarSize = style.ScrollbarSize; + + auto labelSize = ImGui::CalcTextSize(ICON_VS_WHOLE_WORD); + + // Six icon buttons + auto popupSize = ImVec2({(labelSize.x + style.FramePadding.x * 2.0F) * 6.0F, + labelSize.y + style.FramePadding.y * 2.0F + style.WindowPadding.y + 3 }); + + // 2 * 11 spacings in between elements + popupSize.x += style.FramePadding.x * 22.0f; + + // Text input fields are set to 12 characters wide + popupSize.x += ImGui::GetFontSize() * 12.0f; + + // IndexOfCount text + popupSize.x += ImGui::CalcTextSize("2000 of 2000").x + 2.0f; + popupSize.x += scrollbarSize; + + // Position of popup relative to parent window + ImVec2 windowPosForPopup = ImGui::GetWindowPos(); + + // Not the window height but the content height + windowPosForPopup.y += popupSize.y; + + // Add remaining window height + popupSize.y += style.WindowPadding.y + 1; + + // Move to the right so as not to overlap the scrollbar + windowPosForPopup.x += windowSize.x - popupSize.x; + findReplaceHandler->SetFindWindowPos(windowPosForPopup); + + if (replace) { + // Remove one window padding + popupSize.y -= style.WindowPadding.y; + // Add the replace window height + popupSize.y *= 2; + } + + ImGui::SetNextWindowPos(windowPosForPopup); + ImGui::SetNextWindowSize(popupSize); + ImGui::OpenPopup("##pattern_editor_find_replace"); + + findReplaceHandler->resetMatches(); + + // Use selection as find word if there is one, otherwise use the word under the cursor + if (!this->m_textEditor.HasSelection()) + this->m_textEditor.SelectWordUnderCursor(); + + findWord = this->m_textEditor.GetSelectedText(); + + // Find all matches to findWord + findReplaceHandler->FindAllMatches(&m_textEditor,findWord); + position = findReplaceHandler->FindPosition(&m_textEditor,m_textEditor.GetCursorPosition(), true); + count = findReplaceHandler->GetMatches().size(); + findReplaceHandler->SetFindWord(&m_textEditor,findWord); + requestFocus = true; + updateCount = true; + } + + drawFindReplaceDialog(findWord, requestFocus, position, count, updateCount); + + ImGui::PopID(); + ImGui::Button("##settings_drag_bar", ImVec2(ImGui::GetContentRegionAvail().x, 2_scaled)); if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0)) { if (ImGui::IsItemHovered()) @@ -425,6 +559,309 @@ namespace hex::plugin::builtin { View::discardNavigationRequests(); } + void ViewPatternEditor::historyInsert(std::array &history, u32 &size, u32 &index, const std::string &value) { + for (u64 i = 0; i < size; i++) { + if (history[i] == value) + return; + } + + if (size < 256){ + history[size] = value; + size++; + } else { + index = (index - 1) % 256; + history[index] = value; + index = (index + 1) % 256; + } + } + + void ViewPatternEditor::drawFindReplaceDialog(std::string &findWord, bool &requestFocus, u64 &position, u64 &count, bool &updateCount) { + static std::string replaceWord; + + TextEditor::FindReplaceHandler *findReplaceHandler = m_textEditor.GetFindReplaceHandler(); + static bool requestFocusReplace = false; + static bool requestFocusFind = false; + + static bool downArrowFind = false; + static bool upArrowFind = false; + static bool downArrowReplace = false; + static bool upArrowReplace = false; + static bool enterPressedFind = false; + + bool enter = ImGui::IsKeyPressed(ImGuiKey_Enter, false) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter, false); + bool upArrow = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false) || ImGui::IsKeyPressed(ImGuiKey_Keypad8, false); + bool downArrow = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false) || ImGui::IsKeyPressed(ImGuiKey_Keypad2, false); + bool shift = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); + bool alt = ImGui::IsKeyDown(ImGuiKey_LeftAlt) || ImGui::IsKeyDown(ImGuiKey_RightAlt); + + if (ImGui::BeginPopup("##pattern_editor_find_replace")) { + + if (ImGui::BeginTable("##pattern_editor_find_replace_table", 2, + ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_BordersInnerH)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + bool oldReplace = replace; + ImGuiExt::DimmedIconToggle(ICON_VS_TRIANGLE_DOWN, ICON_VS_TRIANGLE_RIGHT, &replace); + if (oldReplace != replace) { + if (replace) + requestFocusReplace = true; + else + requestFocusFind = true; + } + + ImGui::TableNextColumn(); + + static int findFlags = ImGuiInputTextFlags_None; + if (requestFocus && m_findHistoryIndex == m_findHistorySize) + findFlags |= ImGuiInputTextFlags_AutoSelectAll; + else + findFlags &= ~ImGuiInputTextFlags_AutoSelectAll; + + if (m_findHistoryIndex != m_findHistorySize && requestFocusFind ) { + findFlags |= ImGuiInputTextFlags_ReadOnly; + } else + findFlags &= ~ImGuiInputTextFlags_ReadOnly; + + std::string hint = "hex.builtin.view.pattern_editor.find_hint"_lang.operator std::string(); + if (m_findHistorySize > 0) { + hint += " ("; + hint += ICON_BI_DATA_TRANSFER_BOTH; + hint += "hex.builtin.view.pattern_editor.find_hint_history"_lang.operator std::string(); + } + + ImGui::PushItemWidth(ImGui::GetFontSize() * 12); + if (ImGui::InputTextWithHint("###findInputTextWidget", hint.c_str(), findWord, findFlags) || enter ) { + if (enter) { + enterPressedFind = true; + enter = false; + } + updateCount = true; + requestFocusFind = true; + findReplaceHandler->SetFindWord(&m_textEditor,findWord); + } + + if ((!replace && requestFocus) || requestFocusFind) { + ImGui::SetKeyboardFocusHere(-1); + requestFocus = false; + requestFocusFind = false; + } + + if (ImGui::IsItemActive() && (upArrow || downArrow) && m_findHistorySize > 0) { + + if (upArrow) + m_findHistoryIndex = (m_findHistoryIndex + m_findHistorySize - 1) % m_findHistorySize; + + if (downArrow) + m_findHistoryIndex = (m_findHistoryIndex + 1) % m_findHistorySize; + + findWord = m_findHistory[m_findHistoryIndex]; + findReplaceHandler->SetFindWord(&m_textEditor,findWord); + position = findReplaceHandler->FindPosition(&m_textEditor,m_textEditor.GetCursorPosition(), true); + count = findReplaceHandler->GetMatches().size(); + updateCount = true; + requestFocusFind = true; + } + ImGui::PopItemWidth(); + + ImGui::SameLine(); + + bool matchCase = findReplaceHandler->GetMatchCase(); + + // Allow Alt-C to toggle case sensitivity + bool altCPressed = ImGui::IsKeyPressed(ImGuiKey_C, false) && alt; + if (altCPressed || ImGuiExt::DimmedIconToggle(ICON_VS_CASE_SENSITIVE, &matchCase)) { + if (altCPressed) + matchCase = !matchCase; + findReplaceHandler->SetMatchCase(&m_textEditor,matchCase); + updateCount = true; + requestFocusFind = true; + } + + ImGui::SameLine(); + + bool matchWholeWord = findReplaceHandler->GetWholeWord(); + + // Allow Alt-W to toggle whole word + bool altWPressed = ImGui::IsKeyPressed(ImGuiKey_W, false) && alt; + if (altWPressed || ImGuiExt::DimmedIconToggle(ICON_VS_WHOLE_WORD, &matchWholeWord)) { + if (altWPressed) + matchWholeWord = !matchWholeWord; + findReplaceHandler->SetWholeWord(&m_textEditor,matchWholeWord); + position = findReplaceHandler->FindPosition(&m_textEditor,m_textEditor.GetCursorPosition(), true); + count = findReplaceHandler->GetMatches().size(); + updateCount = true; + requestFocusFind = true; + } + + ImGui::SameLine(); + + bool useRegex = findReplaceHandler->GetFindRegEx(); + + // Allow Alt-R to toggle regex + bool altRPressed = ImGui::IsKeyPressed(ImGuiKey_R, false) && alt; + if (altRPressed || ImGuiExt::DimmedIconToggle(ICON_VS_REGEX, &useRegex)) { + if (altRPressed) + useRegex = !useRegex; + findReplaceHandler->SetFindRegEx(&m_textEditor,useRegex); + position = findReplaceHandler->FindPosition(&m_textEditor,m_textEditor.GetCursorPosition(), true); + count = findReplaceHandler->GetMatches().size(); + updateCount = true; + requestFocusFind = true; + } + + static std::string counterString; + + auto totalSize = ImGui::CalcTextSize("2000 of 2000"); + ImVec2 buttonSize; + + if (updateCount) { + updateCount = false; + + if (count == 0 || position == std::numeric_limits::max()) + counterString = "hex.builtin.view.pattern_editor.no_results"_lang.operator std::string(); + else { + if (position > 1999) + counterString = "?"; + else + counterString = hex::format("{} ", position); + counterString += "hex.builtin.view.pattern_editor.of"_lang.operator const char *(); + if (count > 1999) + counterString += "1999+"; + else + counterString += hex::format(" {}", count); + } + } + auto resultSize = ImGui::CalcTextSize(counterString.c_str()); + if (totalSize.x > resultSize.x) + buttonSize = ImVec2(totalSize.x + 2 - resultSize.x, resultSize.y); + else + buttonSize = ImVec2(2, resultSize.y); + + ImGui::SameLine(); + ImGui::TextUnformatted(counterString.c_str()); + + ImGui::SameLine(); + ImGui::InvisibleButton("##find_result_padding", buttonSize); + + ImGui::SameLine(); + if (ImGuiExt::IconButton(ICON_VS_ARROW_DOWN, ImVec4(1, 1, 1, 1))) + downArrowFind = true; + + ImGui::SameLine(); + if (ImGuiExt::IconButton(ICON_VS_ARROW_UP, ImVec4(1, 1, 1, 1))) + upArrowFind = true; + + if (replace) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + + static int replaceFlags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll; + if (m_replaceHistoryIndex != m_replaceHistorySize && requestFocusReplace) { + replaceFlags |= ImGuiInputTextFlags_ReadOnly; + } else + replaceFlags &= ~ImGuiInputTextFlags_ReadOnly; + + hint = "hex.builtin.view.pattern_editor.replace_hint"_lang.operator std::string(); + if (m_replaceHistorySize > 0) { + hint += " ("; + hint += ICON_BI_DATA_TRANSFER_BOTH; + hint += "hex.builtin.view.pattern_editor.replace_hint_history"_lang.operator std::string(); + } + + ImGui::PushItemWidth(ImGui::GetFontSize() * 12); + if (ImGui::InputTextWithHint("##replaceInputTextWidget", hint.c_str(), replaceWord, replaceFlags) || downArrowReplace || upArrowReplace) { + findReplaceHandler->SetReplaceWord(replaceWord); + historyInsert(m_replaceHistory, m_replaceHistorySize, m_replaceHistoryIndex, replaceWord); + + bool textReplaced = findReplaceHandler->Replace(&m_textEditor,!shift && !upArrowReplace); + if (textReplaced) { + if (count > 0) { + if (position == count) + position -= 1; + count -= 1; + if (count == 0) + requestFocusFind = true; + else + requestFocusReplace = true; + } else + requestFocusFind = true; + updateCount = true; + } + + downArrowReplace = false; + upArrowReplace = false; + + if (enterPressedFind) { + enterPressedFind = false; + requestFocusFind = false; + } + } + + if (requestFocus || requestFocusReplace) { + ImGui::SetKeyboardFocusHere(-1); + requestFocus = false; + requestFocusReplace = false; + } + + if (ImGui::IsItemActive() && (upArrow || downArrow) && m_replaceHistorySize > 0) { + if (upArrow) + m_replaceHistoryIndex = (m_replaceHistoryIndex + m_replaceHistorySize - 1) % m_replaceHistorySize; + if (downArrow) + m_replaceHistoryIndex = (m_replaceHistoryIndex + 1) % m_replaceHistorySize; + + replaceWord = m_replaceHistory[m_replaceHistoryIndex]; + findReplaceHandler->SetReplaceWord(replaceWord); + + requestFocusReplace = true; + } + + ImGui::PopItemWidth(); + + ImGui::SameLine(); + if (ImGuiExt::IconButton(ICON_VS_FOLD_DOWN, ImVec4(1, 1, 1, 1))) + downArrowReplace = true; + + ImGui::SameLine(); + if (ImGuiExt::IconButton(ICON_VS_FOLD_UP, ImVec4(1, 1, 1, 1))) + upArrowReplace = true; + + ImGui::SameLine(); + if (ImGuiExt::IconButton(ICON_VS_REPLACE_ALL, ImVec4(1, 1, 1, 1))) { + findReplaceHandler->SetReplaceWord(replaceWord); + historyInsert(m_replaceHistory,m_replaceHistorySize, m_replaceHistoryIndex, replaceWord); + findReplaceHandler->ReplaceAll(&m_textEditor); + count = 0; + position = 0; + requestFocusFind = true; + updateCount = true; + } + findReplaceHandler->SetFindWindowSize(ImGui::GetWindowSize()); + } else + findReplaceHandler->SetFindWindowSize(ImGui::GetWindowSize()); + + if ((ImGui::IsKeyPressed(ImGuiKey_F3, false)) || downArrowFind || upArrowFind || enterPressedFind) { + historyInsert(m_findHistory, m_findHistorySize, m_findHistoryIndex, findWord); + position = findReplaceHandler->FindMatch(&m_textEditor,!shift && !upArrowFind); + count = findReplaceHandler->GetMatches().size(); + updateCount = true; + downArrowFind = false; + upArrowFind = false; + requestFocusFind = true; + enterPressedFind = false; + } + + ImGui::EndTable(); + } + // Escape key to close the popup + if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Escape), false)) + ImGui::CloseCurrentPopup(); + + ImGui::EndPopup(); + } + } + void ViewPatternEditor::drawConsole(ImVec2 size) { if (m_consoleNeedsUpdate) { std::scoped_lock lock(m_logMutex); @@ -1049,8 +1486,8 @@ namespace hex::plugin::builtin { void ViewPatternEditor::parsePattern(const std::string &code, prv::Provider *provider) { m_runningParsers += 1; - ContentRegistry::PatternLanguage::configureRuntime(*m_parserRuntime, nullptr); - const auto &ast = m_parserRuntime->parseString(code); + ContentRegistry::PatternLanguage::configureRuntime(*m_editorRuntime, nullptr); + const auto &ast = m_editorRuntime->parseString(code); auto &patternVariables = m_patternVariables.get(provider); auto oldPatternVariables = std::move(patternVariables); @@ -1344,7 +1781,7 @@ namespace hex::plugin::builtin { ImGui::EndMenu(); } - const auto &types = m_parserRuntime->getInternals().parser->getTypes(); + const auto &types = m_editorRuntime->getInternals().parser->getTypes(); const bool hasPlaceableTypes = std::ranges::any_of(types, [](const auto &type) { return !type.second->isTemplateType(); }); if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.custom"_lang, hasPlaceableTypes)) {