feat: Added multiline search/replace and fixed various crashes. (#1911)
### Problem description Previous implementation ignored everything after the first newline. Other changes include: - Added isEmpty() function that checks if editor has no content. - replaced asserts with default behavior to avoid unneeded crashes. - fixed off by one error in DeleteRange() - some crashes occurred because readily available corrective actions were not being taken. For example start/end being -1 in DeleteRange(). - At the heart of search/replace is the ability to translate from indices into the text string to line/column coordinates used for everything. To this end a function (StringIndexToCoordinates) was added to do the translation taking utf-8 chars into account. This made the recently added Utf8BytesToChars function unneeded, so it was removed. - Removed commented out code that is not useful anymore. Also removed tooltip code which is also unused. - Removed unused parameter wrapAround to FindNext().
This commit is contained in:
parent
05c25b6aff
commit
6d2761f141
@ -211,6 +211,10 @@ public:
|
|||||||
void Render(const char* aTitle, const ImVec2& aSize = ImVec2(), bool aBorder = false);
|
void Render(const char* aTitle, const ImVec2& aSize = ImVec2(), bool aBorder = false);
|
||||||
void SetText(const std::string& aText);
|
void SetText(const std::string& aText);
|
||||||
std::string GetText() const;
|
std::string GetText() const;
|
||||||
|
bool isEmpty() const {
|
||||||
|
auto text = GetText();
|
||||||
|
return text.empty() || text == "\n";
|
||||||
|
}
|
||||||
|
|
||||||
void SetTextLines(const std::vector<std::string>& aLines);
|
void SetTextLines(const std::vector<std::string>& aLines);
|
||||||
std::vector<std::string> GetTextLines() const;
|
std::vector<std::string> GetTextLines() const;
|
||||||
@ -314,7 +318,7 @@ public:
|
|||||||
FindReplaceHandler();
|
FindReplaceHandler();
|
||||||
typedef std::vector<EditorState> Matches;
|
typedef std::vector<EditorState> Matches;
|
||||||
Matches &GetMatches() { return mMatches; }
|
Matches &GetMatches() { return mMatches; }
|
||||||
bool FindNext(TextEditor *editor,bool wrapAround);
|
bool FindNext(TextEditor *editor);
|
||||||
unsigned FindMatch(TextEditor *editor,bool isNex);
|
unsigned FindMatch(TextEditor *editor,bool isNex);
|
||||||
bool Replace(TextEditor *editor,bool right);
|
bool Replace(TextEditor *editor,bool right);
|
||||||
bool ReplaceAll(TextEditor *editor);
|
bool ReplaceAll(TextEditor *editor);
|
||||||
@ -428,10 +432,10 @@ private:
|
|||||||
Coordinates FindWordStart(const Coordinates& aFrom) const;
|
Coordinates FindWordStart(const Coordinates& aFrom) const;
|
||||||
Coordinates FindWordEnd(const Coordinates& aFrom) const;
|
Coordinates FindWordEnd(const Coordinates& aFrom) const;
|
||||||
Coordinates FindNextWord(const Coordinates& aFrom) const;
|
Coordinates FindNextWord(const Coordinates& aFrom) const;
|
||||||
|
Coordinates StringIndexToCoordinates(int aIndex, const std::string &str) const;
|
||||||
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;
|
||||||
int Utf8BytesToChars(const Coordinates &aCoordinates) const;
|
|
||||||
int Utf8CharsToBytes(const Coordinates &aCoordinates) const;
|
int Utf8CharsToBytes(const Coordinates &aCoordinates) const;
|
||||||
unsigned long long GetLineByteCount(int aLine) const;
|
unsigned long long GetLineByteCount(int aLine) const;
|
||||||
int GetStringCharacterCount(std::string str) const;
|
int GetStringCharacterCount(std::string str) const;
|
||||||
|
@ -123,7 +123,7 @@ TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates &aValu
|
|||||||
auto line = aValue.mLine;
|
auto line = aValue.mLine;
|
||||||
auto column = aValue.mColumn;
|
auto column = aValue.mColumn;
|
||||||
if (line >= (int)mLines.size()) {
|
if (line >= (int)mLines.size()) {
|
||||||
if (mLines.empty()) {
|
if (isEmpty()) {
|
||||||
line = 0;
|
line = 0;
|
||||||
column = 0;
|
column = 0;
|
||||||
} else {
|
} else {
|
||||||
@ -132,7 +132,7 @@ TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates &aValu
|
|||||||
}
|
}
|
||||||
return Coordinates(line, column);
|
return Coordinates(line, column);
|
||||||
} else {
|
} else {
|
||||||
column = mLines.empty() ? 0 : std::min(column, GetLineMaxColumn(line));
|
column = isEmpty() ? 0 : std::min(column, GetLineMaxColumn(line));
|
||||||
return Coordinates(line, column);
|
return Coordinates(line, column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,6 +213,8 @@ void TextEditor::DeleteRange(const Coordinates &aStart, const Coordinates &aEnd)
|
|||||||
|
|
||||||
auto start = GetCharacterIndex(aStart);
|
auto start = GetCharacterIndex(aStart);
|
||||||
auto end = GetCharacterIndex(aEnd);
|
auto end = GetCharacterIndex(aEnd);
|
||||||
|
if (start == -1 || end == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
if (aStart.mLine == aEnd.mLine) {
|
if (aStart.mLine == aEnd.mLine) {
|
||||||
auto &line = mLines[aStart.mLine];
|
auto &line = mLines[aStart.mLine];
|
||||||
@ -232,7 +234,7 @@ void TextEditor::DeleteRange(const Coordinates &aStart, const Coordinates &aEnd)
|
|||||||
firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end());
|
firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end());
|
||||||
|
|
||||||
if (aStart.mLine < aEnd.mLine)
|
if (aStart.mLine < aEnd.mLine)
|
||||||
RemoveLine(aStart.mLine + 1, aEnd.mLine + 1);
|
RemoveLine(aStart.mLine + 1, aEnd.mLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
mTextChanged = true;
|
mTextChanged = true;
|
||||||
@ -242,7 +244,10 @@ int TextEditor::InsertTextAt(Coordinates & /* inout */ aWhere, const char *aValu
|
|||||||
int cindex = GetCharacterIndex(aWhere);
|
int cindex = GetCharacterIndex(aWhere);
|
||||||
int totalLines = 0;
|
int totalLines = 0;
|
||||||
while (*aValue != '\0') {
|
while (*aValue != '\0') {
|
||||||
assert(!mLines.empty());
|
if (mLines.empty()) {
|
||||||
|
mLines.push_back(Line());
|
||||||
|
mTextChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (*aValue == '\r') {
|
if (*aValue == '\r') {
|
||||||
// skip
|
// skip
|
||||||
@ -422,7 +427,7 @@ TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates &aFrom) const
|
|||||||
bool isword = false;
|
bool isword = false;
|
||||||
bool skip = false;
|
bool skip = false;
|
||||||
if (cindex < (int)mLines[at.mLine].size()) {
|
if (cindex < (int)mLines[at.mLine].size()) {
|
||||||
auto &line = mLines[at.mLine];
|
const auto &line = mLines[at.mLine];
|
||||||
isword = isalnum(line[cindex].mChar);
|
isword = isalnum(line[cindex].mChar);
|
||||||
skip = isword;
|
skip = isword;
|
||||||
}
|
}
|
||||||
@ -455,26 +460,12 @@ TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates &aFrom) const
|
|||||||
return at;
|
return at;
|
||||||
}
|
}
|
||||||
|
|
||||||
int TextEditor::Utf8BytesToChars(const Coordinates &aCoordinates) const {
|
|
||||||
if (aCoordinates.mLine >= mLines.size())
|
|
||||||
return -1;
|
|
||||||
auto &line = mLines[aCoordinates.mLine];
|
|
||||||
int c = 0;
|
|
||||||
int i = 0;
|
|
||||||
while (i < aCoordinates.mColumn) {
|
|
||||||
i += UTF8CharLength(line[i].mChar);
|
|
||||||
if (line[i].mChar == '\t')
|
|
||||||
c = (c / mTabSize) * mTabSize + mTabSize;
|
|
||||||
else
|
|
||||||
++c;
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
int TextEditor::Utf8CharsToBytes(const Coordinates &aCoordinates) const {
|
int TextEditor::Utf8CharsToBytes(const Coordinates &aCoordinates) const {
|
||||||
if (aCoordinates.mLine >= mLines.size())
|
if (aCoordinates.mLine >= mLines.size())
|
||||||
return -1;
|
return -1;
|
||||||
auto &line = mLines[aCoordinates.mLine];
|
auto &line = mLines[aCoordinates.mLine];
|
||||||
|
if (line.empty())
|
||||||
|
return 0;
|
||||||
int c = 0;
|
int c = 0;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < line.size() && c < aCoordinates.mColumn) {
|
while (i < line.size() && c < aCoordinates.mColumn) {
|
||||||
@ -487,6 +478,18 @@ int TextEditor::Utf8CharsToBytes(const Coordinates &aCoordinates) const {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextEditor::Coordinates TextEditor::StringIndexToCoordinates(int aIndex, const std::string &input ) const {
|
||||||
|
if (aIndex < 0 || aIndex > (int)input.size())
|
||||||
|
return Coordinates(0, 0);
|
||||||
|
std::string str = input.substr(0, aIndex);
|
||||||
|
auto line = std::count(str.begin(),str.end(),'\n');
|
||||||
|
auto index = str.find_last_of('\n');
|
||||||
|
str = str.substr(index+1);
|
||||||
|
auto col = GetStringCharacterCount(str);
|
||||||
|
|
||||||
|
return Coordinates(line, col);
|
||||||
|
}
|
||||||
|
|
||||||
int TextEditor::GetCharacterIndex(const Coordinates &aCoordinates) const {
|
int TextEditor::GetCharacterIndex(const Coordinates &aCoordinates) const {
|
||||||
if (aCoordinates.mLine >= mLines.size())
|
if (aCoordinates.mLine >= mLines.size())
|
||||||
return -1;
|
return -1;
|
||||||
@ -580,7 +583,6 @@ bool TextEditor::IsOnWordBoundary(const Coordinates &aAt) const {
|
|||||||
void TextEditor::RemoveLine(int aStart, int aEnd) {
|
void TextEditor::RemoveLine(int aStart, int aEnd) {
|
||||||
assert(!mReadOnly);
|
assert(!mReadOnly);
|
||||||
assert(aEnd >= aStart);
|
assert(aEnd >= aStart);
|
||||||
assert(mLines.size() > (size_t)(aEnd - aStart));
|
|
||||||
|
|
||||||
ErrorMarkers etmp;
|
ErrorMarkers etmp;
|
||||||
for (auto &i : mErrorMarkers) {
|
for (auto &i : mErrorMarkers) {
|
||||||
@ -601,12 +603,12 @@ void TextEditor::RemoveLine(int aStart, int aEnd) {
|
|||||||
btmp.insert(breakpoint);
|
btmp.insert(breakpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mBreakPointsChanged)
|
|
||||||
mBreakpoints = std::move(btmp);
|
mBreakpoints = std::move(btmp);
|
||||||
if (aStart == 0 && aEnd == (int32_t)mLines.size() - 1)
|
// use clamp to ensure valid results instead of assert.
|
||||||
mLines.erase(mLines.begin() + aStart, mLines.end());
|
auto start = std::clamp(aStart, 0, (int)mLines.size()-1);
|
||||||
else
|
auto end = std::clamp(aEnd, 0, (int)mLines.size());
|
||||||
mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd + 1);
|
mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd + 1);
|
||||||
|
|
||||||
mTextChanged = true;
|
mTextChanged = true;
|
||||||
}
|
}
|
||||||
@ -642,7 +644,16 @@ void TextEditor::RemoveLine(int aIndex) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextEditor::Line &TextEditor::InsertLine(int aIndex) {
|
TextEditor::Line &TextEditor::InsertLine(int aIndex) {
|
||||||
auto &result = *mLines.insert(mLines.begin() + aIndex, Line());
|
|
||||||
|
if (isEmpty())
|
||||||
|
return *mLines.insert(mLines.begin(), Line());
|
||||||
|
|
||||||
|
if (aIndex == mLines.size())
|
||||||
|
return *mLines.insert(mLines.end(), Line());
|
||||||
|
|
||||||
|
auto newLine = Line();
|
||||||
|
|
||||||
|
TextEditor::Line &result = *mLines.insert(mLines.begin() + aIndex, newLine);
|
||||||
|
|
||||||
ErrorMarkers etmp;
|
ErrorMarkers etmp;
|
||||||
for (auto &i : mErrorMarkers)
|
for (auto &i : mErrorMarkers)
|
||||||
@ -717,22 +728,10 @@ void TextEditor::HandleKeyboardInputs() {
|
|||||||
auto ctrl = io.KeyCtrl;
|
auto ctrl = io.KeyCtrl;
|
||||||
auto alt = io.KeyAlt;
|
auto alt = io.KeyAlt;
|
||||||
auto shift = io.KeyShift;
|
auto shift = io.KeyShift;
|
||||||
/* auto left = ImGui::IsKeyPressed(ImGuiKey_LeftArrow);
|
|
||||||
auto right = ImGui::IsKeyPressed(ImGuiKey_RightArrow);
|
|
||||||
auto up = ImGui::IsKeyPressed(ImGuiKey_UpArrow);
|
|
||||||
auto down = ImGui::IsKeyPressed(ImGuiKey_DownArrow);
|
|
||||||
|
|
||||||
auto home = io.ConfigMacOSXBehaviors ? io.KeySuper && left : ImGui::IsKeyPressed(ImGuiKey_Home);
|
|
||||||
auto end = io.ConfigMacOSXBehaviors ? io.KeySuper && right : ImGui::IsKeyPressed(ImGuiKey_End);
|
|
||||||
auto top = io.ConfigMacOSXBehaviors ? io.KeySuper && up : ctrl && ImGui::IsKeyPressed(ImGuiKey_Home);
|
|
||||||
auto bottom = io.ConfigMacOSXBehaviors ? io.KeySuper && down : ctrl && ImGui::IsKeyPressed(ImGuiKey_End);
|
|
||||||
auto pageUp = io.ConfigMacOSXBehaviors ? ctrl && up : ImGui::IsKeyPressed(ImGuiKey_PageUp);
|
|
||||||
auto pageDown = io.ConfigMacOSXBehaviors ? ctrl && down : ImGui::IsKeyPressed(ImGuiKey_PageDown);
|
|
||||||
*/
|
|
||||||
if (ImGui::IsWindowFocused()) {
|
if (ImGui::IsWindowFocused()) {
|
||||||
if (ImGui::IsWindowHovered())
|
if (ImGui::IsWindowHovered())
|
||||||
ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput);
|
ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput);
|
||||||
// ImGui::CaptureKeyboardFromApp(true);
|
|
||||||
|
|
||||||
io.WantCaptureKeyboard = true;
|
io.WantCaptureKeyboard = true;
|
||||||
io.WantTextInput = true;
|
io.WantTextInput = true;
|
||||||
@ -865,7 +864,7 @@ void TextEditor::Render() {
|
|||||||
auto scrollX = ImGui::GetScrollX();
|
auto scrollX = ImGui::GetScrollX();
|
||||||
auto scrollY = ImGui::GetScrollY();
|
auto scrollY = ImGui::GetScrollY();
|
||||||
|
|
||||||
auto lineNo = (int)(std::floor(scrollY / mCharAdvance.y));// + linesAdded);
|
auto lineNo = (int)(std::floor(scrollY / mCharAdvance.y));
|
||||||
auto globalLineMax = (int)mLines.size();
|
auto globalLineMax = (int)mLines.size();
|
||||||
auto lineMax = std::max(0, std::min((int)mLines.size() - 1, lineNo + (int)std::ceil((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)));
|
||||||
|
|
||||||
@ -878,7 +877,7 @@ void TextEditor::Render() {
|
|||||||
buf[0] = '\0';
|
buf[0] = '\0';
|
||||||
mTextStart = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x + mLeftMargin;
|
mTextStart = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x + mLeftMargin;
|
||||||
|
|
||||||
if (!mLines.empty()) {
|
if (!isEmpty()) {
|
||||||
float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x;
|
float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x;
|
||||||
|
|
||||||
while (lineNo <= lineMax) {
|
while (lineNo <= lineMax) {
|
||||||
@ -1048,25 +1047,6 @@ void TextEditor::Render() {
|
|||||||
if (lineNo < mLines.size() && ImGui::GetScrollMaxX() > 0.0f)
|
if (lineNo < mLines.size() && ImGui::GetScrollMaxX() > 0.0f)
|
||||||
longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), longest);
|
longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), longest);
|
||||||
|
|
||||||
// Draw a tooltip on known identifiers/preprocessor symbols
|
|
||||||
if (ImGui::IsMousePosValid()) {
|
|
||||||
auto id = GetWordAt(ScreenPosToCoordinates(ImGui::GetMousePos()));
|
|
||||||
if (!id.empty()) {
|
|
||||||
auto it = mLanguageDefinition.mIdentifiers.find(id);
|
|
||||||
if (it != mLanguageDefinition.mIdentifiers.end() && !it->second.mDeclaration.empty()) {
|
|
||||||
ImGui::BeginTooltip();
|
|
||||||
ImGui::TextUnformatted(it->second.mDeclaration.c_str());
|
|
||||||
ImGui::EndTooltip();
|
|
||||||
} else {
|
|
||||||
auto pi = mLanguageDefinition.mPreprocIdentifiers.find(id);
|
|
||||||
if (pi != mLanguageDefinition.mPreprocIdentifiers.end() && !pi->second.mDeclaration.empty()) {
|
|
||||||
ImGui::BeginTooltip();
|
|
||||||
ImGui::TextUnformatted(pi->second.mDeclaration.c_str());
|
|
||||||
ImGui::EndTooltip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y));
|
ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y));
|
||||||
@ -1098,8 +1078,7 @@ void TextEditor::Render() {
|
|||||||
auto popupStack = g.OpenPopupStack;
|
auto popupStack = g.OpenPopupStack;
|
||||||
if (popupStack.Size > 0) {
|
if (popupStack.Size > 0) {
|
||||||
for (int n = 0; n < popupStack.Size; n++){
|
for (int n = 0; n < popupStack.Size; n++){
|
||||||
auto window = popupStack[n].Window;
|
if (auto window = popupStack[n].Window; window != nullptr) {
|
||||||
if (window != nullptr) {
|
|
||||||
if (window->Size.x == mFindReplaceHandler.GetFindWindowSize().x &&
|
if (window->Size.x == mFindReplaceHandler.GetFindWindowSize().x &&
|
||||||
window->Size.y == mFindReplaceHandler.GetFindWindowSize().y &&
|
window->Size.y == mFindReplaceHandler.GetFindWindowSize().y &&
|
||||||
window->Pos.x == mFindReplaceHandler.GetFindWindowPos().x &&
|
window->Pos.x == mFindReplaceHandler.GetFindWindowPos().x &&
|
||||||
@ -1112,11 +1091,9 @@ void TextEditor::Render() {
|
|||||||
mTopMargin = 0;
|
mTopMargin = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static float linesAdded = 0;
|
|
||||||
static float pixelsAdded = 0;
|
|
||||||
static float savedScrollY = 0;
|
|
||||||
static float shiftedScrollY = 0;
|
|
||||||
if (mTopMargin != oldTopMargin) {
|
if (mTopMargin != oldTopMargin) {
|
||||||
|
static float savedScrollY = 0;
|
||||||
if (oldTopMargin == 0)
|
if (oldTopMargin == 0)
|
||||||
savedScrollY = ImGui::GetScrollY();
|
savedScrollY = ImGui::GetScrollY();
|
||||||
auto window = ImGui::GetCurrentWindow();
|
auto window = ImGui::GetCurrentWindow();
|
||||||
@ -1124,6 +1101,7 @@ void TextEditor::Render() {
|
|||||||
if (maxScroll > 0) {
|
if (maxScroll > 0) {
|
||||||
float lineCount;
|
float lineCount;
|
||||||
float pixelCount;
|
float pixelCount;
|
||||||
|
static float linesAdded = 0;
|
||||||
if (mTopMargin > oldTopMargin) {
|
if (mTopMargin > oldTopMargin) {
|
||||||
pixelCount = mTopMargin - oldTopMargin;
|
pixelCount = mTopMargin - oldTopMargin;
|
||||||
lineCount = pixelCount / mCharAdvance.y;
|
lineCount = pixelCount / mCharAdvance.y;
|
||||||
@ -1148,6 +1126,7 @@ void TextEditor::Render() {
|
|||||||
else
|
else
|
||||||
mLines.erase(mLines.begin() + mLines.size() - 1);
|
mLines.erase(mLines.begin() + mLines.size() - 1);
|
||||||
}
|
}
|
||||||
|
static float pixelsAdded = 0;
|
||||||
if (mTopMargin > oldTopMargin) {
|
if (mTopMargin > oldTopMargin) {
|
||||||
linesAdded += lineCount;
|
linesAdded += lineCount;
|
||||||
pixelsAdded += pixelCount;
|
pixelsAdded += pixelCount;
|
||||||
@ -1159,6 +1138,7 @@ void TextEditor::Render() {
|
|||||||
pixelsAdded = 0;
|
pixelsAdded = 0;
|
||||||
}
|
}
|
||||||
if (oldScrollY + pixelCount < maxScroll) {
|
if (oldScrollY + pixelCount < maxScroll) {
|
||||||
|
static float shiftedScrollY = 0;
|
||||||
if (mTopMargin > oldTopMargin)
|
if (mTopMargin > oldTopMargin)
|
||||||
shiftedScrollY = oldScrollY + pixelCount;
|
shiftedScrollY = oldScrollY + pixelCount;
|
||||||
else if (mTopMargin > 0)
|
else if (mTopMargin > 0)
|
||||||
@ -1211,15 +1191,15 @@ void TextEditor::Render(const char *aTitle, const ImVec2 &aSize, bool aBorder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TextEditor::SetText(const std::string &aText) {
|
void TextEditor::SetText(const std::string &aText) {
|
||||||
mLines.clear();
|
mLines.resize(1);
|
||||||
mLines.emplace_back(Line());
|
mLines[0].clear();
|
||||||
for (auto chr : aText) {
|
for (auto chr : aText) {
|
||||||
if (chr == '\r') {
|
if (chr == '\r') {
|
||||||
// ignore the carriage return character
|
// ignore the carriage return character
|
||||||
} else if (chr == '\n')
|
} else if (chr == '\n')
|
||||||
mLines.emplace_back(Line());
|
mLines.push_back(Line());
|
||||||
else {
|
else {
|
||||||
mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default));
|
mLines.back().push_back(Glyph(chr, PaletteIndex::Default));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1235,7 +1215,7 @@ void TextEditor::SetText(const std::string &aText) {
|
|||||||
void TextEditor::SetTextLines(const std::vector<std::string> &aLines) {
|
void TextEditor::SetTextLines(const std::vector<std::string> &aLines) {
|
||||||
mLines.clear();
|
mLines.clear();
|
||||||
|
|
||||||
if (aLines.empty()) {
|
if (isEmpty()) {
|
||||||
mLines.emplace_back(Line());
|
mLines.emplace_back(Line());
|
||||||
} else {
|
} else {
|
||||||
mLines.resize(aLines.size());
|
mLines.resize(aLines.size());
|
||||||
@ -1281,7 +1261,7 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) {
|
|||||||
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 = isEmpty() ? 0 : (int)mLines.size() - 1;
|
||||||
end.mColumn = GetLineMaxColumn(end.mLine);
|
end.mColumn = GetLineMaxColumn(end.mLine);
|
||||||
|
|
||||||
u.mRemovedStart = start;
|
u.mRemovedStart = start;
|
||||||
@ -1350,6 +1330,10 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) {
|
|||||||
auto coord = GetActualCursorCoordinates();
|
auto coord = GetActualCursorCoordinates();
|
||||||
u.mAddedStart = coord;
|
u.mAddedStart = coord;
|
||||||
|
|
||||||
|
|
||||||
|
if (mLines.empty())
|
||||||
|
mLines.push_back(Line());
|
||||||
|
|
||||||
if (aChar == '\n') {
|
if (aChar == '\n') {
|
||||||
InsertLine(coord.mLine + 1);
|
InsertLine(coord.mLine + 1);
|
||||||
auto &line = mLines[coord.mLine];
|
auto &line = mLines[coord.mLine];
|
||||||
@ -1613,7 +1597,7 @@ void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) {
|
|||||||
auto oldPos = mState.mCursorPosition;
|
auto oldPos = mState.mCursorPosition;
|
||||||
|
|
||||||
ResetCursorBlinkTime();
|
ResetCursorBlinkTime();
|
||||||
if (mLines.empty() || oldPos.mLine >= mLines.size())
|
if (isEmpty() || oldPos.mLine >= mLines.size())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mState.mCursorPosition = GetActualCursorCoordinates();
|
mState.mCursorPosition = GetActualCursorCoordinates();
|
||||||
@ -1670,7 +1654,7 @@ void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) {
|
|||||||
ResetCursorBlinkTime();
|
ResetCursorBlinkTime();
|
||||||
auto oldPos = mState.mCursorPosition;
|
auto oldPos = mState.mCursorPosition;
|
||||||
|
|
||||||
if (mLines.empty() || oldPos.mLine >= mLines.size())
|
if (isEmpty() || oldPos.mLine >= mLines.size())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mState.mCursorPosition = GetActualCursorCoordinates();
|
mState.mCursorPosition = GetActualCursorCoordinates();
|
||||||
@ -1796,7 +1780,7 @@ void TextEditor::Delete() {
|
|||||||
ResetCursorBlinkTime();
|
ResetCursorBlinkTime();
|
||||||
assert(!mReadOnly);
|
assert(!mReadOnly);
|
||||||
|
|
||||||
if (mLines.empty())
|
if (isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
UndoRecord u;
|
UndoRecord u;
|
||||||
@ -1853,7 +1837,7 @@ void TextEditor::Backspace() {
|
|||||||
ResetCursorBlinkTime();
|
ResetCursorBlinkTime();
|
||||||
assert(!mReadOnly);
|
assert(!mReadOnly);
|
||||||
|
|
||||||
if (mLines.empty())
|
if (isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
UndoRecord u;
|
UndoRecord u;
|
||||||
@ -1932,14 +1916,14 @@ void TextEditor::SelectAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TextEditor::HasSelection() const {
|
bool TextEditor::HasSelection() const {
|
||||||
return mState.mSelectionEnd > mState.mSelectionStart;
|
return !isEmpty() && mState.mSelectionEnd > mState.mSelectionStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextEditor::Copy() {
|
void TextEditor::Copy() {
|
||||||
if (HasSelection()) {
|
if (HasSelection()) {
|
||||||
ImGui::SetClipboardText(GetSelectedText().c_str());
|
ImGui::SetClipboardText(GetSelectedText().c_str());
|
||||||
} else {
|
} else {
|
||||||
if (!mLines.empty()) {
|
if (!isEmpty()) {
|
||||||
std::string str;
|
std::string str;
|
||||||
auto &line = mLines[GetActualCursorCoordinates().mLine];
|
auto &line = mLines[GetActualCursorCoordinates().mLine];
|
||||||
for (auto &g : line)
|
for (auto &g : line)
|
||||||
@ -2165,13 +2149,13 @@ std::string make_wholeWord(const std::string &s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Performs actual search to fill mMatches
|
// Performs actual search to fill mMatches
|
||||||
bool TextEditor::FindReplaceHandler::FindNext(TextEditor *editor, bool wrapAround) {
|
bool TextEditor::FindReplaceHandler::FindNext(TextEditor *editor) {
|
||||||
Coordinates curPos;
|
Coordinates curPos;
|
||||||
curPos.mLine = mMatches.empty() ? editor->mState.mCursorPosition.mLine : mMatches.back().mCursorPosition.mLine;
|
curPos.mLine = mMatches.empty() ? editor->mState.mCursorPosition.mLine : mMatches.back().mCursorPosition.mLine;
|
||||||
curPos.mColumn = mMatches.empty() ? editor->mState.mCursorPosition.mColumn : editor->Utf8CharsToBytes(
|
curPos.mColumn = mMatches.empty() ? editor->mState.mCursorPosition.mColumn : editor->Utf8CharsToBytes(
|
||||||
mMatches.back().mCursorPosition);
|
mMatches.back().mCursorPosition);
|
||||||
|
|
||||||
unsigned long selectionLength = editor->GetStringCharacterCount(mFindWord);
|
unsigned long matchLength = editor->GetStringCharacterCount(mFindWord);
|
||||||
size_t byteIndex = 0;
|
size_t byteIndex = 0;
|
||||||
|
|
||||||
for (size_t ln = 0; ln < curPos.mLine; ln++)
|
for (size_t ln = 0; ln < curPos.mLine; ln++)
|
||||||
@ -2218,67 +2202,32 @@ bool TextEditor::FindReplaceHandler::FindNext(TextEditor *editor, bool wrapAroun
|
|||||||
|
|
||||||
if(firstLoc > byteIndex) {
|
if(firstLoc > byteIndex) {
|
||||||
pos = firstLoc;
|
pos = firstLoc;
|
||||||
selectionLength = firstLength;
|
matchLength = firstLength;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
while (iter != end) {
|
while (iter != end) {
|
||||||
iter++;
|
iter++;
|
||||||
if (((pos = iter->position()) > byteIndex) && ((selectionLength = iter->length()) > 0))
|
if (((pos = iter->position()) > byteIndex) && ((matchLength = iter->length()) > 0))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iter == end && !wrapAround)
|
if (iter == end)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
textLoc = pos;
|
textLoc = pos;
|
||||||
if (wrapAround) {
|
|
||||||
if (iter == end)
|
|
||||||
selectionLength = firstLength;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// non regex search
|
// non regex search
|
||||||
textLoc = textSrc.find(wordLower, byteIndex);
|
textLoc = textSrc.find(wordLower, byteIndex);
|
||||||
if (textLoc == std::string::npos) {
|
if (textLoc == std::string::npos)
|
||||||
if (wrapAround)
|
return false;
|
||||||
textLoc = textSrc.find(wordLower, 0);
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (textLoc != std::string::npos) {
|
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];
|
|
||||||
int lineSize = line.size();
|
|
||||||
for (int i = 0; i < std::min(lineSize,curPos.mColumn); i++) {
|
|
||||||
if (line[i].mChar == '\t')
|
|
||||||
curPos.mColumn += (editor->mTabSize - 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} else {// just keep adding
|
|
||||||
byteIndex += byteCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
return false;
|
return false;
|
||||||
Coordinates selStart, selEnd;
|
|
||||||
selStart.mLine = curPos.mLine;
|
|
||||||
selStart.mColumn = editor->Utf8BytesToChars(curPos);
|
|
||||||
selEnd = selStart;
|
|
||||||
selEnd.mColumn += selectionLength;
|
|
||||||
TextEditor::EditorState state;
|
TextEditor::EditorState state;
|
||||||
state.mSelectionStart = selStart;
|
state.mSelectionStart = editor->StringIndexToCoordinates(textLoc,textSrc);
|
||||||
state.mSelectionEnd = selEnd;
|
state.mSelectionEnd = editor->StringIndexToCoordinates(textLoc + matchLength,textSrc);
|
||||||
state.mCursorPosition = selEnd;
|
state.mCursorPosition = state.mSelectionEnd;
|
||||||
mMatches.push_back(state);
|
mMatches.push_back(state);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -2305,7 +2254,7 @@ void TextEditor::FindReplaceHandler::FindAllMatches(TextEditor *editor,std::stri
|
|||||||
Coordinates begin = Coordinates(0,0);
|
Coordinates begin = Coordinates(0,0);
|
||||||
editor->mState.mCursorPosition = begin;
|
editor->mState.mCursorPosition = begin;
|
||||||
|
|
||||||
if (!FindNext(editor,false)) {
|
if (!FindNext(editor)) {
|
||||||
editor->mState = saveState;
|
editor->mState = saveState;
|
||||||
editor->EnsureCursorVisible();
|
editor->EnsureCursorVisible();
|
||||||
return;
|
return;
|
||||||
@ -2313,7 +2262,7 @@ void TextEditor::FindReplaceHandler::FindAllMatches(TextEditor *editor,std::stri
|
|||||||
TextEditor::EditorState state = mMatches.back();
|
TextEditor::EditorState state = mMatches.back();
|
||||||
|
|
||||||
while( state.mCursorPosition < startingPos) {
|
while( state.mCursorPosition < startingPos) {
|
||||||
if (!FindNext(editor,false)) {
|
if (!FindNext(editor)) {
|
||||||
editor->mState = saveState;
|
editor->mState = saveState;
|
||||||
editor->EnsureCursorVisible();
|
editor->EnsureCursorVisible();
|
||||||
return;
|
return;
|
||||||
@ -2321,7 +2270,7 @@ void TextEditor::FindReplaceHandler::FindAllMatches(TextEditor *editor,std::stri
|
|||||||
state = mMatches.back();
|
state = mMatches.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
while (FindNext(editor,false));
|
while (FindNext(editor));
|
||||||
|
|
||||||
editor->mState = saveState;
|
editor->mState = saveState;
|
||||||
editor->EnsureCursorVisible();
|
editor->EnsureCursorVisible();
|
||||||
@ -2538,7 +2487,7 @@ void TextEditor::Colorize(int aFromLine, int aLines) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TextEditor::ColorizeRange(int aFromLine, int aToLine) {
|
void TextEditor::ColorizeRange(int aFromLine, int aToLine) {
|
||||||
if (mLines.empty() || aFromLine >= aToLine)
|
if (isEmpty() || aFromLine >= aToLine)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string buffer;
|
std::string buffer;
|
||||||
@ -2628,7 +2577,7 @@ void TextEditor::ColorizeRange(int aFromLine, int aToLine) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TextEditor::ColorizeInternal() {
|
void TextEditor::ColorizeInternal() {
|
||||||
if (mLines.empty() || !mColorizerEnabled)
|
if (isEmpty() || !mColorizerEnabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (mCheckComments) {
|
if (mCheckComments) {
|
||||||
|
Loading…
Reference in New Issue
Block a user