feat: added matched bracket highlights + updating pattern language library (#2335)

Added clion-like bracket matching feature with shortcut to go to the
other one. Also improved some cumbersome repeated function call.
Added support for using negative indices in coordinates so -1 is the last 
line or column, -2 the previous, etc..
Pattern library has fixes for column errors being incorrectly set for lines
containing tabs that are not replaced by 4 spaces.
This commit is contained in:
paxcut
2025-07-15 18:47:33 -07:00
committed by GitHub
parent 4725bf7271
commit 38c5868029
6 changed files with 466 additions and 190 deletions

View File

@@ -235,6 +235,7 @@ public:
bool mGlobalDocComment : 1;
bool mDeactivated : 1;
bool mPreprocessor : 1;
bool mMatchedBracket : 1;
};
union Flags {
Flags(char value) : mValue(value) {}
@@ -244,6 +245,8 @@ public:
};
constexpr static char InComment = 31;
int GetCharacterColumn(int aIndex) const;
class LineIterator {
public:
strConstIter mCharsIter;
@@ -817,6 +820,7 @@ public:
void MoveBottom(bool aSelect = false);
void MoveHome(bool aSelect = false);
void MoveEnd(bool aSelect = false);
void MoveToMatchedBracket(bool aSelect = false);
void SetSelectionStart(const Coordinates& aPosition);
void SetSelectionEnd(const Coordinates& aPosition);
@@ -961,6 +965,25 @@ private:
typedef std::vector<UndoRecord> UndoBuffer;
struct MatchedBracket {
bool mActive=false;
bool mChanged=false;
Coordinates mNearCursor = {};
Coordinates mMatched = {};
static const std::string mSeparators;
static const std::string mOperators;
MatchedBracket(const MatchedBracket &other) : mActive(other.mActive), mChanged(other.mChanged), mNearCursor(other.mNearCursor), mMatched(other.mMatched) {}
MatchedBracket() : mActive(false), mChanged(false), mNearCursor(0,0), mMatched(0,0) {}
MatchedBracket(bool active, bool changed, const Coordinates &nearCursor, const Coordinates &matched) : mActive(active), mChanged(changed), mNearCursor(nearCursor), mMatched(matched) {}
bool CheckPosition(TextEditor *editor, const Coordinates &aFrom);
bool IsNearABracket(TextEditor *editor, const Coordinates &aFrom);
int DetectDirection(TextEditor *editor, const Coordinates &aFrom);
void FindMatchingBracket(TextEditor *editor);
bool IsActive() const { return mActive; }
bool hasChanged() const { return mChanged; }
};
void ProcessInputs();
void ColorizeRange();
void ColorizeInternal();
@@ -980,11 +1003,11 @@ private:
Coordinates FindNextWord(const Coordinates& aFrom) const;
Coordinates StringIndexToCoordinates(int aIndex, const std::string &str) const;
int GetCharacterIndex(const Coordinates& aCoordinates) const;
int GetCharacterColumn(int aLine, int aIndex) const;
Coordinates GetCharacterCoordinates(int aLine, int aIndex) const;
int GetLineCharacterCount(int aLine) const;
int Utf8CharsToBytes(const Coordinates &aCoordinates) const;
static int Utf8CharsToBytes(std::string line, uint32_t start, uint32_t numChars);
unsigned long long GetLineByteCount(int aLine) const;
int Utf8CharsToBytes(const Coordinates &aCoordinates) const;
static int Utf8CharsToBytes(std::string line, uint32_t start, uint32_t numChars);
unsigned long long GetLineByteCount(int aLine) const;
int GetStringCharacterCount(std::string str) const;
int GetLineMaxColumn(int aLine) const;
bool IsOnWordBoundary(const Coordinates& aAt) const;
@@ -1038,6 +1061,7 @@ private:
bool mIgnoreImGuiChild = false;
bool mShowWhitespaces = true;
MatchedBracket mMatchedBracket={};
Palette mPalette = {};
LanguageDefinition mLanguageDefinition = {};
RegexList mRegexList;

View File

@@ -29,6 +29,9 @@ const int TextEditor::sCursorBlinkOnTime = 800;
TextEditor::Palette sPaletteBase = TextEditor::GetDarkPalette();
TextEditor::FindReplaceHandler::FindReplaceHandler() : mWholeWord(false),mFindRegEx(false),mMatchCase(false) {}
const std::string TextEditor::MatchedBracket::mSeparators = "()[]{}";
const std::string TextEditor::MatchedBracket::mOperators = "<>";
TextEditor::TextEditor() {
mStartTime = ImGui::GetTime() * 1000;
@@ -131,13 +134,16 @@ TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const {
TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates &aValue) const {
Coordinates result = aValue;
if (aValue.mLine < 0)
result.mLine = mLines.size() + aValue.mLine;
if (aValue.mColumn < 0)
result.mColumn = GetLineMaxColumn(result.mLine) + aValue.mColumn + 1;
auto lineCount = (int)mLines.size();
if (aValue.mLine < 0 && lineCount + aValue.mLine >= 0)
result.mLine = lineCount + aValue.mLine;
result.mLine = std::clamp(result.mLine, 0, (int)mLines.size()-1);
result.mColumn = std::clamp(result.mColumn, 0, GetLineMaxColumn(result.mLine));
auto maxColumn = GetLineMaxColumn(result.mLine) + 1;
if (aValue.mColumn < 0 && maxColumn + aValue.mColumn >= 0)
result.mColumn = maxColumn + aValue.mColumn;
result.mLine = std::clamp(result.mLine, 0, lineCount - 1);
result.mColumn = std::clamp(result.mColumn, 0, maxColumn - 1);
return result;
}
@@ -196,7 +202,7 @@ void TextEditor::Advance(Coordinates &aCoordinates) const {
auto characterIndex = GetCharacterIndex(aCoordinates);
int maxDelta = line.size() - characterIndex;
characterIndex += std::min(UTF8CharLength(line[characterIndex]), maxDelta);
aCoordinates.mColumn = GetCharacterColumn(aCoordinates.mLine, characterIndex);
aCoordinates.mColumn = line.GetCharacterColumn(characterIndex);
}
void TextEditor::DeleteRange(const Coordinates &aStart, const Coordinates &aEnd) {
@@ -338,7 +344,7 @@ TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates &aFrom) cons
while (cindex > 0 && isspace(line.mChars[cindex-1]))
--cindex;
}
return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex));
return GetCharacterCoordinates(at.mLine, cindex);
}
TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates &aFrom) const {
@@ -359,7 +365,7 @@ TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates &aFrom) const
while (cindex < (int)line.mChars.size() && isspace(line.mChars[cindex]))
++cindex;
}
return Coordinates(aFrom.mLine, GetCharacterColumn(aFrom.mLine, cindex));
return GetCharacterCoordinates(at.mLine, cindex);
}
TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates &aFrom) const {
@@ -381,7 +387,7 @@ TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates &aFrom) const
while (cindex < (int)line.mChars.size() && (ispunct(line.mChars[cindex])))
++cindex;
}
return Coordinates(aFrom.mLine, GetCharacterColumn(aFrom.mLine, cindex));
return GetCharacterCoordinates(at.mLine, cindex);
}
TextEditor::Coordinates TextEditor::FindPreviousWord(const Coordinates &aFrom) const {
@@ -403,7 +409,7 @@ TextEditor::Coordinates TextEditor::FindPreviousWord(const Coordinates &aFrom) c
while (cindex > 0 && ispunct(line.mChars[cindex-1]))
--cindex;
}
return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex));
return GetCharacterCoordinates(at.mLine, cindex);
}
@@ -469,25 +475,24 @@ int TextEditor::GetCharacterIndex(const Coordinates &aCoordinates) const {
return index;
}
int TextEditor::GetCharacterColumn(int aLine, int aIndex) const {
if (aLine >= mLines.size())
return 0;
if (aLine < 0)
return 0;
auto &line = mLines[aLine];
int TextEditor::Line::GetCharacterColumn(int aIndex) const {
int col = 0;
int i = 0;
while (i < aIndex && i < (int)line.size()) {
auto c = line[i];
while (i < aIndex && i < (int)size()) {
auto c = mChars[i];
i += UTF8CharLength(c);
if (c == '\t')
col = (col / mTabSize) * mTabSize + mTabSize;
else
col++;
col++;
}
return col;
}
TextEditor::Coordinates TextEditor::GetCharacterCoordinates(int aLine, int aIndex) const {
if (aLine < 0 || aLine >= (int)mLines.size())
return Coordinates(0, 0);
auto &line = mLines[aLine];
return Coordinates(aLine, line.GetCharacterColumn(aIndex));
}
int TextEditor::GetStringCharacterCount(std::string str) const {
if (str.empty())
return 0;
@@ -843,6 +848,198 @@ void TextEditor::SetFocus() {
mUpdateFocus = false;
}
bool TextEditor::MatchedBracket::CheckPosition(TextEditor *editor, const Coordinates &aFrom) {
auto lineIndex = aFrom.mLine;
auto line = editor->mLines[lineIndex].mChars;
auto colors = editor->mLines[lineIndex].mColors;
if (!line.empty() && colors.empty())
return false;
auto result = aFrom.mColumn;
auto character = line[result];
auto color = colors[result];
if (mSeparators.find(character) != std::string::npos && (static_cast<PaletteIndex>(color) == PaletteIndex::Separator || static_cast<PaletteIndex>(color) == PaletteIndex::WarningText) ||
mOperators.find(character) != std::string::npos && (static_cast<PaletteIndex>(color) == PaletteIndex::Operator || static_cast<PaletteIndex>(color) == PaletteIndex::WarningText)) {
if (mNearCursor != editor->GetCharacterCoordinates(lineIndex, result)) {
mNearCursor = editor->GetCharacterCoordinates(lineIndex, result);
mChanged = true;
}
mActive = true;
return true;
}
return false;
}
int TextEditor::MatchedBracket::DetectDirection(TextEditor *editor, const Coordinates &aFrom) {
int result = 0; // -1 previous 0 current
auto from = editor->SanitizeCoordinates(aFrom);
auto lineIndex = from.mLine;
auto charIndex = editor->GetCharacterIndex(from);
if (charIndex == 0) // no previous character
return 0;
auto line = editor->mLines[lineIndex].mChars;
auto ch1 = line[charIndex-1];
auto ch2 = line[charIndex];
std::string brackets = "()[]{}<>";
auto idx1 = brackets.find(ch1);
auto idx2 = brackets.find(ch2);
if (idx1 == std::string::npos && idx2 == std::string::npos) // no brackets
return 0;
if (idx1 != std::string::npos && idx2 != std::string::npos) {
if (idx1 % 2) // closing bracket + any bracket
return -1;
else if (!(idx1 % 2) && !(idx2 % 2)) // opening bracket + opening bracket
return 0;
} else if (idx1 != std::string::npos) // only first bracket
return -1;
else if (idx2 != std::string::npos) // only second bracket
return 0;
return result;
}
bool TextEditor::MatchedBracket::IsNearABracket(TextEditor *editor, const Coordinates &aFrom) {
auto from = editor->SanitizeCoordinates(aFrom);
auto lineIndex = from.mLine;
auto charIndex = editor->GetCharacterIndex(from);
auto direction1 = DetectDirection(editor, from);
auto direction2 = -(direction1 + 1);
if (CheckPosition(editor, Coordinates(lineIndex, charIndex+direction1)))
return true;
if (CheckPosition(editor, Coordinates(lineIndex, charIndex+direction2)))
return true;
uint64_t result = 0;
std::string line = editor->mLines[lineIndex].mChars;
if (charIndex==0)
if (line[0] == ' ')
result = std::string::npos;
else
result = 0;
else
result = line.find_last_not_of(' ', charIndex-1);
if (result != std::string::npos) {
if (CheckPosition(editor, Coordinates(lineIndex, result)))
return true;
}
result = line.find_first_not_of(' ', charIndex);
if (result != std::string::npos) {
if (CheckPosition(editor, Coordinates(lineIndex, result)))
return true;
}
if (mActive) {
editor->mLines[mNearCursor.mLine].mColorized = false;
editor->mLines[mMatched.mLine].mColorized = false;
mActive = false;
editor->Colorize();
}
return false;
}
void TextEditor::MatchedBracket::FindMatchingBracket(TextEditor *editor) {
auto from = editor->SanitizeCoordinates(mNearCursor);
mMatched = from;
auto lineIndex = from.mLine;
auto maxLineIndex = editor->mLines.size() - 1;
auto charIndex = editor->GetCharacterIndex(from);
std::string line = editor->mLines[lineIndex].mChars;
std::string colors = editor->mLines[lineIndex].mColors;
if (!line.empty() && colors.empty()) {
mActive = false;
return;
}
std::string brackets = "()[]{}<>";
char bracketChar = line[charIndex];
char color1;
auto idx = brackets.find_first_of(bracketChar);
if (idx == std::string::npos) {
if (mActive) {
mActive = false;
editor->Colorize();
}
return;
}
auto bracketChar2 = brackets[idx ^ 1];
brackets = bracketChar;
brackets += bracketChar2;
int32_t direction = 1 - 2 * (idx % 2);
if (idx > 5)
color1 = static_cast<char>(PaletteIndex::Operator);
else
color1 = static_cast<char>(PaletteIndex::Separator);
char color = static_cast<char>(PaletteIndex::WarningText);
int32_t depth = 1;
if (charIndex == (line.size()-1) * (1 + direction) / 2 ) {
if (lineIndex == maxLineIndex * (1 + direction) / 2) {
mActive = false;
return;
}
lineIndex += direction;
line = editor->mLines[lineIndex].mChars;
colors = editor->mLines[lineIndex].mColors;
if (!line.empty() && colors.empty()) {
mActive = false;
return;
}
charIndex = (line.size()-1) * (1 - direction) / 2 - direction;
}
for (int32_t i = charIndex + direction; ; i += direction) {
if (direction == 1)
idx = line.find_first_of(brackets, i);
else
idx = line.find_last_of(brackets, i);
if (idx != std::string::npos) {
if (line[idx] == bracketChar && (colors[idx] == color || colors[idx] == color1)) {
++depth;
i = idx;
} else if (line[idx] == bracketChar2 && (colors[idx] == color) || colors[idx] == color1) {
--depth;
if (depth == 0) {
if (mMatched != editor->GetCharacterCoordinates(lineIndex, idx)) {
mMatched = editor->GetCharacterCoordinates(lineIndex, idx);
mChanged = true;
}
mActive = true;
break;
}
i = idx;
} else {
i = idx;
}
} else {
if (direction == 1)
i = line.size()-1;
else
i = 0;
}
if ((int32_t)(direction * i) >= (int32_t)((line.size() - 1) * (1 + direction) / 2)) {
if (lineIndex == maxLineIndex * (1 + direction) / 2) {
if (mActive) {
mActive = false;
mChanged = true;
}
break;
} else {
lineIndex += direction;
line = editor->mLines[lineIndex].mChars;
colors = editor->mLines[lineIndex].mColors;
if (!line.empty() && colors.empty()) {
mActive = false;
return;
}
i = (line.size() - 1) * (1 - direction) / 2 - direction;
}
}
}
if (mChanged) {
editor->mLines[mNearCursor.mLine].mColorized = false;
editor->mLines[mMatched.mLine].mColorized = false;
editor->Colorize();
mChanged = false;
}
}
void TextEditor::RenderText(const char *aTitle, const ImVec2 &lineNumbersStartPos, const ImVec2 &textEditorSize) {
/* Compute mCharAdvance regarding scaled font size (Ctrl + mouse wheel)*/
const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x;
@@ -1001,6 +1198,8 @@ void TextEditor::RenderText(const char *aTitle, const ImVec2 &lineNumbersStartPo
drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]);
if (elapsed > sCursorBlinkInterval)
mStartTime = timeEnd;
if (mMatchedBracket.IsNearABracket(this, mState.mCursorPosition))
mMatchedBracket.FindMatchingBracket(this);
}
}
}
@@ -1349,7 +1548,7 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) {
}
if (modified) {
start = Coordinates(start.mLine, GetCharacterColumn(start.mLine, 0));
start = GetCharacterCoordinates(start.mLine, 0);
Coordinates rangeEnd;
if (originalEnd.mColumn != 0) {
end = Coordinates(end.mLine, GetLineMaxColumn(end.mLine));
@@ -1414,7 +1613,7 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) {
newLine.insert(newLine.end(), line.begin() + cstart, line.end());
line.erase(line.begin() + cstart,-1);
line.mColorized = false;
SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, cpos)));
SetCursorPosition(GetCharacterCoordinates(coord.mLine + 1, cpos));
u.mAdded = (char)aChar;
} else if (aChar == '\t') {
auto &line = mLines[coord.mLine];
@@ -1425,7 +1624,7 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) {
std::string spaces(spacesToInsert, ' ');
line.insert(line.begin() + cindex, spaces.begin(), spaces.end());
line.mColorized = false;
SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + spacesToInsert)));
SetCursorPosition(GetCharacterCoordinates(coord.mLine, cindex + spacesToInsert));
} else {
auto spacesToRemove = (cindex % mTabSize);
if (spacesToRemove == 0) spacesToRemove = mTabSize;
@@ -1437,7 +1636,7 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) {
}
}
line.mColorized = false;
SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, std::max(0, cindex))));
SetCursorPosition(GetCharacterCoordinates(coord.mLine, std::max(0, cindex)));
}
} else {
@@ -1451,7 +1650,7 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) {
auto d = UTF8CharLength(line[cindex]);
u.mRemovedStart = mState.mCursorPosition;
u.mRemovedEnd = Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + d));
u.mRemovedEnd = GetCharacterCoordinates(coord.mLine, cindex + d);
u.mRemoved = std::string(line.mChars.begin() + cindex, line.mChars.begin() + cindex + d);
line.erase(line.begin() + cindex, d);
line.mColorized = false;
@@ -1460,7 +1659,7 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) {
line.mColorized = false;
u.mAdded = buf;
auto charCount = GetStringCharacterCount(buf);
SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + charCount)));
SetCursorPosition(GetCharacterCoordinates(coord.mLine, cindex + charCount));
} else
return;
}
@@ -1593,6 +1792,32 @@ void TextEditor::JumpToCoords(const Coordinates &aNewPos) {
SetFocusAtCoords(aNewPos);
}
void TextEditor::MoveToMatchedBracket(bool aSelect) {
ResetCursorBlinkTime();
if (mMatchedBracket.IsNearABracket(this, mState.mCursorPosition)) {
mMatchedBracket.FindMatchingBracket(this);
auto oldPos = mMatchedBracket.mNearCursor;
auto newPos = mMatchedBracket.mMatched;
if (newPos != Coordinates(-1, -1)) {
if (aSelect) {
if (oldPos == mInteractiveStart)
mInteractiveStart = newPos;
else if (oldPos == mInteractiveEnd)
mInteractiveEnd = newPos;
else {
mInteractiveStart = newPos;
mInteractiveEnd = oldPos;
}
} else
mInteractiveStart = mInteractiveEnd = newPos;
SetSelection(mInteractiveStart, mInteractiveEnd);
SetCursorPosition(newPos);
EnsureCursorVisible();
}
}
}
void TextEditor::MoveUp(int aAmount, bool aSelect) {
ResetCursorBlinkTime();
auto oldPos = mState.mCursorPosition;
@@ -1696,7 +1921,7 @@ void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) {
}
}
mState.mCursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex));
mState.mCursorPosition = GetCharacterCoordinates(lindex, cindex);
IM_ASSERT(mState.mCursorPosition.mColumn >= 0);
if (aSelect) {
@@ -1728,7 +1953,7 @@ void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) {
auto lindex = mState.mCursorPosition.mLine;
while (aAmount-- > 0) {
auto &line = mLines[lindex];
const auto &line = mLines[lindex];
if (cindex >= line.size()) {
if (lindex < mLines.size() - 1) {
@@ -1749,7 +1974,7 @@ void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) {
}
}
mState.mCursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex));
mState.mCursorPosition = GetCharacterCoordinates(lindex, cindex);
IM_ASSERT(mState.mCursorPosition.mColumn >= 0);
if (aSelect) {
@@ -1989,7 +2214,7 @@ void TextEditor::Backspace() {
u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates();
--u.mRemovedStart.mColumn;
mState.mCursorPosition.mColumn = GetCharacterColumn(mState.mCursorPosition.mLine, cindex);
mState.mCursorPosition.mColumn = line.GetCharacterColumn(cindex);
u.mRemoved = GetText(u.mRemovedStart, u.mRemovedEnd);
if (cend > cindex && cend < (int) line.size())
line.erase(line.begin() + cindex, cend-cindex);
@@ -2019,10 +2244,7 @@ void TextEditor::SelectWordUnderCursor() {
}
void TextEditor::SelectAll() {
if (isEmpty())
return;
SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), mLines.empty() ? 0 : GetLineMaxColumn((int)mLines.size() - 1)));
SetSelection(Coordinates(0, 0), Coordinates(-1, -1));
}
bool TextEditor::HasSelection() const {
@@ -2096,7 +2318,7 @@ std::string TextEditor::ReplaceStrings(std::string string, const std::string &se
}
std::vector<std::string> TextEditor::SplitString(const std::string &string, const std::string &delimiter, bool removeEmpty) {
if (delimiter.empty()) {
if (delimiter.empty() || string.empty()) {
return { string };
}
@@ -2113,7 +2335,8 @@ std::vector<std::string> TextEditor::SplitString(const std::string &string, cons
result.emplace_back(std::move(token));
}
result.emplace_back(string.substr(start));
if (start < string.length())
result.emplace_back(string.substr(start));
if (removeEmpty)
std::erase_if(result, [](const auto &string) { return string.empty(); });
@@ -2757,7 +2980,14 @@ void TextEditor::ColorizeRange() {
token_length = token_end - token_begin;
}
}
if ((token_color != PaletteIndex::Directive && token_color != PaletteIndex::PreprocIdentifier) || flags.mBits.mDeactivated) {
if (flags.mBits.mMatchedBracket) {
if (token_color != PaletteIndex::WarningText) {
token_color = PaletteIndex::WarningText;
}
token_length = token_end - token_begin;
} else if (flags.mBits.mPreprocessor && !flags.mBits.mDeactivated) {
token_length = token_end - token_begin;
} else if ((token_color != PaletteIndex::Directive && token_color != PaletteIndex::PreprocIdentifier) || flags.mBits.mDeactivated) {
if (flags.mBits.mDeactivated && flags.mBits.mPreprocessor) {
token_color = PaletteIndex::PreprocessorDeactivated;
token_begin -= 1;
@@ -2769,9 +2999,9 @@ void TextEditor::ColorizeRange() {
}
auto flag = line.mFlags[token_offset];
token_length = line.mFlags.find_first_not_of(flag, token_offset + 1);
if (token_length == std::string::npos)
token_length = line.size() - token_offset;
token_length = line.mFlags.find_first_not_of(flag, token_offset + 1);
if (token_length == std::string::npos)
token_length = line.size() - token_offset;
else
token_length -= token_offset;
@@ -2816,6 +3046,8 @@ void TextEditor::ColorizeInternal() {
auto withinNotDef = false;
auto currentLine = 0;
auto commentLength = 0;
auto matchedBracket = false;
std::string brackets = "()[]{}<>";
std::vector<bool> ifDefs;
ifDefs.push_back(true);
@@ -2828,170 +3060,184 @@ void TextEditor::ColorizeInternal() {
line.mFlags.resize(lineLength, 0);
line.mColorized = false;
}
//if (!line.mColorized) {
auto withinComment = false;
auto withinDocComment = false;
auto withinPreproc = false;
auto firstChar = true; // there is no other non-whitespace characters in the line before
auto setGlyphFlags = [&](int index) {
Line::Flags flags(0);
flags.mBits.mComment = withinComment;
flags.mBits.mBlockComment = withinBlockComment;
flags.mBits.mDocComment = withinDocComment;
flags.mBits.mGlobalDocComment = withinGlobalDocComment;
flags.mBits.mBlockDocComment = withinBlockDocComment;
flags.mBits.mDeactivated = withinNotDef;
if (mLines[currentLine].mFlags[index] != flags.mValue) {
mLines[currentLine].mColorized = false;
mLines[currentLine].mFlags[index] = flags.mValue;
auto withinComment = false;
auto withinDocComment = false;
auto withinPreproc = false;
auto firstChar = true; // there is no other non-whitespace characters in the line before
auto setGlyphFlags = [&](int index) {
Line::Flags flags(0);
flags.mBits.mComment = withinComment;
flags.mBits.mBlockComment = withinBlockComment;
flags.mBits.mDocComment = withinDocComment;
flags.mBits.mGlobalDocComment = withinGlobalDocComment;
flags.mBits.mBlockDocComment = withinBlockDocComment;
flags.mBits.mDeactivated = withinNotDef;
flags.mBits.mMatchedBracket = matchedBracket;
if (mLines[currentLine].mFlags[index] != flags.mValue) {
mLines[currentLine].mColorized = false;
mLines[currentLine].mFlags[index] = flags.mValue;
}
};
auto currentIndex = 0;
if (line.empty())
continue;
while (currentIndex < lineLength) {
auto &g = line[currentIndex];
auto c = g;
matchedBracket = false;
if (MatchedBracket::mSeparators.contains(c) && mMatchedBracket.IsActive()) {
if (mMatchedBracket.mNearCursor == Coordinates(currentLine, currentIndex) || mMatchedBracket.mMatched == Coordinates(currentLine, currentIndex))
matchedBracket = true;
} else if (MatchedBracket::mOperators.contains(c) && mMatchedBracket.IsActive()) {
if (mMatchedBracket.mNearCursor == Coordinates(currentLine, currentIndex) || mMatchedBracket.mMatched == Coordinates(currentLine, currentIndex)) {
if ((c == '<' && line.mColors[currentIndex - 1] == static_cast<char>(PaletteIndex::UserDefinedType)) ||
(c == '>' && (mMatchedBracket.mMatched.mColumn > 0 && line.mColors[mMatchedBracket.mMatched.mColumn - 1] == static_cast<char>(PaletteIndex::UserDefinedType)) ||
(mMatchedBracket.mNearCursor.mColumn > 0 && line.mColors[mMatchedBracket.mNearCursor.mColumn - 1] == static_cast<char>(PaletteIndex::UserDefinedType)))) {
matchedBracket = true;
}
}
};
}
auto currentIndex = 0;
if (line.empty())
continue;
while (currentIndex < lineLength) {
auto &g = line[currentIndex];
auto c = g;
if (c != mLanguageDefinition.mPreprocChar && !isspace(c))
firstChar = false;
if (c != mLanguageDefinition.mPreprocChar && !isspace(c))
firstChar = false;
bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
if (withinString) {
if (withinString) {
setGlyphFlags(currentIndex);
if (c == '\\') {
currentIndex++;
setGlyphFlags(currentIndex);
if (c == '\\') {
currentIndex++;
setGlyphFlags(currentIndex);
} else if (c == '\"')
withinString = false;
} else {
if (firstChar && c == mLanguageDefinition.mPreprocChar && !inComment && !withinComment && !withinDocComment && !withinString) {
withinPreproc = true;
std::string directive;
auto start = currentIndex + 1;
while (start < (int) line.size() && !isspace(line[start])) {
directive += line[start];
start++;
}
while (start < (int) line.size() && isspace(line[start]))
start++;
if (directive == "endif" && !ifDefs.empty()) {
ifDefs.pop_back();
withinNotDef = !ifDefs.back();
} else {
std::string identifier;
while (start < (int) line.size() && !isspace(line[start])) {
identifier += line[start];
start++;
}
if (directive == "define") {
if (identifier.size() > 0 && !withinNotDef && std::find(mDefines.begin(), mDefines.end(), identifier) == mDefines.end())
mDefines.push_back(identifier);
} else if (directive == "undef") {
if (identifier.size() > 0 && !withinNotDef)
mDefines.erase(std::remove(mDefines.begin(), mDefines.end(), identifier), mDefines.end());
} else if (directive == "ifdef") {
if (!withinNotDef) {
bool isConditionMet = std::find(mDefines.begin(), mDefines.end(), identifier) != mDefines.end();
ifDefs.push_back(isConditionMet);
} else
ifDefs.push_back(false);
} else if (directive == "ifndef") {
if (!withinNotDef) {
bool isConditionMet = std::find(mDefines.begin(), mDefines.end(), identifier) == mDefines.end();
ifDefs.push_back(isConditionMet);
} else
ifDefs.push_back(false);
}
}
} else if (c == '\"')
withinString = false;
} else {
if (firstChar && c == mLanguageDefinition.mPreprocChar && !inComment && !withinComment && !withinDocComment && !withinString) {
withinPreproc = true;
std::string directive;
auto start = currentIndex + 1;
while (start < (int) line.size() && !isspace(line[start])) {
directive += line[start];
start++;
}
if (c == '\"' && !withinPreproc && !inComment && !withinComment && !withinDocComment) {
withinString = true;
setGlyphFlags(currentIndex);
while (start < (int) line.size() && isspace(line[start]))
start++;
if (directive == "endif" && !ifDefs.empty()) {
ifDefs.pop_back();
withinNotDef = !ifDefs.back();
} else {
auto pred = [](const char &a, const char &b) { return a == b; };
std::string identifier;
while (start < (int) line.size() && !isspace(line[start])) {
identifier += line[start];
start++;
}
if (directive == "define") {
if (identifier.size() > 0 && !withinNotDef && std::find(mDefines.begin(), mDefines.end(), identifier) == mDefines.end())
mDefines.push_back(identifier);
} else if (directive == "undef") {
if (identifier.size() > 0 && !withinNotDef)
mDefines.erase(std::remove(mDefines.begin(), mDefines.end(), identifier), mDefines.end());
} else if (directive == "ifdef") {
if (!withinNotDef) {
bool isConditionMet = std::find(mDefines.begin(), mDefines.end(), identifier) != mDefines.end();
ifDefs.push_back(isConditionMet);
} else
ifDefs.push_back(false);
} else if (directive == "ifndef") {
if (!withinNotDef) {
bool isConditionMet = std::find(mDefines.begin(), mDefines.end(), identifier) == mDefines.end();
ifDefs.push_back(isConditionMet);
} else
ifDefs.push_back(false);
}
}
}
auto compareForth = [&](const std::string &a, const std::string &b) {
return !a.empty() && (currentIndex + a.size() <= b.size()) && equals(a.begin(), a.end(), b.begin() + currentIndex, b.begin() + (currentIndex + a.size()), pred);
};
if (c == '\"' && !withinPreproc && !inComment && !withinComment && !withinDocComment) {
withinString = true;
setGlyphFlags(currentIndex);
} else {
auto pred = [](const char &a, const char &b) { return a == b; };
auto compareBack = [&](const std::string &a, const std::string &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);
};
auto compareForth = [&](const std::string &a, const std::string &b) {
return !a.empty() && (currentIndex + a.size() <= b.size()) && equals(a.begin(), a.end(), b.begin() + currentIndex, b.begin() + (currentIndex + a.size()), pred);
};
if (!inComment && !withinComment && !withinDocComment && !withinPreproc && !withinString) {
if (compareForth(mLanguageDefinition.mDocComment, line.mChars)) {
withinDocComment = !inComment;
commentLength = 3;
} else if (compareForth(mLanguageDefinition.mSingleLineComment, line.mChars)) {
withinComment = !inComment;
commentLength = 2;
} else {
bool isGlobalDocComment = compareForth(mLanguageDefinition.mGlobalDocComment, line.mChars);
bool isBlockDocComment = compareForth(mLanguageDefinition.mBlockDocComment, line.mChars);
bool isBlockComment = compareForth(mLanguageDefinition.mCommentStart, line.mChars);
if (isGlobalDocComment || isBlockDocComment || isBlockComment) {
commentStartLine = currentLine;
commentStartIndex = currentIndex;
if (currentIndex < line.size() - 4 && isBlockComment &&
line.mChars[currentIndex + 2] == '*' &&
line.mChars[currentIndex + 3] == '/') {
withinBlockComment = true;
commentLength = 2;
} else if (isGlobalDocComment) {
withinGlobalDocComment = true;
commentLength = 3;
} else if (isBlockDocComment) {
withinBlockDocComment = true;
commentLength = 3;
} else {
withinBlockComment = true;
commentLength = 2;
}
auto compareBack = [&](const std::string &a, const std::string &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 && !withinComment && !withinDocComment && !withinPreproc && !withinString) {
if (compareForth(mLanguageDefinition.mDocComment, line.mChars)) {
withinDocComment = !inComment;
commentLength = 3;
} else if (compareForth(mLanguageDefinition.mSingleLineComment, line.mChars)) {
withinComment = !inComment;
commentLength = 2;
} else {
bool isGlobalDocComment = compareForth(mLanguageDefinition.mGlobalDocComment, line.mChars);
bool isBlockDocComment = compareForth(mLanguageDefinition.mBlockDocComment, line.mChars);
bool isBlockComment = compareForth(mLanguageDefinition.mCommentStart, line.mChars);
if (isGlobalDocComment || isBlockDocComment || isBlockComment) {
commentStartLine = currentLine;
commentStartIndex = currentIndex;
if (currentIndex < line.size() - 4 && isBlockComment &&
line.mChars[currentIndex + 2] == '*' &&
line.mChars[currentIndex + 3] == '/') {
withinBlockComment = true;
commentLength = 2;
} else if (isGlobalDocComment) {
withinGlobalDocComment = true;
commentLength = 3;
} else if (isBlockDocComment) {
withinBlockDocComment = true;
commentLength = 3;
} else {
withinBlockComment = true;
commentLength = 2;
}
}
inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
}
setGlyphFlags(currentIndex);
inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
}
setGlyphFlags(currentIndex);
if (compareBack(mLanguageDefinition.mCommentEnd, line.mChars) && ((commentStartLine != currentLine) || (commentStartIndex + commentLength < currentIndex))) {
withinBlockComment = false;
withinBlockDocComment = false;
withinGlobalDocComment = false;
commentStartLine = endLine;
commentStartIndex = 0;
commentLength = 0;
}
if (compareBack(mLanguageDefinition.mCommentEnd, line.mChars) && ((commentStartLine != currentLine) || (commentStartIndex + commentLength < currentIndex))) {
withinBlockComment = false;
withinBlockDocComment = false;
withinGlobalDocComment = false;
commentStartLine = endLine;
commentStartIndex = 0;
commentLength = 0;
}
}
if (currentIndex < line.size()) {
Line::Flags flags(0);
flags.mValue = mLines[currentLine].mFlags[currentIndex];
flags.mBits.mPreprocessor = withinPreproc;
}
if (currentIndex < line.size()) {
Line::Flags flags(0);
flags.mValue = mLines[currentLine].mFlags[currentIndex];
flags.mBits.mPreprocessor = withinPreproc;
mLines[currentLine].mFlags[currentIndex] = flags.mValue;
}
auto utf8CharLength = UTF8CharLength(c);
if (utf8CharLength > 1) {
Line::Flags flags(0);
flags.mValue = mLines[currentLine].mFlags[currentIndex];
for (int j = 1; j < utf8CharLength; j++) {
currentIndex++;
mLines[currentLine].mFlags[currentIndex] = flags.mValue;
}
auto utf8CharLength = UTF8CharLength(c);
if (utf8CharLength > 1) {
Line::Flags flags(0);
flags.mValue = mLines[currentLine].mFlags[currentIndex];
for (int j = 1; j < utf8CharLength; j++) {
currentIndex++;
mLines[currentLine].mFlags[currentIndex] = flags.mValue;
}
}
currentIndex++;
}
withinNotDef = !ifDefs.back();
// }
// mUpdateFlags = false;
currentIndex++;
}
withinNotDef = !ifDefs.back();
}
mDefines.clear();
}

View File

@@ -1037,6 +1037,7 @@
"hex.builtin.view.pattern_editor.shortcut.move_home": "Move Cursor to the Start of the Line",
"hex.builtin.view.pattern_editor.shortcut.move_end": "Move Cursor to the End of the Line",
"hex.builtin.view.pattern_editor.shortcut.move_top": "Move Cursor to the Start of the File",
"hex.builtin.view.pattern_editor.shortcut.move_matched_bracket": "Move Cursor to the Matching Bracket",
"hex.builtin.view.pattern_editor.shortcut.move_bottom": "Move Cursor to the End of the File",
"hex.builtin.view.pattern_editor.shortcut.delete_word_left": "Delete One Word to the Left of the Cursor",
"hex.builtin.view.pattern_editor.shortcut.delete_word_right": "Delete One Word to the Right of the Cursor",

View File

@@ -2513,6 +2513,11 @@ namespace hex::plugin::builtin {
editor->MoveEnd(false);
});
ShortcutManager::addShortcut(this, CTRLCMD + SHIFT + Keys::M + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.move_matched_bracket", [this] {
if (auto editor = getEditorFromFocusedWindow(); editor != nullptr)
editor->MoveToMatchedBracket(false);
});
ShortcutManager::addShortcut(this, Keys::F8 + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.add_breakpoint", [this] {
const auto line = m_textEditor.get(ImHexApi::Provider::get()).GetCursorPosition().mLine + 1;
const auto &runtime = ContentRegistry::PatternLanguage::getRuntime();