feat: Added find-replace to pattern editor (#1465)
This commit is contained in:
parent
823881f7f1
commit
1957d6f432
@ -25,8 +25,11 @@ public:
|
|||||||
Identifier,
|
Identifier,
|
||||||
KnownIdentifier,
|
KnownIdentifier,
|
||||||
PreprocIdentifier,
|
PreprocIdentifier,
|
||||||
|
GlobalDocComment,
|
||||||
|
DocComment,
|
||||||
Comment,
|
Comment,
|
||||||
MultiLineComment,
|
MultiLineComment,
|
||||||
|
PreprocessorDeactivated,
|
||||||
Background,
|
Background,
|
||||||
Cursor,
|
Cursor,
|
||||||
Selection,
|
Selection,
|
||||||
@ -140,9 +143,12 @@ public:
|
|||||||
bool mComment : 1;
|
bool mComment : 1;
|
||||||
bool mMultiLineComment : 1;
|
bool mMultiLineComment : 1;
|
||||||
bool mPreprocessor : 1;
|
bool mPreprocessor : 1;
|
||||||
|
bool mDocComment : 1;
|
||||||
|
bool mGlobalDocComment : 1;
|
||||||
|
bool mDeactivated : 1;
|
||||||
|
|
||||||
Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex),
|
Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex), mComment(false),
|
||||||
mComment(false), mMultiLineComment(false), mPreprocessor(false) {}
|
mMultiLineComment(false), mPreprocessor(false), mDocComment(false), mGlobalDocComment(false), mDeactivated(false) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector<Glyph> Line;
|
typedef std::vector<Glyph> Line;
|
||||||
@ -158,7 +164,7 @@ public:
|
|||||||
Keywords mKeywords;
|
Keywords mKeywords;
|
||||||
Identifiers mIdentifiers;
|
Identifiers mIdentifiers;
|
||||||
Identifiers mPreprocIdentifiers;
|
Identifiers mPreprocIdentifiers;
|
||||||
std::string mCommentStart, mCommentEnd, mSingleLineComment;
|
std::string mCommentStart, mCommentEnd, mSingleLineComment, mGlobalDocComment, mDocComment;
|
||||||
char mPreprocChar;
|
char mPreprocChar;
|
||||||
bool mAutoIndentation;
|
bool mAutoIndentation;
|
||||||
|
|
||||||
@ -169,7 +175,7 @@ public:
|
|||||||
bool mCaseSensitive;
|
bool mCaseSensitive;
|
||||||
|
|
||||||
LanguageDefinition()
|
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 GetSelectedText() const;
|
||||||
std::string GetCurrentLineText()const;
|
std::string GetCurrentLineText()const;
|
||||||
|
class FindReplaceHandler;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FindReplaceHandler *GetFindReplaceHandler() { return &mFindReplaceHandler; }
|
||||||
int GetTotalLines() const { return (int)mLines.size(); }
|
int GetTotalLines() const { return (int)mLines.size(); }
|
||||||
bool IsOverwrite() const { return mOverwrite; }
|
bool IsOverwrite() const { return mOverwrite; }
|
||||||
|
|
||||||
@ -260,6 +269,8 @@ public:
|
|||||||
void Paste();
|
void Paste();
|
||||||
void Delete();
|
void Delete();
|
||||||
|
|
||||||
|
ImVec2 &GetCharAdvance() { return mCharAdvance; }
|
||||||
|
|
||||||
bool CanUndo() const;
|
bool CanUndo() const;
|
||||||
bool CanRedo() const;
|
bool CanRedo() const;
|
||||||
void Undo(int aSteps = 1);
|
void Undo(int aSteps = 1);
|
||||||
@ -279,6 +290,75 @@ private:
|
|||||||
Coordinates mCursorPosition;
|
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
|
class UndoRecord
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -335,6 +415,8 @@ private:
|
|||||||
int GetCharacterIndex(const Coordinates& aCoordinates) const;
|
int GetCharacterIndex(const Coordinates& aCoordinates) const;
|
||||||
int GetCharacterColumn(int aLine, int aIndex) const;
|
int GetCharacterColumn(int aLine, int aIndex) const;
|
||||||
int GetLineCharacterCount(int aLine) const;
|
int GetLineCharacterCount(int aLine) const;
|
||||||
|
unsigned long long GetLineByteCount(int aLine) const;
|
||||||
|
int GetStringCharacterCount(std::string str) const;
|
||||||
int GetLineMaxColumn(int aLine) const;
|
int GetLineMaxColumn(int aLine) const;
|
||||||
bool IsOnWordBoundary(const Coordinates& aAt) const;
|
bool IsOnWordBoundary(const Coordinates& aAt) const;
|
||||||
void RemoveLine(int aStart, int aEnd);
|
void RemoveLine(int aStart, int aEnd);
|
||||||
@ -356,6 +438,8 @@ private:
|
|||||||
EditorState mState;
|
EditorState mState;
|
||||||
UndoBuffer mUndoBuffer;
|
UndoBuffer mUndoBuffer;
|
||||||
int mUndoIndex;
|
int mUndoIndex;
|
||||||
|
bool mScrollToBottom;
|
||||||
|
float mTopMargin;
|
||||||
|
|
||||||
int mTabSize;
|
int mTabSize;
|
||||||
bool mOverwrite;
|
bool mOverwrite;
|
||||||
@ -379,7 +463,6 @@ private:
|
|||||||
Palette mPalette;
|
Palette mPalette;
|
||||||
LanguageDefinition mLanguageDefinition;
|
LanguageDefinition mLanguageDefinition;
|
||||||
RegexList mRegexList;
|
RegexList mRegexList;
|
||||||
|
|
||||||
bool mCheckComments;
|
bool mCheckComments;
|
||||||
Breakpoints mBreakpoints;
|
Breakpoints mBreakpoints;
|
||||||
ErrorMarkers mErrorMarkers;
|
ErrorMarkers mErrorMarkers;
|
||||||
@ -387,6 +470,7 @@ private:
|
|||||||
Coordinates mInteractiveStart, mInteractiveEnd;
|
Coordinates mInteractiveStart, mInteractiveEnd;
|
||||||
std::string mLineBuffer;
|
std::string mLineBuffer;
|
||||||
uint64_t mStartTime;
|
uint64_t mStartTime;
|
||||||
|
std::vector<std::string> mDefines;
|
||||||
|
|
||||||
float mLastClick;
|
float mLastClick;
|
||||||
bool mShowCursor;
|
bool mShowCursor;
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
#include "imgui.h" // for imGui::GetCurrentWindow()
|
#include "imgui.h" // for imGui::GetCurrentWindow()
|
||||||
|
#include "imgui_internal.h"
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
// - multiline comments vs single-line: latter is blocking start of a ML
|
// - 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::Palette TextEditor::sPaletteBase = TextEditor::GetDarkPalette();
|
||||||
|
|
||||||
|
TextEditor::FindReplaceHandler::FindReplaceHandler() : mWholeWord(false),mFindRegEx(false),mMatchCase(false) {}
|
||||||
|
|
||||||
TextEditor::TextEditor()
|
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());
|
SetLanguageDefinition(LanguageDefinition::HLSL());
|
||||||
mLines.push_back(Line());
|
mLines.push_back(Line());
|
||||||
}
|
}
|
||||||
@ -442,6 +445,15 @@ int TextEditor::GetCharacterColumn(int aLine, int aIndex) const {
|
|||||||
return col;
|
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 {
|
int TextEditor::GetLineCharacterCount(int aLine) const {
|
||||||
if (aLine >= mLines.size())
|
if (aLine >= mLines.size())
|
||||||
return 0;
|
return 0;
|
||||||
@ -452,6 +464,13 @@ int TextEditor::GetLineCharacterCount(int aLine) const {
|
|||||||
return c;
|
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 {
|
int TextEditor::GetLineMaxColumn(int aLine) const {
|
||||||
if (aLine >= mLines.size())
|
if (aLine >= mLines.size())
|
||||||
return 0;
|
return 0;
|
||||||
@ -577,10 +596,16 @@ std::string TextEditor::GetWordAt(const Coordinates &aCoords) const {
|
|||||||
ImU32 TextEditor::GetGlyphColor(const Glyph &aGlyph) const {
|
ImU32 TextEditor::GetGlyphColor(const Glyph &aGlyph) const {
|
||||||
if (!mColorizerEnabled)
|
if (!mColorizerEnabled)
|
||||||
return mPalette[(int)PaletteIndex::Default];
|
return mPalette[(int)PaletteIndex::Default];
|
||||||
|
if (aGlyph.mGlobalDocComment)
|
||||||
|
return mPalette[(int)PaletteIndex::GlobalDocComment];
|
||||||
|
if (aGlyph.mDocComment)
|
||||||
|
return mPalette[(int)PaletteIndex::DocComment];
|
||||||
if (aGlyph.mComment)
|
if (aGlyph.mComment)
|
||||||
return mPalette[(int)PaletteIndex::Comment];
|
return mPalette[(int)PaletteIndex::Comment];
|
||||||
if (aGlyph.mMultiLineComment)
|
if (aGlyph.mMultiLineComment)
|
||||||
return mPalette[(int)PaletteIndex::MultiLineComment];
|
return mPalette[(int)PaletteIndex::MultiLineComment];
|
||||||
|
if (aGlyph.mDeactivated && !aGlyph.mPreprocessor)
|
||||||
|
return mPalette[(int)PaletteIndex::PreprocessorDeactivated];
|
||||||
auto const color = mPalette[(int)aGlyph.mColorIndex];
|
auto const color = mPalette[(int)aGlyph.mColorIndex];
|
||||||
if (aGlyph.mPreprocessor) {
|
if (aGlyph.mPreprocessor) {
|
||||||
const auto ppcolor = mPalette[(int)PaletteIndex::Preprocessor];
|
const auto ppcolor = mPalette[(int)PaletteIndex::Preprocessor];
|
||||||
@ -671,6 +696,16 @@ void TextEditor::HandleKeyboardInputs() {
|
|||||||
EnterCharacter('\n', false);
|
EnterCharacter('\n', false);
|
||||||
else if (!IsReadOnly() && !ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab)))
|
else if (!IsReadOnly() && !ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab)))
|
||||||
EnterCharacter('\t', shift);
|
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()) {
|
if (!IsReadOnly() && !io.InputQueueCharacters.empty()) {
|
||||||
for (int i = 0; i < io.InputQueueCharacters.Size; i++) {
|
for (int i = 0; i < io.InputQueueCharacters.Size; i++) {
|
||||||
@ -691,7 +726,7 @@ void TextEditor::HandleMouseInputs() {
|
|||||||
auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
|
auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
|
||||||
|
|
||||||
if (ImGui::IsWindowHovered()) {
|
if (ImGui::IsWindowHovered()) {
|
||||||
if (!shift && !alt) {
|
if (!alt) {
|
||||||
auto click = ImGui::IsMouseClicked(0);
|
auto click = ImGui::IsMouseClicked(0);
|
||||||
auto doubleClick = ImGui::IsMouseDoubleClicked(0);
|
auto doubleClick = ImGui::IsMouseDoubleClicked(0);
|
||||||
auto t = ImGui::GetTime();
|
auto t = ImGui::GetTime();
|
||||||
@ -732,11 +767,16 @@ void TextEditor::HandleMouseInputs() {
|
|||||||
Left mouse button click
|
Left mouse button click
|
||||||
*/
|
*/
|
||||||
else if (click) {
|
else if (click) {
|
||||||
|
if (ctrl) {
|
||||||
mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
|
mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
|
||||||
if (ctrl)
|
|
||||||
mSelectionMode = SelectionMode::Word;
|
mSelectionMode = SelectionMode::Word;
|
||||||
else
|
} else if (shift) {
|
||||||
mSelectionMode = SelectionMode::Normal;
|
mSelectionMode = SelectionMode::Normal;
|
||||||
|
mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
|
||||||
|
} else {
|
||||||
|
mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos());
|
||||||
|
mSelectionMode = SelectionMode::Normal;
|
||||||
|
}
|
||||||
SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
|
SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
|
||||||
|
|
||||||
mLastClick = (float)ImGui::GetTime();
|
mLastClick = (float)ImGui::GetTime();
|
||||||
@ -752,7 +792,7 @@ void TextEditor::HandleMouseInputs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TextEditor::Render() {
|
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;
|
const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x;
|
||||||
mCharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * mLineSpacing);
|
mCharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * mLineSpacing);
|
||||||
|
|
||||||
@ -765,7 +805,7 @@ void TextEditor::Render() {
|
|||||||
|
|
||||||
assert(mLineBuffer.empty());
|
assert(mLineBuffer.empty());
|
||||||
|
|
||||||
auto contentSize = ImGui::GetWindowContentRegionMax();
|
auto contentSize = ImGui::GetWindowContentRegionMax() - ImVec2(0,mTopMargin);
|
||||||
auto drawList = ImGui::GetWindowDrawList();
|
auto drawList = ImGui::GetWindowDrawList();
|
||||||
float longest(mTextStart);
|
float longest(mTextStart);
|
||||||
|
|
||||||
@ -774,13 +814,18 @@ void TextEditor::Render() {
|
|||||||
ImGui::SetScrollY(0.f);
|
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 scrollX = ImGui::GetScrollX();
|
||||||
auto scrollY = ImGui::GetScrollY();
|
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 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
|
// Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width
|
||||||
char buf[16];
|
char buf[16];
|
||||||
@ -959,6 +1004,8 @@ void TextEditor::Render() {
|
|||||||
|
|
||||||
++lineNo;
|
++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
|
// Draw a tooltip on known identifiers/preprocessor symbols
|
||||||
if (ImGui::IsMousePosValid()) {
|
if (ImGui::IsMousePosValid()) {
|
||||||
@ -981,13 +1028,96 @@ void TextEditor::Render() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y));
|
ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y));
|
||||||
|
|
||||||
if (mScrollToCursor) {
|
if (mScrollToCursor) {
|
||||||
EnsureCursorVisible();
|
EnsureCursorVisible();
|
||||||
mScrollToCursor = false;
|
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) {
|
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)
|
if (start > end)
|
||||||
std::swap(start, end);
|
std::swap(start, end);
|
||||||
start.mColumn = 0;
|
start.mColumn = 0;
|
||||||
// end.mColumn = end.mLine < mLines.size() ? mLines[end.mLine].size() : 0;
|
|
||||||
if (end.mColumn == 0 && end.mLine > 0)
|
if (end.mColumn == 0 && end.mLine > 0)
|
||||||
--end.mLine;
|
--end.mLine;
|
||||||
if (end.mLine >= (int)mLines.size())
|
if (end.mLine >= (int)mLines.size())
|
||||||
end.mLine = mLines.empty() ? 0 : (int)mLines.size() - 1;
|
end.mLine = mLines.empty() ? 0 : (int)mLines.size() - 1;
|
||||||
end.mColumn = GetLineMaxColumn(end.mLine);
|
end.mColumn = GetLineMaxColumn(end.mLine);
|
||||||
|
|
||||||
// if (end.mColumn >= GetLineMaxColumn(end.mLine))
|
|
||||||
// end.mColumn = GetLineMaxColumn(end.mLine) - 1;
|
|
||||||
|
|
||||||
u.mRemovedStart = start;
|
u.mRemovedStart = start;
|
||||||
u.mRemovedEnd = end;
|
u.mRemovedEnd = end;
|
||||||
u.mRemoved = GetText(start, end);
|
u.mRemoved = GetText(start, end);
|
||||||
@ -1176,10 +1303,19 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) {
|
|||||||
newLine.push_back(line[it]);
|
newLine.push_back(line[it]);
|
||||||
|
|
||||||
const size_t whitespaceSize = newLine.size();
|
const size_t whitespaceSize = newLine.size();
|
||||||
|
int cstart = 0;
|
||||||
|
int cpos = 0;
|
||||||
auto cindex = GetCharacterIndex(coord);
|
auto cindex = GetCharacterIndex(coord);
|
||||||
newLine.insert(newLine.end(), line.begin() + cindex, line.end());
|
if (cindex < whitespaceSize && mLanguageDefinition.mAutoIndentation) {
|
||||||
line.erase(line.begin() + cindex, line.begin() + line.size());
|
cstart = (int) whitespaceSize;
|
||||||
SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (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;
|
u.mAdded = (char)aChar;
|
||||||
} else if (aChar == '\t') {
|
} else if (aChar == '\t') {
|
||||||
auto &line = mLines[coord.mLine];
|
auto &line = mLines[coord.mLine];
|
||||||
@ -1651,9 +1787,6 @@ void TextEditor::Backspace() {
|
|||||||
while (cindex > 0 && IsUTFSequence(line[cindex].mChar))
|
while (cindex > 0 && IsUTFSequence(line[cindex].mChar))
|
||||||
--cindex;
|
--cindex;
|
||||||
|
|
||||||
// if (cindex > 0 && UTF8CharLength(line[cindex].mChar) > 1)
|
|
||||||
// --cindex;
|
|
||||||
|
|
||||||
u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
|
u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
|
||||||
--u.mRemovedStart.mColumn;
|
--u.mRemovedStart.mColumn;
|
||||||
mState.mCursorPosition.mColumn = GetCharacterColumn(mState.mCursorPosition.mLine, cindex);
|
mState.mCursorPosition.mColumn = GetCharacterColumn(mState.mCursorPosition.mLine, cindex);
|
||||||
@ -1766,6 +1899,360 @@ void TextEditor::Redo(int aSteps) {
|
|||||||
mUndoBuffer[mUndoIndex++].Redo(this);
|
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 TextEditor::Palette &TextEditor::GetDarkPalette() {
|
||||||
const static Palette p = {
|
const static Palette p = {
|
||||||
{
|
{
|
||||||
@ -1779,8 +2266,11 @@ const TextEditor::Palette &TextEditor::GetDarkPalette() {
|
|||||||
0xffaaaaaa, // Identifier
|
0xffaaaaaa, // Identifier
|
||||||
0xff9bc64d, // Known identifier
|
0xff9bc64d, // Known identifier
|
||||||
0xffc040a0, // Preproc identifier
|
0xffc040a0, // Preproc identifier
|
||||||
|
0xff708020, // Global Doc Comment
|
||||||
|
0xff586820, // Doc Comment
|
||||||
0xff206020, // Comment (single line)
|
0xff206020, // Comment (single line)
|
||||||
0xff406020, // Comment (multi line)
|
0xff406020, // Comment (multi line)
|
||||||
|
0xff004545, // Preprocessor deactivated
|
||||||
0xff101010, // Background
|
0xff101010, // Background
|
||||||
0xffe0e0e0, // Cursor
|
0xffe0e0e0, // Cursor
|
||||||
0x80a06020, // Selection
|
0x80a06020, // Selection
|
||||||
@ -1808,8 +2298,11 @@ const TextEditor::Palette &TextEditor::GetLightPalette() {
|
|||||||
0xff404040, // Identifier
|
0xff404040, // Identifier
|
||||||
0xff606010, // Known identifier
|
0xff606010, // Known identifier
|
||||||
0xffc040a0, // Preproc identifier
|
0xffc040a0, // Preproc identifier
|
||||||
|
0xff707820, // Global Doc Comment
|
||||||
|
0xff586020, // Doc Comment
|
||||||
0xff205020, // Comment (single line)
|
0xff205020, // Comment (single line)
|
||||||
0xff405020, // Comment (multi line)
|
0xff405020, // Comment (multi line)
|
||||||
|
0xffa7cccc, // Preprocessor deactivated
|
||||||
0xffffffff, // Background
|
0xffffffff, // Background
|
||||||
0xff000000, // Cursor
|
0xff000000, // Cursor
|
||||||
0x80600000, // Selection
|
0x80600000, // Selection
|
||||||
@ -1837,8 +2330,11 @@ const TextEditor::Palette &TextEditor::GetRetroBluePalette() {
|
|||||||
0xff00ffff, // Identifier
|
0xff00ffff, // Identifier
|
||||||
0xffffffff, // Known identifier
|
0xffffffff, // Known identifier
|
||||||
0xffff00ff, // Preproc identifier
|
0xffff00ff, // Preproc identifier
|
||||||
|
0xff101010, // Global Doc Comment
|
||||||
|
0xff202020, // Doc Comment
|
||||||
0xff808080, // Comment (single line)
|
0xff808080, // Comment (single line)
|
||||||
0xff404040, // Comment (multi line)
|
0xff404040, // Comment (multi line)
|
||||||
|
0xff004000, // Preprocessor deactivated
|
||||||
0xff800000, // Background
|
0xff800000, // Background
|
||||||
0xff0080ff, // Cursor
|
0xff0080ff, // Cursor
|
||||||
0x80ffff00, // Selection
|
0x80ffff00, // Selection
|
||||||
@ -1999,24 +2495,33 @@ void TextEditor::ColorizeInternal() {
|
|||||||
auto endIndex = 0;
|
auto endIndex = 0;
|
||||||
auto commentStartLine = endLine;
|
auto commentStartLine = endLine;
|
||||||
auto commentStartIndex = endIndex;
|
auto commentStartIndex = endIndex;
|
||||||
|
auto withinGlobalDocComment = false;
|
||||||
|
auto withinDocComment = false;
|
||||||
|
auto withinComment = false;
|
||||||
auto withinString = false;
|
auto withinString = false;
|
||||||
auto withinSingleLineComment = false;
|
auto withinSingleLineComment = false;
|
||||||
auto withinPreproc = false;
|
auto withinPreproc = false;
|
||||||
|
auto withinNotDef = false;
|
||||||
auto firstChar = true; // there is no other non-whitespace characters in the line before
|
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 currentLine = 0;
|
||||||
auto currentIndex = 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) {
|
while (currentLine < endLine || currentIndex < endIndex) {
|
||||||
auto &line = mLines[currentLine];
|
auto &line = mLines[currentLine];
|
||||||
|
|
||||||
if (currentIndex == 0 && !concatenate) {
|
if (currentIndex == 0) {
|
||||||
withinSingleLineComment = false;
|
withinSingleLineComment = false;
|
||||||
withinPreproc = false;
|
withinPreproc = false;
|
||||||
firstChar = true;
|
firstChar = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
concatenate = false;
|
|
||||||
|
|
||||||
if (!line.empty()) {
|
if (!line.empty()) {
|
||||||
auto &g = line[currentIndex];
|
auto &g = line[currentIndex];
|
||||||
auto c = g.mChar;
|
auto c = g.mChar;
|
||||||
@ -2024,59 +2529,153 @@ void TextEditor::ColorizeInternal() {
|
|||||||
if (c != mLanguageDefinition.mPreprocChar && !isspace(c))
|
if (c != mLanguageDefinition.mPreprocChar && !isspace(c))
|
||||||
firstChar = false;
|
firstChar = false;
|
||||||
|
|
||||||
if (currentIndex == (int)line.size() - 1 && line[line.size() - 1].mChar == '\\')
|
|
||||||
concatenate = true;
|
|
||||||
|
|
||||||
bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
|
bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
|
||||||
|
|
||||||
if (withinString) {
|
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 (c == '\"') {
|
||||||
if (currentIndex + 1 < (int)line.size() && line[currentIndex + 1].mChar == '\"') {
|
if (currentIndex > 2 && line[currentIndex - 1].mChar == '\\' && line[currentIndex - 2].mChar != '\\') {
|
||||||
currentIndex += 1;
|
currentIndex += 1;
|
||||||
if (currentIndex < (int)line.size())
|
if (currentIndex < (int)line.size()) {
|
||||||
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
|
} else
|
||||||
withinString = false;
|
withinString = false;
|
||||||
} else if (c == '\\') {
|
|
||||||
currentIndex += 1;
|
|
||||||
if (currentIndex < (int)line.size())
|
|
||||||
line[currentIndex].mMultiLineComment = inComment;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (firstChar && c == mLanguageDefinition.mPreprocChar)
|
if (firstChar && c == mLanguageDefinition.mPreprocChar) {
|
||||||
withinPreproc = true;
|
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 == '\"') {
|
if (c == '\"') {
|
||||||
withinString = true;
|
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 {
|
} else {
|
||||||
auto pred = [](const char &a, const Glyph &b) { return a == b.mChar; };
|
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() &&
|
auto compareForth = [&](const std::string &a, const std::vector<Glyph> &b) {
|
||||||
currentIndex + singleStartStr.size() <= line.size() &&
|
return !a.empty() && currentIndex + a.size() <= b.size() && equals(a.begin(), a.end(),
|
||||||
equals(singleStartStr.begin(), singleStartStr.end(), from, from + singleStartStr.size(), pred)) {
|
b.begin() + currentIndex, b.begin() + currentIndex + a.size(), pred);
|
||||||
withinSingleLineComment = true;
|
};
|
||||||
} else if (!startStr.empty() && !withinSingleLineComment && currentIndex + startStr.size() <= line.size() &&
|
|
||||||
equals(startStr.begin(), startStr.end(), from, from + startStr.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;
|
commentStartLine = currentLine;
|
||||||
commentStartIndex = currentIndex;
|
commentStartIndex = currentIndex;
|
||||||
|
if (isGlobalDocComment)
|
||||||
|
withinGlobalDocComment = true;
|
||||||
|
else if (isDocComment)
|
||||||
|
withinDocComment = true;
|
||||||
|
else
|
||||||
|
withinComment = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
inComment = inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
|
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].mComment = withinSingleLineComment;
|
||||||
|
line[currentIndex].mDeactivated = withinNotDef;
|
||||||
|
|
||||||
auto &endStr = mLanguageDefinition.mCommentEnd;
|
auto &endStr = mLanguageDefinition.mCommentEnd;
|
||||||
if (currentIndex + 1 >= (int)endStr.size() &&
|
if (compareBack(endStr, line)) {
|
||||||
equals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, pred)) {
|
withinComment = false;
|
||||||
commentStartIndex = endIndex;
|
withinDocComment = false;
|
||||||
|
withinGlobalDocComment = false;
|
||||||
commentStartLine = endLine;
|
commentStartLine = endLine;
|
||||||
|
commentStartIndex = endIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2093,6 +2692,7 @@ void TextEditor::ColorizeInternal() {
|
|||||||
++currentLine;
|
++currentLine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mDefines.clear();
|
||||||
mCheckComments = false;
|
mCheckComments = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2143,30 +2743,34 @@ void TextEditor::EnsureCursorVisible() {
|
|||||||
float scrollX = ImGui::GetScrollX();
|
float scrollX = ImGui::GetScrollX();
|
||||||
float scrollY = ImGui::GetScrollY();
|
float scrollY = ImGui::GetScrollY();
|
||||||
|
|
||||||
auto height = ImGui::GetWindowHeight();
|
auto windowPadding = ImGui::GetStyle().WindowPadding * 2.0f;
|
||||||
auto width = ImGui::GetWindowWidth();
|
|
||||||
|
|
||||||
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 bottom = (int)ceil((scrollY + height) / mCharAdvance.y);
|
||||||
|
|
||||||
auto left = (int)ceil(scrollX / mCharAdvance.x);
|
auto left = scrollX;
|
||||||
auto right = (int)ceil((scrollX + width) / mCharAdvance.x);
|
auto right = scrollX + width;
|
||||||
|
|
||||||
auto pos = GetActualCursorCoordinates();
|
auto pos = GetActualCursorCoordinates();
|
||||||
auto len = TextDistanceToLineStart(pos);
|
auto len = TextDistanceToLineStart(pos);
|
||||||
|
|
||||||
if (pos.mLine < top)
|
if (pos.mLine <= top + 1)
|
||||||
ImGui::SetScrollY(std::max(0.0f, (pos.mLine - 1) * mCharAdvance.y));
|
ImGui::SetScrollY(std::max(0.0f, (pos.mLine - 1) * mCharAdvance.y));
|
||||||
if (pos.mLine > bottom - 4)
|
if (pos.mLine >= bottom - 2)
|
||||||
ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 4) * mCharAdvance.y - height));
|
ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 2) * mCharAdvance.y - height));
|
||||||
if (len + mTextStart < left + 4)
|
if (len == 0)
|
||||||
|
ImGui::SetScrollX(0);
|
||||||
|
else if (len + mTextStart <= left + 4)
|
||||||
ImGui::SetScrollX(std::max(0.0f, len + mTextStart - 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));
|
ImGui::SetScrollX(std::max(0.0f, len + mTextStart + 4 - width + mCharAdvance.x * 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
int TextEditor::GetPageSize() const {
|
int TextEditor::GetPageSize() const {
|
||||||
auto height = ImGui::GetWindowHeight() - 20.0f;
|
auto height = ImGui::GetWindowHeight() - 20.0f - mTopMargin;
|
||||||
return (int)floor(height / mCharAdvance.y);
|
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 "
|
// 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++;
|
||||||
|
|
||||||
p++;
|
p++;
|
||||||
|
@ -7,13 +7,14 @@ extern const unsigned int blendericons_compressed_data[];
|
|||||||
|
|
||||||
|
|
||||||
#define ICON_MIN_BI 0xea00
|
#define ICON_MIN_BI 0xea00
|
||||||
#define ICON_MAX_BI 0xea08
|
#define ICON_MAX_BI 0xea09
|
||||||
#define ICON_BI_CUBE "\xee\xa8\x80" //< U+ea00
|
#define ICON_BI_CUBE "\xee\xa8\x80" //< U+ea00
|
||||||
#define ICON_BI_EMPTY_ARROWS "\xee\xa8\x81" //< U+ea01
|
#define ICON_BI_DATA_TRANSFER_BOTH "\xee\xa8\x81" //< U+ea01
|
||||||
#define ICON_BI_GRID "\xee\xa8\x82" //< U+ea02
|
#define ICON_BI_EMPTY_ARROWS "\xee\xa8\x82" //< U+ea02
|
||||||
#define ICON_BI_MESH_GRID "\xee\xa8\x83" //< U+ea03
|
#define ICON_BI_GRID "\xee\xa8\x83" //< U+ea03
|
||||||
#define ICON_BI_MOD_SOLIDIFY "\xee\xa8\x84" //< U+ea04
|
#define ICON_BI_MESH_GRID "\xee\xa8\x84" //< U+ea04
|
||||||
#define ICON_BI_ORIENTATION_GLOBAL "\xee\xa8\x85" //< U+ea05
|
#define ICON_BI_MOD_SOLIDIFY "\xee\xa8\x85" //< U+ea05
|
||||||
#define ICON_BI_ORIENTATION_LOCAL "\xee\xa8\x86" //< U+ea06
|
#define ICON_BI_ORIENTATION_GLOBAL "\xee\xa8\x86" //< U+ea06
|
||||||
#define ICON_BI_VIEW_ORTHO "\xee\xa8\x87" //< U+ea07
|
#define ICON_BI_ORIENTATION_LOCAL "\xee\xa8\x87" //< U+ea07
|
||||||
#define ICON_BI_VIEW_PERSPECTIVE "\xee\xa8\x88" //< U+ea08
|
#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;
|
u32 color;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<pl::PatternLanguage> m_parserRuntime;
|
std::unique_ptr<pl::PatternLanguage> m_editorRuntime;
|
||||||
|
|
||||||
PerProvider<std::vector<std::fs::path>> m_possiblePatternFiles;
|
PerProvider<std::vector<std::fs::path>> m_possiblePatternFiles;
|
||||||
bool m_runAutomatically = false;
|
bool m_runAutomatically = false;
|
||||||
@ -220,6 +220,13 @@ namespace hex::plugin::builtin {
|
|||||||
|
|
||||||
std::array<AccessData, 512> m_accessHistory = {};
|
std::array<AccessData, 512> m_accessHistory = {};
|
||||||
u32 m_accessHistoryIndex = 0;
|
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:
|
private:
|
||||||
void drawConsole(ImVec2 size);
|
void drawConsole(ImVec2 size);
|
||||||
@ -231,6 +238,10 @@ namespace hex::plugin::builtin {
|
|||||||
|
|
||||||
void drawPatternTooltip(pl::ptrn::Pattern *pattern);
|
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 loadPatternFile(const std::fs::path &path, prv::Provider *provider);
|
||||||
|
|
||||||
void parsePattern(const std::string &code, 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.context.replace.hex": "Hex",
|
||||||
"hex.builtin.view.find.demangled": "Demangled",
|
"hex.builtin.view.find.demangled": "Demangled",
|
||||||
"hex.builtin.view.find.name": "Find",
|
"hex.builtin.view.find.name": "Find",
|
||||||
|
"hex.builtin.view.replace.name": "Replace",
|
||||||
"hex.builtin.view.find.regex": "Regex",
|
"hex.builtin.view.find.regex": "Regex",
|
||||||
"hex.builtin.view.find.regex.full_match": "Require full match",
|
"hex.builtin.view.find.regex.full_match": "Require full match",
|
||||||
"hex.builtin.view.find.regex.pattern": "Pattern",
|
"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.debugger.scope.global": "Global Scope",
|
||||||
"hex.builtin.view.pattern_editor.env_vars": "Environment Variables",
|
"hex.builtin.view.pattern_editor.env_vars": "Environment Variables",
|
||||||
"hex.builtin.view.pattern_editor.evaluating": "Evaluating...",
|
"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": "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": "Built-in Type",
|
||||||
"hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.array": "Array",
|
"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.edit.place_pattern.custom": "Custom Type",
|
||||||
"hex.builtin.view.pattern_editor.menu.file.load_pattern": "Load pattern...",
|
"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.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.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_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.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.section_popup": "Section",
|
||||||
"hex.builtin.view.pattern_editor.sections": "Sections",
|
"hex.builtin.view.pattern_editor.sections": "Sections",
|
||||||
"hex.builtin.view.pattern_editor.settings": "Settings",
|
"hex.builtin.view.pattern_editor.settings": "Settings",
|
||||||
|
@ -225,8 +225,11 @@ namespace hex::plugin::builtin {
|
|||||||
{ "identifier", u32(TextEditor::PaletteIndex::Identifier) },
|
{ "identifier", u32(TextEditor::PaletteIndex::Identifier) },
|
||||||
{ "known-identifier", u32(TextEditor::PaletteIndex::KnownIdentifier) },
|
{ "known-identifier", u32(TextEditor::PaletteIndex::KnownIdentifier) },
|
||||||
{ "preproc-identifier", u32(TextEditor::PaletteIndex::PreprocIdentifier) },
|
{ "preproc-identifier", u32(TextEditor::PaletteIndex::PreprocIdentifier) },
|
||||||
|
{ "global-doc-comment", u32(TextEditor::PaletteIndex::GlobalDocComment) },
|
||||||
|
{ "doc-comment", u32(TextEditor::PaletteIndex::DocComment) },
|
||||||
{ "comment", u32(TextEditor::PaletteIndex::Comment) },
|
{ "comment", u32(TextEditor::PaletteIndex::Comment) },
|
||||||
{ "multi-line-comment", u32(TextEditor::PaletteIndex::MultiLineComment) },
|
{ "multi-line-comment", u32(TextEditor::PaletteIndex::MultiLineComment) },
|
||||||
|
{ "preprocessor-deactivated", u32(TextEditor::PaletteIndex::PreprocessorDeactivated) },
|
||||||
{ "background", u32(TextEditor::PaletteIndex::Background) },
|
{ "background", u32(TextEditor::PaletteIndex::Background) },
|
||||||
{ "cursor", u32(TextEditor::PaletteIndex::Cursor) },
|
{ "cursor", u32(TextEditor::PaletteIndex::Cursor) },
|
||||||
{ "selection", u32(TextEditor::PaletteIndex::Selection) },
|
{ "selection", u32(TextEditor::PaletteIndex::Selection) },
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "content/views/view_pattern_editor.hpp"
|
#include "content/views/view_pattern_editor.hpp"
|
||||||
|
#include "fonts/blendericons_font.h"
|
||||||
|
|
||||||
#include <hex/api/content_registry.hpp>
|
#include <hex/api/content_registry.hpp>
|
||||||
#include <hex/api/project_file_manager.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) {
|
ViewPatternEditor::ViewPatternEditor() : View::Window("hex.builtin.view.pattern_editor.name", ICON_VS_SYMBOL_NAMESPACE) {
|
||||||
m_parserRuntime = std::make_unique<pl::PatternLanguage>();
|
m_editorRuntime = std::make_unique<pl::PatternLanguage>();
|
||||||
ContentRegistry::PatternLanguage::configureRuntime(*m_parserRuntime, nullptr);
|
ContentRegistry::PatternLanguage::configureRuntime(*m_editorRuntime, nullptr);
|
||||||
|
|
||||||
m_textEditor.SetLanguageDefinition(PatternLanguage());
|
m_textEditor.SetLanguageDefinition(PatternLanguage());
|
||||||
m_textEditor.SetShowWhitespaces(false);
|
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));
|
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);
|
m_textEditor.Render("hex.builtin.view.pattern_editor.name"_lang, textEditorSize, true);
|
||||||
|
TextEditor::FindReplaceHandler *findReplaceHandler = m_textEditor.GetFindReplaceHandler();
|
||||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||||
ImGui::OpenPopup("##pattern_editor_context_menu");
|
ImGui::OpenPopup("##pattern_editor_context_menu");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool clickedMenuFind = false;
|
||||||
|
bool clickedMenuReplace = false;
|
||||||
if (ImGui::BeginPopup("##pattern_editor_context_menu")) {
|
if (ImGui::BeginPopup("##pattern_editor_context_menu")) {
|
||||||
const bool hasSelection = m_textEditor.HasSelection();
|
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)) {
|
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();
|
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();
|
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));
|
ImGui::Button("##settings_drag_bar", ImVec2(ImGui::GetContentRegionAvail().x, 2_scaled));
|
||||||
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0)) {
|
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0)) {
|
||||||
if (ImGui::IsItemHovered())
|
if (ImGui::IsItemHovered())
|
||||||
@ -425,6 +559,309 @@ namespace hex::plugin::builtin {
|
|||||||
View::discardNavigationRequests();
|
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) {
|
void ViewPatternEditor::drawConsole(ImVec2 size) {
|
||||||
if (m_consoleNeedsUpdate) {
|
if (m_consoleNeedsUpdate) {
|
||||||
std::scoped_lock lock(m_logMutex);
|
std::scoped_lock lock(m_logMutex);
|
||||||
@ -1049,8 +1486,8 @@ namespace hex::plugin::builtin {
|
|||||||
void ViewPatternEditor::parsePattern(const std::string &code, prv::Provider *provider) {
|
void ViewPatternEditor::parsePattern(const std::string &code, prv::Provider *provider) {
|
||||||
m_runningParsers += 1;
|
m_runningParsers += 1;
|
||||||
|
|
||||||
ContentRegistry::PatternLanguage::configureRuntime(*m_parserRuntime, nullptr);
|
ContentRegistry::PatternLanguage::configureRuntime(*m_editorRuntime, nullptr);
|
||||||
const auto &ast = m_parserRuntime->parseString(code);
|
const auto &ast = m_editorRuntime->parseString(code);
|
||||||
|
|
||||||
auto &patternVariables = m_patternVariables.get(provider);
|
auto &patternVariables = m_patternVariables.get(provider);
|
||||||
auto oldPatternVariables = std::move(patternVariables);
|
auto oldPatternVariables = std::move(patternVariables);
|
||||||
@ -1344,7 +1781,7 @@ namespace hex::plugin::builtin {
|
|||||||
ImGui::EndMenu();
|
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(); });
|
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)) {
|
if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.custom"_lang, hasPlaceableTypes)) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user