1
0
mirror of synced 2025-01-11 05:42:15 +01:00

feat: Added clickable links to error messages in the pattern editor (#1988)

Errors printed in the console can be clicked to have the cursor jump to
the source code line where the error is at.

The mouse cursor changes its shape to indicate which parts of the error
message can be clicked on the console. When the cursor jumps, the text
editor takes the focus away from the console and it scrolls the window
to make the line with the error is visible if it isn't. This code uses
the function created for the go-to PR but adds code to switch focus to
the target. When the codes are merged please keep both the part that
jumps the cursor and the part that sets the focus.

---------

Co-authored-by: Nik <werwolv98@gmail.com>
This commit is contained in:
paxcut 2024-12-06 14:45:36 -07:00 committed by GitHub
parent d02e170dac
commit cf09029847
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 202 additions and 25 deletions

View File

@ -9,6 +9,7 @@
#include <map> #include <map>
#include <regex> #include <regex>
#include "imgui.h" #include "imgui.h"
#include "imgui_internal.h"
class TextEditor class TextEditor
{ {
@ -132,11 +133,72 @@ public:
using Identifiers = std::unordered_map<std::string, Identifier>; using Identifiers = std::unordered_map<std::string, Identifier>;
using Keywords = std::unordered_set<std::string> ; using Keywords = std::unordered_set<std::string> ;
using ErrorMarkers = std::map<Coordinates, std::pair<uint32_t ,std::string>>; using ErrorMarkers = std::map<Coordinates, std::pair<uint32_t ,std::string>>;
using ErrorHoverBoxes = std::map<Coordinates, std::pair<ImVec2,ImVec2>>;
using Breakpoints = std::unordered_set<uint32_t>; using Breakpoints = std::unordered_set<uint32_t>;
using Palette = std::array<ImU32, (uint32_t)PaletteIndex::Max>; using Palette = std::array<ImU32, (uint32_t)PaletteIndex::Max>;
using Char = uint8_t ; using Char = uint8_t ;
class ActionableBox {
ImRect mBox;
public:
ActionableBox()=default;
explicit ActionableBox(const ImRect &box) : mBox(box) {}
std::function<void()> mCallback;
virtual bool trigger() {
return ImGui::IsMouseHoveringRect(mBox.Min,mBox.Max);
}
void setCallback(const std::function<void()> &callback) { mCallback = callback; }
};
class CursorChangeBox : public ActionableBox {
public:
CursorChangeBox()=default;
explicit CursorChangeBox(const ImRect &box) : ActionableBox(box) {
setCallback([]() {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
});
}
};
class ErrorGotoBox : public ActionableBox {
Coordinates mPos;
public:
ErrorGotoBox()=default;
ErrorGotoBox(const ImRect &box, const Coordinates &pos, TextEditor *editor) : ActionableBox(box), mPos(pos) {
setCallback( [this,editor]() {
editor->JumpToCoords(mPos);
});
}
bool trigger() override {
return ActionableBox::trigger() && ImGui::IsMouseClicked(0);
}
};
using ErrorGotoBoxes = std::map<Coordinates, ErrorGotoBox>;
using CursorBoxes = std::map<Coordinates, CursorChangeBox>;
class ErrorHoverBox : public ActionableBox {
Coordinates mPos;
std::string mErrorText;
public:
ErrorHoverBox()=default;
ErrorHoverBox(const ImRect &box, const Coordinates &pos,const char *errorText) : ActionableBox(box), mPos(pos), mErrorText(errorText) {
setCallback([this]() {
ImGui::BeginTooltip();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f));
ImGui::Text("Error at line %d:", mPos.mLine);
ImGui::PopStyleColor();
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.2f, 1.0f));
ImGui::TextUnformatted(mErrorText.c_str());
ImGui::PopStyleColor();
ImGui::EndTooltip();
}
);
}
};
using ErrorHoverBoxes = std::map<Coordinates, ErrorHoverBox>;
struct Glyph struct Glyph
{ {
Char mChar; Char mChar;
@ -188,6 +250,23 @@ public:
static const LanguageDefinition& AngelScript(); static const LanguageDefinition& AngelScript();
static const LanguageDefinition& Lua(); static const LanguageDefinition& Lua();
}; };
void ClearErrorMarkers() {
mErrorMarkers.clear();
mErrorHoverBoxes.clear();
}
void ClearGotoBoxes() {
mErrorGotoBoxes.clear();
}
void ClearCursorBoxes() {
mCursorBoxes.clear();
}
void ClearActionables() {
ClearErrorMarkers();
ClearGotoBoxes();
ClearCursorBoxes();
}
struct Selection { struct Selection {
Coordinates mStart; Coordinates mStart;
@ -210,6 +289,8 @@ 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);
void JumpToLine(int line);
void JumpToCoords(const Coordinates &coords);
std::string GetText() const; std::string GetText() const;
bool isEmpty() const { bool isEmpty() const {
auto text = GetText(); auto text = GetText();
@ -221,12 +302,32 @@ public:
std::string GetSelectedText() const; std::string GetSelectedText() const;
std::string GetCurrentLineText()const; std::string GetCurrentLineText()const;
std::string GetLineText(int line)const;
void SetSourceCodeEditor(TextEditor *editor) { mSourceCodeEditor = editor; }
TextEditor *GetSourceCodeEditor() {
if(mSourceCodeEditor!=nullptr)
return mSourceCodeEditor;
return this;
}
class FindReplaceHandler; class FindReplaceHandler;
public: public:
void AddClickableText(std::string text) {
mClickableText.push_back(text);
}
void ClearClickableText() {
mClickableText.clear();
}
FindReplaceHandler *GetFindReplaceHandler() { return &mFindReplaceHandler; } 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; }
void setFocusAtCoords(const Coordinates &coords) {
mFocusAtCoords = coords;
mUpdateFocus = true;
}
void SetOverwrite(bool aValue) { mOverwrite = aValue; } void SetOverwrite(bool aValue) { mOverwrite = aValue; }
void SetReadOnly(bool aValue); void SetReadOnly(bool aValue);
@ -244,8 +345,10 @@ public:
Coordinates GetCursorPosition() const { return GetActualCursorCoordinates(); } Coordinates GetCursorPosition() const { return GetActualCursorCoordinates(); }
void SetCursorPosition(const Coordinates& aPosition); void SetCursorPosition(const Coordinates& aPosition);
bool RaiseContextMenu() { return mRaiseContextMenu; } bool RaiseContextMenu() { return mRaiseContextMenu; }
void ClearRaiseContextMenu() { mRaiseContextMenu = false; } void ClearRaiseContextMenu() { mRaiseContextMenu = false; }
inline void SetHandleMouseInputs (bool aValue){ mHandleMouseInputs = aValue;} inline void SetHandleMouseInputs (bool aValue){ mHandleMouseInputs = aValue;}
inline bool IsHandleMouseInputsEnabled() const { return mHandleKeyboardInputs; } inline bool IsHandleMouseInputsEnabled() const { return mHandleKeyboardInputs; }
@ -491,11 +594,14 @@ private:
Breakpoints mBreakpoints; Breakpoints mBreakpoints;
ErrorMarkers mErrorMarkers; ErrorMarkers mErrorMarkers;
ErrorHoverBoxes mErrorHoverBoxes; ErrorHoverBoxes mErrorHoverBoxes;
ErrorGotoBoxes mErrorGotoBoxes;
CursorBoxes mCursorBoxes;
ImVec2 mCharAdvance; ImVec2 mCharAdvance;
Coordinates mInteractiveStart, mInteractiveEnd; Coordinates mInteractiveStart, mInteractiveEnd;
std::string mLineBuffer; std::string mLineBuffer;
uint64_t mStartTime; uint64_t mStartTime;
std::vector<std::string> mDefines; std::vector<std::string> mDefines;
TextEditor *mSourceCodeEditor=nullptr;
float m_linesAdded = 0; float m_linesAdded = 0;
float m_savedScrollY = 0; float m_savedScrollY = 0;
float m_pixelsAdded = 0; float m_pixelsAdded = 0;
@ -504,6 +610,10 @@ private:
bool mShowCursor; bool mShowCursor;
bool mShowLineNumbers; bool mShowLineNumbers;
bool mRaiseContextMenu = false; bool mRaiseContextMenu = false;
Coordinates mFocusAtCoords;
bool mUpdateFocus = false;
std::vector<std::string> mClickableText;
static const int sCursorBlinkInterval; static const int sCursorBlinkInterval;
static const int sCursorBlinkOnTime; static const int sCursorBlinkOnTime;

View File

@ -982,6 +982,48 @@ void TextEditor::Render() {
} }
} }
// Render goto buttons
auto lineText = GetLineText(lineNo);
Coordinates gotoKey = Coordinates(lineNo + 1, 0);
std::string errorLineColumn;
bool found = false;
for (auto text : mClickableText) {
if (lineText.find(text) == 0) {
errorLineColumn = lineText.substr(text.size());
if (!errorLineColumn.empty()) {
found = true;
break;
}
}
}
if (found) {
int currLine = 0, currColumn = 0;
if (auto idx = errorLineColumn.find(":"); idx != std::string::npos) {
auto errorLine = errorLineColumn.substr(0, idx);
if (!errorLine.empty())
currLine = std::stoi(errorLine) - 1;
auto errorColumn = errorLineColumn.substr(idx + 1);
if (!errorColumn.empty())
currColumn = std::stoi(errorColumn) - 1;
}
TextEditor::Coordinates errorPos = {currLine, currColumn};
ImVec2 errorStart = ImVec2(lineStartScreenPos.x, lineStartScreenPos.y);
ImVec2 errorEnd = ImVec2( lineStartScreenPos.x + TextDistanceToLineStart(Coordinates(lineNo, GetLineCharacterCount(lineNo))), lineStartScreenPos.y + mCharAdvance.y);
ErrorGotoBox box = ErrorGotoBox(ImRect({errorStart, errorEnd}), errorPos, GetSourceCodeEditor());
mErrorGotoBoxes[gotoKey] = box;
CursorChangeBox cursorBox = CursorChangeBox(ImRect({errorStart, errorEnd}));
mCursorBoxes[gotoKey] = cursorBox;
}
if (mCursorBoxes.find(gotoKey) != mCursorBoxes.end()) {
auto box = mCursorBoxes[gotoKey];
if (box.trigger()) box.mCallback();
}
if (mErrorGotoBoxes.find(gotoKey) != mErrorGotoBoxes.end()) {
auto box = mErrorGotoBoxes[gotoKey];
if (box.trigger()) box.mCallback();
}
// Render colorized text // Render colorized text
auto prevColor = line.empty() ? mPalette[(int)PaletteIndex::Default] : GetGlyphColor(line[0]); auto prevColor = line.empty() ? mPalette[(int)PaletteIndex::Default] : GetGlyphColor(line[0]);
ImVec2 bufferOffset; ImVec2 bufferOffset;
@ -1004,17 +1046,35 @@ void TextEditor::Render() {
mLineBuffer.clear(); mLineBuffer.clear();
} }
if (underwaved) { if (underwaved) {
auto textStart = TextDistanceToLineStart(Coordinates(lineNo, i)) + mTextStart; auto textStart = TextDistanceToLineStart(Coordinates(lineNo, i));
auto begin = ImVec2(lineStartScreenPos.x + textStart, lineStartScreenPos.y); auto begin = ImVec2(lineStartScreenPos.x + textStart, lineStartScreenPos.y);
auto errorLength = errorIt->second.first; auto errorLength = errorIt->second.first;
auto errorMessage = errorIt->second.second;
if (errorLength == 0) if (errorLength == 0)
errorLength = line.size() - i - 1; errorLength = line.size() - i - 1;
auto end = Underwaves(begin, errorLength, mPalette[(int32_t) PaletteIndex::ErrorMarker]); auto end = Underwaves(begin, errorLength, mPalette[(int32_t) PaletteIndex::ErrorMarker]);
mErrorHoverBoxes[Coordinates(lineNo+1,i+1)]=std::make_pair(begin,end); Coordinates key = Coordinates(lineNo+1,i+1);
ErrorHoverBox box = ErrorHoverBox(ImRect({begin, end}), key, errorMessage.c_str());
mErrorHoverBoxes[key] = box;
}
Coordinates key = Coordinates(lineNo + 1, i + 1);
if (mErrorHoverBoxes.find(key) != mErrorHoverBoxes.end()) {
auto box = mErrorHoverBoxes[key];
if (box.trigger()) box.mCallback();
} }
prevColor = color; prevColor = color;
if (mUpdateFocus && mFocusAtCoords == Coordinates(lineNo, i)) {
mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = mFocusAtCoords;
mSelectionMode = SelectionMode::Normal;
SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode);
ResetCursorBlinkTime();
EnsureCursorVisible();
ImGui::SetKeyboardFocusHere(-1);
mUpdateFocus = false;
}
if (glyph.mChar == '\t') { if (glyph.mChar == '\t') {
auto oldX = bufferOffset.x; auto oldX = bufferOffset.x;
bufferOffset.x = (1.0f + std::floor((1.0f + bufferOffset.x) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); bufferOffset.x = (1.0f + std::floor((1.0f + bufferOffset.x) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize);
@ -1070,22 +1130,6 @@ void TextEditor::Render() {
mScrollToCursor = false; mScrollToCursor = false;
} }
for (auto [key,value] : mErrorMarkers) {
auto start = mErrorHoverBoxes[key].first;
auto end = mErrorHoverBoxes[key].second;
if (ImGui::IsMouseHoveringRect(start, end)) {
ImGui::BeginTooltip();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f));
ImGui::Text("Error at line %d:", key.mLine);
ImGui::PopStyleColor();
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.2f, 1.0f));
ImGui::Text("%s", value.second.c_str());
ImGui::PopStyleColor();
ImGui::EndTooltip();
}
}
ImGuiPopupFlags_ popup_flags = ImGuiPopupFlags_None; ImGuiPopupFlags_ popup_flags = ImGuiPopupFlags_None;
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
auto oldTopMargin = mTopMargin; auto oldTopMargin = mTopMargin;
@ -1553,6 +1597,19 @@ void TextEditor::DeleteSelection() {
Colorize(mState.mSelectionStart.mLine, 1); Colorize(mState.mSelectionStart.mLine, 1);
} }
void TextEditor::JumpToLine(int line) {
auto newPos = Coordinates(line, 0);
JumpToCoords(newPos);
setFocusAtCoords(newPos);
}
void TextEditor::JumpToCoords(const Coordinates &aNewPos) {
SetSelection(aNewPos, aNewPos);
SetCursorPosition(aNewPos);
EnsureCursorVisible();
setFocusAtCoords(aNewPos);
}
void TextEditor::MoveUp(int aAmount, bool aSelect) { void TextEditor::MoveUp(int aAmount, bool aSelect) {
ResetCursorBlinkTime(); ResetCursorBlinkTime();
auto oldPos = mState.mCursorPosition; auto oldPos = mState.mCursorPosition;
@ -2478,10 +2535,12 @@ std::string TextEditor::GetSelectedText() const {
} }
std::string TextEditor::GetCurrentLineText() const { std::string TextEditor::GetCurrentLineText() const {
auto lineLength = GetLineMaxColumn(mState.mCursorPosition.mLine); return GetLineText(mState.mCursorPosition.mLine);
return GetText( }
Coordinates(mState.mCursorPosition.mLine, 0),
Coordinates(mState.mCursorPosition.mLine, lineLength)); std::string TextEditor::GetLineText(int line) const {
auto lineLength = GetLineCharacterCount(line);
return GetText(Coordinates(line, 0),Coordinates(line, lineLength));
} }
void TextEditor::ProcessInputs() { void TextEditor::ProcessInputs() {

View File

@ -214,6 +214,13 @@ namespace hex::plugin::builtin {
m_consoleEditor.SetReadOnly(true); m_consoleEditor.SetReadOnly(true);
m_consoleEditor.SetShowCursor(false); m_consoleEditor.SetShowCursor(false);
m_consoleEditor.SetShowLineNumbers(false); m_consoleEditor.SetShowLineNumbers(false);
m_consoleEditor.SetSourceCodeEditor(&m_textEditor);
std::string sourcecode = pl::api::Source::DefaultSource;
std::string error = "E: ";
std::string end = ":";
std::string arrow = " --> in ";
m_consoleEditor.AddClickableText(error + sourcecode + end);
m_consoleEditor.AddClickableText(error + arrow + sourcecode + end);
this->registerEvents(); this->registerEvents();
this->registerMenuItems(); this->registerMenuItems();
@ -1753,8 +1760,9 @@ namespace hex::plugin::builtin {
m_runningEvaluators += 1; m_runningEvaluators += 1;
m_executionDone.get(provider) = false; m_executionDone.get(provider) = false;
m_textEditor.ClearActionables();
m_textEditor.SetErrorMarkers({}); m_consoleEditor.ClearActionables();
m_console.get(provider).clear(); m_console.get(provider).clear();
m_consoleNeedsUpdate = true; m_consoleNeedsUpdate = true;