1
0
mirror of synced 2024-11-14 11:07:43 +01:00

feat: Added underwaved text functions (#1889)

### Problem description
Currently when errors are found the entire line where the error occurred
is highlighted and one has to look at the error message in order to find
where the error is located on the line. With this PR the line will no
longer be highlighted and the location of the error will be marked with
an red waved line under the error location. Hovering over the text where
the error occurred produces an error overlay so if several errors occur
on the same line they can all be seen separately.

### Implementation description
The definition of error marker was switched to include column and size
as well as line and message like before.
This change required changing the way view pattern editor draws the
error markers because the errors themselves don't have size information.
Also, a new errorHoverBoxes type was defined to help in the detection of
the floating error messages when error is hovered.

Note that the underwave code depends on having a monospaced. If font is
not monospaced the underwaved text can be short/long or displaced.


### Screenshots

![image](https://github.com/user-attachments/assets/f0b08e10-612c-404a-8863-d4f00054d198)


![image](https://github.com/user-attachments/assets/911fcacb-2a1e-431f-bbc8-8e05bcd61341)
This commit is contained in:
paxcut 2024-09-15 06:19:04 -07:00 committed by GitHub
parent 4b3bbb4a97
commit 54f5bd1d80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 132 additions and 43 deletions

View File

@ -137,6 +137,8 @@ namespace ImGuiExt {
void UnderlinedText(const char *label, ImColor color = ImGui::GetStyleColorVec4(ImGuiCol_Text), const ImVec2 &size_arg = ImVec2(0, 0)); void UnderlinedText(const char *label, ImColor color = ImGui::GetStyleColorVec4(ImGuiCol_Text), const ImVec2 &size_arg = ImVec2(0, 0));
void UnderwavedText(const char *label, ImColor textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text), ImColor lineColor = ImGui::GetStyleColorVec4(ImGuiCol_Text), const ImVec2 &size_arg = ImVec2(0, 0));
void TextSpinner(const char *label); void TextSpinner(const char *label);
void Header(const char *label, bool firstEntry = false); void Header(const char *label, bool firstEntry = false);

View File

@ -550,6 +550,34 @@ namespace ImGuiExt {
PopStyleColor(); PopStyleColor();
} }
void UnderwavedText(const char *label, ImColor textColor, ImColor lineColor, const ImVec2 &size_arg) {
ImGuiWindow *window = GetCurrentWindow();
std::string labelStr(label);
for (char letter : labelStr) {
std::string letterStr(1, letter);
const ImVec2 label_size = CalcTextSize(letterStr.c_str(), nullptr, true);
ImVec2 size = CalcItemSize(size_arg, label_size.x, label_size.y);
ImVec2 pos = window->DC.CursorPos;
float lineWidth = size.x / 3.0f;
float halfLineW = lineWidth / 2.0f;
float lineY = pos.y + size.y;
ImVec2 initial = ImVec2(pos.x, lineY);
ImVec2 pos1 = ImVec2(pos.x + lineWidth, lineY - 2.0f);
ImVec2 pos2 = ImVec2(pos.x + lineWidth + halfLineW, lineY);
ImVec2 pos3 = ImVec2(pos.x + lineWidth * 2 + halfLineW, lineY - 2.0f);
ImVec2 pos4 = ImVec2(pos.x + lineWidth * 3, lineY - 1.0f);
PushStyleColor(ImGuiCol_Text, ImU32(textColor));
TextEx(letterStr.c_str(), nullptr, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting
GetWindowDrawList()->AddLine(initial, pos1, ImU32(lineColor),0.4f);
GetWindowDrawList()->AddLine(pos1, pos2, ImU32(lineColor),0.3f);
GetWindowDrawList()->AddLine(pos2, pos3, ImU32(lineColor),0.4f);
GetWindowDrawList()->AddLine(pos3, pos4, ImU32(lineColor),0.3f);
PopStyleColor();
window->DC.CursorPos = ImVec2(pos.x + size.x, pos.y);
}
}
void TextSpinner(const char *label) { void TextSpinner(const char *label) {
Text("[%c] %s", "|/-\\"[ImU32(GetTime() * 20) % 4], label); Text("[%c] %s", "|/-\\"[ImU32(GetTime() * 20) % 4], label);
} }

View File

@ -128,13 +128,14 @@ public:
std::string mDeclaration; std::string mDeclaration;
}; };
typedef std::string String; using String = std::string;
typedef std::unordered_map<std::string, Identifier> Identifiers; using Identifiers = std::unordered_map<std::string, Identifier>;
typedef std::unordered_set<std::string> Keywords; using Keywords = std::unordered_set<std::string> ;
typedef std::map<int, std::string> ErrorMarkers; using ErrorMarkers = std::map<Coordinates, std::pair<uint32_t ,std::string>>;
typedef std::unordered_set<int> Breakpoints; using ErrorHoverBoxes = std::map<Coordinates, std::pair<ImVec2,ImVec2>>;
typedef std::array<ImU32, (unsigned)PaletteIndex::Max> Palette; using Breakpoints = std::unordered_set<int32_t>;
typedef uint8_t Char; using Palette = std::array<ImU32, (uint32_t)PaletteIndex::Max>;
using Char = uint8_t ;
struct Glyph struct Glyph
{ {
@ -199,6 +200,7 @@ public:
void SetErrorMarkers(const ErrorMarkers& aMarkers) { mErrorMarkers = aMarkers; } void SetErrorMarkers(const ErrorMarkers& aMarkers) { mErrorMarkers = aMarkers; }
void SetBreakpoints(const Breakpoints& aMarkers) { mBreakpoints = aMarkers; } void SetBreakpoints(const Breakpoints& aMarkers) { mBreakpoints = aMarkers; }
ImVec2 Underwaves( ImVec2 pos, uint32_t nChars, ImColor color= ImGui::GetStyleColorVec4(ImGuiCol_Text), const ImVec2 &size_arg= ImVec2(0, 0));
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);
@ -473,6 +475,7 @@ private:
bool mCheckComments; bool mCheckComments;
Breakpoints mBreakpoints; Breakpoints mBreakpoints;
ErrorMarkers mErrorMarkers; ErrorMarkers mErrorMarkers;
ErrorHoverBoxes mErrorHoverBoxes;
ImVec2 mCharAdvance; ImVec2 mCharAdvance;
Coordinates mInteractiveStart, mInteractiveEnd; Coordinates mInteractiveStart, mInteractiveEnd;
std::string mLineBuffer; std::string mLineBuffer;

View File

@ -38,6 +38,37 @@ TextEditor::TextEditor()
TextEditor::~TextEditor() { TextEditor::~TextEditor() {
} }
ImVec2 TextEditor::Underwaves( ImVec2 pos ,uint32_t nChars, ImColor color, const ImVec2 &size_arg) {
auto save = ImGui::GetStyle().AntiAliasedLines;
ImGui::GetStyle().AntiAliasedLines = false;
ImGuiWindow *window = ImGui::GetCurrentWindow();
window->DC.CursorPos =pos;
const ImVec2 label_size = ImGui::CalcTextSize("W", nullptr, true);
ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x, label_size.y);
float lineWidth = size.x / 3.0f + 0.5f;
float halfLineW = lineWidth / 2.0f;
for (uint32_t i = 0; i < nChars; i++) {
pos = window->DC.CursorPos;
float lineY = pos.y + size.y;
ImVec2 pos1_1 = ImVec2(pos.x + 0*lineWidth, lineY + halfLineW);
ImVec2 pos1_2 = ImVec2(pos.x + 1*lineWidth, lineY - halfLineW);
ImVec2 pos2_1 = ImVec2(pos.x + 2*lineWidth, lineY + halfLineW);
ImVec2 pos2_2 = ImVec2(pos.x + 3*lineWidth, lineY - halfLineW);
ImGui::GetWindowDrawList()->AddLine(pos1_1, pos1_2, ImU32(color), 0.4f);
ImGui::GetWindowDrawList()->AddLine(pos1_2, pos2_1, ImU32(color), 0.4f);
ImGui::GetWindowDrawList()->AddLine(pos2_1, pos2_2, ImU32(color), 0.4f);
window->DC.CursorPos = ImVec2(pos.x + size.x, pos.y);
}
auto ret = window->DC.CursorPos;
ret.y += size.y;
return ret;
}
void TextEditor::SetLanguageDefinition(const LanguageDefinition &aLanguageDef) { void TextEditor::SetLanguageDefinition(const LanguageDefinition &aLanguageDef) {
mLanguageDefinition = aLanguageDef; mLanguageDefinition = aLanguageDef;
mRegexList.clear(); mRegexList.clear();
@ -541,8 +572,8 @@ void TextEditor::RemoveLine(int aStart, int aEnd) {
ErrorMarkers etmp; ErrorMarkers etmp;
for (auto &i : mErrorMarkers) { for (auto &i : mErrorMarkers) {
ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second); ErrorMarkers::value_type e(i.first.mLine >= aStart ? Coordinates(i.first.mLine - 1,i.first.mColumn ) : i.first, i.second);
if (e.first >= aStart && e.first <= aEnd) if (e.first.mLine >= aStart && e.first.mLine <= aEnd)
continue; continue;
etmp.insert(e); etmp.insert(e);
} }
@ -555,9 +586,10 @@ void TextEditor::RemoveLine(int aStart, int aEnd) {
btmp.insert(i >= aStart ? i - 1 : i); btmp.insert(i >= aStart ? i - 1 : i);
} }
mBreakpoints = std::move(btmp); mBreakpoints = std::move(btmp);
if (aStart == 0 && aEnd == (int32_t)mLines.size() - 1)
mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd); mLines.erase(mLines.begin() + aStart, mLines.end());
assert(!mLines.empty()); else
mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd + 1);
mTextChanged = true; mTextChanged = true;
} }
@ -568,8 +600,8 @@ void TextEditor::RemoveLine(int aIndex) {
ErrorMarkers etmp; ErrorMarkers etmp;
for (auto &i : mErrorMarkers) { for (auto &i : mErrorMarkers) {
ErrorMarkers::value_type e(i.first > aIndex ? i.first - 1 : i.first, i.second); ErrorMarkers::value_type e(i.first.mLine > aIndex ? Coordinates(i.first.mLine - 1 ,i.first.mColumn) : i.first, i.second);
if (e.first - 1 == aIndex) if (e.first.mLine - 1 == aIndex)
continue; continue;
etmp.insert(e); etmp.insert(e);
} }
@ -594,7 +626,7 @@ TextEditor::Line &TextEditor::InsertLine(int aIndex) {
ErrorMarkers etmp; ErrorMarkers etmp;
for (auto &i : mErrorMarkers) for (auto &i : mErrorMarkers)
etmp.insert(ErrorMarkers::value_type(i.first >= aIndex ? i.first + 1 : i.first, i.second)); etmp.insert(ErrorMarkers::value_type(i.first.mLine >= aIndex ? Coordinates(i.first.mLine + 1,i.first.mColumn) : i.first, i.second));
mErrorMarkers = std::move(etmp); mErrorMarkers = std::move(etmp);
Breakpoints btmp; Breakpoints btmp;
@ -853,25 +885,6 @@ void TextEditor::Render() {
auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y); auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y);
// Draw error markers
auto errorIt = mErrorMarkers.find(lineNo + 1);
if (errorIt != mErrorMarkers.end()) {
auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y);
drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::ErrorMarker]);
if (ImGui::IsMouseHoveringRect(lineStartScreenPos, end)) {
ImGui::BeginTooltip();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f));
ImGui::Text("Error at line %d:", errorIt->first);
ImGui::PopStyleColor();
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.2f, 1.0f));
ImGui::Text("%s", errorIt->second.c_str());
ImGui::PopStyleColor();
ImGui::EndTooltip();
}
}
// Draw line number (right aligned) // Draw line number (right aligned)
if (mShowLineNumbers) { if (mShowLineNumbers) {
snprintf(buf, 16, "%d ", lineNo + 1); snprintf(buf, 16, "%d ", lineNo + 1);
@ -891,7 +904,6 @@ void TextEditor::Render() {
if (mState.mCursorPosition.mLine == lineNo && mShowCursor) { if (mState.mCursorPosition.mLine == lineNo && mShowCursor) {
bool focused = ImGui::IsWindowFocused(); bool focused = ImGui::IsWindowFocused();
ImGuiViewport *viewport = ImGui::GetWindowViewport();
// Highlight the current line (where the cursor is) // Highlight the current line (where the cursor is)
if (!HasSelection()) { if (!HasSelection()) {
@ -937,7 +949,14 @@ void TextEditor::Render() {
for (int i = 0; i < line.size();) { for (int i = 0; i < line.size();) {
auto &glyph = line[i]; auto &glyph = line[i];
auto color = GetGlyphColor(glyph); auto color = GetGlyphColor(glyph);
bool underwaved = false;
ErrorMarkers::iterator errorIt;
if (mErrorMarkers.size() > 0) {
errorIt = mErrorMarkers.find(Coordinates(lineNo+1,i));
if (errorIt != mErrorMarkers.end()) {
underwaved = true;
}
}
if ((color != prevColor || glyph.mChar == '\t' || glyph.mChar == ' ') && !mLineBuffer.empty()) { if ((color != prevColor || glyph.mChar == '\t' || glyph.mChar == ' ') && !mLineBuffer.empty()) {
const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y);
drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); drawList->AddText(newOffset, prevColor, mLineBuffer.c_str());
@ -945,6 +964,13 @@ void TextEditor::Render() {
bufferOffset.x += textSize.x; bufferOffset.x += textSize.x;
mLineBuffer.clear(); mLineBuffer.clear();
} }
if (underwaved) {
auto textStart = TextDistanceToLineStart(Coordinates(lineNo, i-1)) + mTextStart;
auto begin = ImVec2(lineStartScreenPos.x + textStart, lineStartScreenPos.y);
auto end = Underwaves(begin, errorIt->second.first, mPalette[(int32_t) PaletteIndex::ErrorMarker]);
mErrorHoverBoxes[Coordinates(lineNo+1,i)]=std::make_pair(begin,end);
}
prevColor = color; prevColor = color;
if (glyph.mChar == '\t') { if (glyph.mChar == '\t') {
@ -1021,6 +1047,22 @@ 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;
@ -1788,7 +1830,7 @@ void TextEditor::Backspace() {
ErrorMarkers etmp; ErrorMarkers etmp;
for (auto &i : mErrorMarkers) for (auto &i : mErrorMarkers)
etmp.insert(ErrorMarkers::value_type(i.first - 1 == mState.mCursorPosition.mLine ? i.first - 1 : i.first, i.second)); etmp.insert(ErrorMarkers::value_type(i.first.mLine - 1 == mState.mCursorPosition.mLine ? Coordinates(i.first.mLine - 1,i.first.mColumn) : i.first, i.second));
mErrorMarkers = std::move(etmp); mErrorMarkers = std::move(etmp);
RemoveLine(mState.mCursorPosition.mLine); RemoveLine(mState.mCursorPosition.mLine);
@ -2457,7 +2499,7 @@ void TextEditor::ColorizeRange(int aFromLine, int aToLine) {
hasTokenizeResult = true; hasTokenizeResult = true;
} }
if (hasTokenizeResult == false) { if (!hasTokenizeResult) {
// todo : remove // todo : remove
// printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), first); // printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), first);

View File

@ -250,6 +250,7 @@ namespace hex::plugin::builtin {
PerProvider<std::optional<pl::core::err::PatternLanguageError>> m_lastEvaluationError; PerProvider<std::optional<pl::core::err::PatternLanguageError>> m_lastEvaluationError;
PerProvider<std::vector<pl::core::err::CompileError>> m_lastCompileError; PerProvider<std::vector<pl::core::err::CompileError>> m_lastCompileError;
PerProvider<std::vector<const pl::core::ast::ASTNode*>> m_callStack;
PerProvider<std::map<std::string, pl::core::Token::Literal>> m_lastEvaluationOutVars; PerProvider<std::map<std::string, pl::core::Token::Literal>> m_lastEvaluationOutVars;
PerProvider<std::map<std::string, PatternVariable>> m_patternVariables; PerProvider<std::map<std::string, PatternVariable>> m_patternVariables;
PerProvider<std::map<u64, pl::api::Section>> m_sections; PerProvider<std::map<u64, pl::api::Section>> m_sections;

View File

@ -1343,15 +1343,27 @@ namespace hex::plugin::builtin {
}; };
TextEditor::ErrorMarkers errorMarkers; TextEditor::ErrorMarkers errorMarkers;
if (m_lastEvaluationError->has_value()) { if (!m_callStack->empty()) {
errorMarkers[(*m_lastEvaluationError)->line] = processMessage((*m_lastEvaluationError)->message); for (const auto &frame : *m_callStack | std::views::reverse) {
auto location = frame->getLocation();
std::string message;
if (location.source->source == pl::api::Source::DefaultSource) {
if (m_lastEvaluationError->has_value())
message = processMessage((*m_lastEvaluationError)->message);
auto key = TextEditor::Coordinates(location.line, location.column);
errorMarkers[key] = std::make_pair(location.length, message);
}
}
} }
if (!m_lastCompileError->empty()) { if (!m_lastCompileError->empty()) {
for (const auto &error : *m_lastCompileError) { for (const auto &error : *m_lastCompileError) {
auto source = error.getLocation().source; auto source = error.getLocation().source;
if (source != nullptr && source->source == pl::api::Source::DefaultSource) if (source != nullptr && source->source == pl::api::Source::DefaultSource) {
errorMarkers[error.getLocation().line] = processMessage(error.getMessage()); auto key = TextEditor::Coordinates(error.getLocation().line, error.getLocation().column);
if (!errorMarkers.contains(key) ||errorMarkers[key].first < error.getLocation().length)
errorMarkers[key] = std::make_pair(error.getLocation().length,processMessage(error.getMessage()));
}
} }
} }
@ -1794,6 +1806,7 @@ namespace hex::plugin::builtin {
if (!m_lastEvaluationResult) { if (!m_lastEvaluationResult) {
*m_lastEvaluationError = runtime.getEvalError(); *m_lastEvaluationError = runtime.getEvalError();
*m_lastCompileError = runtime.getCompileErrors(); *m_lastCompileError = runtime.getCompileErrors();
*m_callStack = reinterpret_cast<const std::vector<const pl::core::ast::ASTNode *> &>(runtime.getInternals().evaluator->getCallStack());
} }
TaskManager::doLater([code] { TaskManager::doLater([code] {