feat: Added find-replace to pattern editor (#1465)
This commit is contained in:
parent
823881f7f1
commit
1957d6f432
@ -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;
|
||||
|
@ -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++;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
Binary file not shown.
@ -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",
|
||||
|
@ -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) },
|
||||
|
@ -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)) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user