1
0
mirror of synced 2025-01-31 03:53:44 +01:00

feat: Added find-replace to pattern editor (#1465)

This commit is contained in:
paxcut 2024-01-27 14:48:45 +01:00 committed by WerWolv
parent 823881f7f1
commit 1957d6f432
8 changed files with 1248 additions and 92 deletions

View File

@ -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<Glyph> 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<EditorState> 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<std::string> mDefines;
float mLastClick;
bool mShowCursor;

View File

@ -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::milliseconds>(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::milliseconds>(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<bool> 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<Glyph> &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<Glyph> &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++;

View File

@ -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

View File

@ -170,7 +170,7 @@ namespace hex::plugin::builtin {
u32 color;
};
std::unique_ptr<pl::PatternLanguage> m_parserRuntime;
std::unique_ptr<pl::PatternLanguage> m_editorRuntime;
PerProvider<std::vector<std::fs::path>> m_possiblePatternFiles;
bool m_runAutomatically = false;
@ -220,6 +220,13 @@ namespace hex::plugin::builtin {
std::array<AccessData, 512> m_accessHistory = {};
u32 m_accessHistoryIndex = 0;
bool replace = false;
static inline std::array<std::string,256> m_findHistory;
static inline u32 m_findHistorySize = 0;
static inline u32 m_findHistoryIndex = 0;
static inline std::array<std::string,256> 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<std::string,256> &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);

View File

@ -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",

View File

@ -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) },

View File

@ -1,4 +1,5 @@
#include "content/views/view_pattern_editor.hpp"
#include "fonts/blendericons_font.h"
#include <hex/api/content_registry.hpp>
#include <hex/api/project_file_manager.hpp>
@ -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<pl::PatternLanguage>();
ContentRegistry::PatternLanguage::configureRuntime(*m_parserRuntime, nullptr);
m_editorRuntime = std::make_unique<pl::PatternLanguage>();
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<std::string,256> &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<u64>::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)) {