Fixes for next release. (#2429)

Attempt to fix WerWolv's strange off by one problem when using the
mouse. Added a popup question for files that contain long lines (>1024
bytes). Also improved the handling of large lines, so it won't stall the
app. May also contain other smaller issue fixes.
This commit is contained in:
paxcut 2025-09-02 15:30:50 -07:00 committed by GitHub
parent 790c19a1cd
commit 52952652de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 162 additions and 128 deletions

View File

@ -1048,6 +1048,7 @@
"hex.builtin.view.pattern_editor.menu.edit.copy": "Copy",
"hex.builtin.view.pattern_editor.shortcut.copy": "Copy Selection to the Clipboard",
"hex.builtin.view.pattern_editor.shortcut.cut": "Copy Selection to the Clipboard and Delete it",
"hex.builtin.view.pattern_editor.warning_paste_large": "The clipboard contains a large amount of data. Are you sure you want to paste it?",
"hex.builtin.view.pattern_editor.shortcut.paste": "Paste Clipboard Contents at the Cursor Position",
"hex.builtin.view.pattern_editor.menu.edit.paste": "Paste",
"hex.builtin.view.pattern_editor.menu.edit.undo": "Undo",

View File

@ -324,16 +324,19 @@ namespace hex::ui {
std::string m_colors;
std::string m_flags;
bool m_colorized = false;
i32 m_lineMaxColumn;
i32 m_lineTextSize;
Line() : m_chars(), m_colors(), m_flags(), m_colorized(false), m_lineMaxColumn(-1) {}
Line() : m_chars(), m_colors(), m_flags(), m_colorized(false), m_lineTextSize(-1) {}
explicit Line(const char *line) { Line(std::string(line)); }
explicit Line(const std::string &line) :
m_chars(line), m_colors(std::string(line.size(), 0x00)), m_flags(std::string(line.size(), 0x00)), m_colorized(false), m_lineMaxColumn(getMaxCharColumn()) {}
Line(const Line &line) : m_chars(line.m_chars), m_colors(line.m_colors), m_flags(line.m_flags), m_colorized(line.m_colorized), m_lineMaxColumn(line.m_lineMaxColumn) {}
m_chars(line), m_colors(std::string(line.size(), 0x00)), m_flags(std::string(line.size(), 0x00)), m_colorized(false), m_lineTextSize(
getLineTextSize()) {}
Line(const Line &line) : m_chars(line.m_chars), m_colors(line.m_colors), m_flags(line.m_flags), m_colorized(line.m_colorized), m_lineTextSize(line.m_lineTextSize) {}
i32 getCharacterColumn(i32 index) const;
i32 getMaxCharColumn() const;
i32 getCharColumn(i32 stringIndex) const;
i32 getColumnIndex(i32 column) const;
i32 getLineTextSize();
i32 getStringTextSize(const std::string &str) const;
LineIterator begin();
LineIterator end();
Line &operator=(const Line &line);
@ -500,7 +503,7 @@ namespace hex::ui {
void drawText(Coordinates &lineStart, u64 i, u32 tokenLength, char color);
void postRender(const char *title, ImVec2 position, float lineNo);
ImVec2 calculateCharAdvance() const;
float textDistanceToLineStart(const Coordinates &from) const;
float textDistanceToLineStart(const Coordinates &from);
// Highlighting
public:
void colorize();
@ -529,6 +532,7 @@ namespace hex::ui {
void copy();
void cut();
void paste();
void doPaste(const char *clipText);
void deleteChar();
void insertText(const std::string &value);
void insertText(const char *value);
@ -621,9 +625,9 @@ namespace hex::ui {
Coordinates lineCoordsToIndexCoords(const Coordinates &coordinates) const;
i32 lineCoordinatesToIndex(const Coordinates &coordinates) const;
Coordinates getCharacterCoordinates(i32 line, i32 index);
i32 getLineCharacterCount(i32 line);
i32 getLineCharColumn(i32 lineIndex, i32 stringIndex);
u64 getLineByteCount(i32 line) const;
i32 getLineMaxColumn(i32 line);
i32 getLineMaxCharColumn(i32 lineIndex);
public:
FindReplaceHandler m_findReplaceHandler;

View File

@ -1,12 +1,10 @@
#include <ui/text_editor.hpp>
#include <algorithm>
#include <chrono>
#include <string>
#include <regex>
#include <cmath>
#include <iostream>
#include <hex/helpers/utils.hpp>
#include <wolv/utils/string.hpp>
#include <popups/popup_question.hpp>
#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui.h"
@ -227,6 +225,7 @@ namespace hex::ui {
m_lines.resize(1);
m_lines[0].clear();
} else {
m_lines.clear();
m_lines.resize(lineCount);
u64 i = 0;
for (auto line: vectorString) {
@ -276,7 +275,7 @@ namespace hex::ui {
--end.m_line;
if (end.m_line >= (i32) m_lines.size())
end.m_line = isEmpty() ? 0 : (i32) m_lines.size() - 1;
end.m_column = getLineMaxColumn(end.m_line);
end.m_column = getLineMaxCharColumn(end.m_line);
u.m_removedSelection = Selection(start, end);
u.m_removed = getText(u.m_removedSelection);
@ -530,7 +529,7 @@ namespace hex::ui {
setCursorPosition(pos);
auto &line = m_lines[pos.m_line];
if (pos.m_column == getLineMaxColumn(pos.m_line)) {
if (pos.m_column == getLineMaxCharColumn(pos.m_line)) {
if (pos.m_line == (i32) m_lines.size() - 1)
return;
@ -589,7 +588,7 @@ namespace hex::ui {
advance(u.m_removedSelection.m_end);
auto &prevLine = m_lines[pos.m_line - 1];
auto prevSize = getLineMaxColumn(pos.m_line - 1);
auto prevSize = getLineMaxCharColumn(pos.m_line - 1);
if (prevSize == 0)
prevLine = line;
else
@ -684,34 +683,45 @@ namespace hex::ui {
refreshSearchResults();
}
void TextEditor::doPaste(const char *clipText) {
UndoRecord u;
if (clipText != nullptr) {
auto clipTextStr = wolv::util::preprocessText(clipText);
u.m_before = m_state;
if (hasSelection()) {
u.m_removed = getSelectedText();
u.m_removedSelection = m_state.m_selection;
deleteSelection();
}
u.m_added = clipTextStr;
u.m_addedSelection.m_start = setCoordinates(m_state.m_cursorPosition);
insertText(clipTextStr);
u.m_addedSelection.m_end = setCoordinates(m_state.m_cursorPosition);
u.m_after = m_state;
addUndo(u);
}
refreshSearchResults();
}
void TextEditor::paste() {
if (m_readOnly)
return;
auto clipText = ImGui::GetClipboardText();
const char *clipText = ImGui::GetClipboardText();
if (clipText != nullptr) {
auto len = strlen(clipText);
if (len > 0) {
std::string text = wolv::util::preprocessText(clipText);
UndoRecord u;
u.m_before = m_state;
if (hasSelection()) {
u.m_removed = getSelectedText();
u.m_removedSelection = m_state.m_selection;
deleteSelection();
}
u.m_added = text;
u.m_addedSelection.m_start = setCoordinates(m_state.m_cursorPosition);
insertText(text);
u.m_addedSelection.m_end = setCoordinates(m_state.m_cursorPosition);
u.m_after = m_state;
addUndo(u);
auto stringVector = wolv::util::splitString(clipText, "\n", false);
if (std::any_of(stringVector.begin(), stringVector.end(), [](const std::string &s) { return s.size() > 1024; })) {
ui::PopupQuestion::open("hex.builtin.view.pattern_editor.warning_paste_large"_lang, [this, clipText]() {
this->doPaste(clipText);
}, [] {});
} else {
doPaste(clipText);
}
}
refreshSearchResults();
}
bool TextEditor::canUndo() {

View File

@ -124,7 +124,7 @@ namespace hex::ui {
return;
auto lindex = m_state.m_cursorPosition.m_line;
auto lineMaxColumn = getLineMaxColumn(lindex);
auto lineMaxColumn = getLineMaxCharColumn(lindex);
auto column = std::min(m_state.m_cursorPosition.m_column, lineMaxColumn);
while (amount-- > 0) {
@ -167,7 +167,7 @@ namespace hex::ui {
return;
auto lindex = m_state.m_cursorPosition.m_line;
auto lineMaxColumn = getLineMaxColumn(lindex);
auto lineMaxColumn = getLineMaxCharColumn(lindex);
auto column = std::min(m_state.m_cursorPosition.m_column, lineMaxColumn);
while (amount-- > 0) {
@ -247,7 +247,7 @@ namespace hex::ui {
else {
postIdx = postfix.find_first_not_of(" ");
if (postIdx == std::string::npos)
home = getLineMaxColumn(oldPos.m_line);
home = getLineMaxCharColumn(oldPos.m_line);
else if (postIdx == 0)
home = 0;
else
@ -262,7 +262,7 @@ namespace hex::ui {
else {
postIdx = postfix.find_first_not_of(" ");
if (postIdx == std::string::npos)
home = getLineMaxColumn(oldPos.m_line);
home = getLineMaxCharColumn(oldPos.m_line);
else
home = oldPos.m_column + postIdx;
}
@ -288,7 +288,7 @@ namespace hex::ui {
void TextEditor::moveEnd(bool select) {
resetCursorBlinkTime();
auto oldPos = m_state.m_cursorPosition;
setCursorPosition(setCoordinates(m_state.m_cursorPosition.m_line, getLineMaxColumn(oldPos.m_line)));
setCursorPosition(setCoordinates(m_state.m_cursorPosition.m_line, getLineMaxCharColumn(oldPos.m_line)));
if (m_state.m_cursorPosition != oldPos) {
if (select) {
@ -339,7 +339,7 @@ namespace hex::ui {
else
result.m_line = std::clamp(line, 0, lineCount - 1);
auto maxColumn = getLineMaxColumn(result.m_line) + 1;
auto maxColumn = getLineMaxCharColumn(result.m_line) + 1;
if (column < 0 && maxColumn + column >= 0)
result.m_column = maxColumn + column;
else

View File

@ -287,18 +287,52 @@ namespace hex::ui {
continue;
}
auto colors = m_lines[lineNo].m_colors;
u64 colorsSize = std::min((u64)std::floor(textEditorSize.x / m_charAdvance.x), (u64) colors.size());
u64 i = ImGui::GetScrollX() / m_charAdvance.x;
u64 maxI = i + colorsSize;
auto lineSize = line.getLineTextSize();
i64 colorsSize = std::min((u64)textEditorSize.x, (u64) lineSize);
i64 start = ImGui::GetScrollX();
i64 textSize = 0;
Coordinates head = Coordinates(lineNo, start / m_charAdvance.x);
textSize = textDistanceToLineStart(head);
auto maxColumn = line.getCharColumn(line.size());
if (textSize < start) {
while (textSize < start && head.m_column < maxColumn) {
head.m_column += 1;
textSize = textDistanceToLineStart(head);
}
} else {
while (textSize > start && head.m_column > 0) {
head.m_column -= 1;
textSize = textDistanceToLineStart(head);
}
}
Coordinates current = Coordinates(lineNo, (start + colorsSize) / m_charAdvance.x);
textSize = textDistanceToLineStart(current);
if (textSize < start + colorsSize) {
while (textSize < start + colorsSize && current.m_column < maxColumn) {
current.m_column += 1;
textSize = textDistanceToLineStart(current);
}
} else {
while (textSize > start + colorsSize && current.m_column > 0) {
current.m_column -= 1;
textSize = textDistanceToLineStart(current);
}
}
u64 i = line.getColumnIndex(head.m_column);
u64 maxI = line.getColumnIndex(current.m_column);
while (i < maxI) {
char color = std::clamp(colors[i], (char) PaletteIndex::Default, (char) ((u8) PaletteIndex::Max - 1));
auto index = colors.find_first_not_of(color, i);
index -= i;
if (index == std::string::npos)
index = maxI;
else
index -= i;
u32 tokenLength = std::clamp((u64) index,(u64) 1, maxI - i);
if (m_updateFocus)
setFocus();
auto lineStart = setCoordinates(lineNo, i);
auto lineStart = setCoordinates(lineNo, line.getCharColumn(i));
drawText(lineStart, i, tokenLength, color);
@ -571,10 +605,17 @@ namespace hex::ui {
return ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * m_lineSpacing);
}
float TextEditor::textDistanceToLineStart(const Coordinates &aFrom) const {
float TextEditor::textDistanceToLineStart(const Coordinates &aFrom) {
auto &line = m_lines[aFrom.m_line];
i32 colIndex = lineCoordinatesToIndex(aFrom);
auto substr = line.m_chars.substr(0, colIndex);
return ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, substr.c_str(), nullptr, nullptr).x;
auto substr1 = line.m_chars.substr(0, colIndex);
auto substr2 =line.m_chars.substr(colIndex, line.m_chars.size() - colIndex);
if (substr2.size() < substr1.size()) {
auto distanceToEnd = line.getStringTextSize(substr2.c_str());
line.m_lineTextSize = line.getLineTextSize();
return line.m_lineTextSize - distanceToEnd;
}
return line.getStringTextSize(substr1.c_str());
}
}

View File

@ -156,7 +156,7 @@ namespace hex::ui {
m_colors = line.m_colors;
m_flags = line.m_flags;
m_colorized = line.m_colorized;
m_lineMaxColumn = line.m_lineMaxColumn;
m_lineTextSize = line.m_lineTextSize;
return *this;
}
@ -165,7 +165,7 @@ namespace hex::ui {
m_colors = std::move(line.m_colors);
m_flags = std::move(line.m_flags);
m_colorized = line.m_colorized;
m_lineMaxColumn = line.m_lineMaxColumn;
m_lineTextSize = line.m_lineTextSize;
return *this;
}
@ -198,7 +198,7 @@ namespace hex::ui {
m_colors.push_back(0x00);
m_flags.push_back(0x00);
m_colorized = false;
m_lineMaxColumn = -1;
m_lineTextSize = -1;
}
bool TextEditor::Line::empty() const {
@ -278,12 +278,7 @@ namespace hex::ui {
void TextEditor::Line::append(LineIterator begin, LineIterator end) {
if (begin.m_charsIter < end.m_charsIter) {
m_chars.append(begin.m_charsIter, end.m_charsIter);
std::string charsAppended(begin.m_charsIter, end.m_charsIter);
if (m_lineMaxColumn < 0)
m_lineMaxColumn = this->getMaxCharColumn();
m_lineMaxColumn += TextEditor::getStringCharacterCount(charsAppended);
m_lineTextSize = -1;
}
if (begin.m_colorsIter < end.m_colorsIter)
m_colors.append(begin.m_colorsIter, end.m_colorsIter);
@ -317,12 +312,7 @@ namespace hex::ui {
m_colors.insert(iter.m_colorsIter, beginLine.m_colorsIter, endLine.m_colorsIter);
m_flags.insert(iter.m_flagsIter, beginLine.m_flagsIter, endLine.m_flagsIter);
m_colorized = false;
std::string charsInserted(beginLine.m_charsIter, endLine.m_charsIter);
if (m_lineMaxColumn < 0)
m_lineMaxColumn = this->getMaxCharColumn();
m_lineMaxColumn += TextEditor::getStringCharacterCount(charsInserted);
m_lineTextSize = -1;
}
}
@ -331,12 +321,7 @@ namespace hex::ui {
m_colors.erase(begin.m_colorsIter);
m_flags.erase(begin.m_flagsIter);
m_colorized = false;
std::string charsErased(begin.m_charsIter, end().m_charsIter);
if (m_lineMaxColumn < 0)
m_lineMaxColumn = this->getMaxCharColumn();
m_lineMaxColumn -= TextEditor::getStringCharacterCount(charsErased);
m_lineTextSize = -1;
}
void TextEditor::Line::erase(LineIterator begin, u64 count) {
@ -346,12 +331,7 @@ namespace hex::ui {
m_colors.erase(begin.m_colorsIter, begin.m_colorsIter + count);
m_flags.erase(begin.m_flagsIter, begin.m_flagsIter + count);
m_colorized = false;
std::string charsErased(begin.m_charsIter, begin.m_charsIter + count);
if (m_lineMaxColumn < 0)
m_lineMaxColumn = this->getMaxCharColumn();
m_lineMaxColumn -= TextEditor::getStringCharacterCount(charsErased);
m_lineTextSize = -1;
}
void TextEditor::Line::erase(u64 start, u64 length) {
@ -374,7 +354,7 @@ namespace hex::ui {
m_colors.clear();
m_flags.clear();
m_colorized = false;
m_lineMaxColumn = 0;
m_lineTextSize = -1;
}
void TextEditor::Line::setLine(const std::string &text) {
@ -382,7 +362,7 @@ namespace hex::ui {
m_colors = std::string(text.size(), 0x00);
m_flags = std::string(text.size(), 0x00);
m_colorized = false;
m_lineMaxColumn = -1;
m_lineTextSize = -1;
}
void TextEditor::Line::setLine(const Line &text) {
@ -390,7 +370,7 @@ namespace hex::ui {
m_colors = text.m_colors;
m_flags = text.m_flags;
m_colorized = text.m_colorized;
m_lineMaxColumn = text.m_lineMaxColumn;
m_lineTextSize = text.m_lineTextSize;
}
bool TextEditor::Line::needsUpdate() const {
@ -520,7 +500,7 @@ namespace hex::ui {
m_state.m_cursorPosition = screenPosToCoordinates(ImGui::GetMousePos());
auto line = m_state.m_cursorPosition.m_line;
m_state.m_selection.m_start = setCoordinates(line, 0);
m_state.m_selection.m_end = setCoordinates(line, getLineMaxColumn(line));
m_state.m_selection.m_end = setCoordinates(line, getLineMaxCharColumn(line));
}
m_lastClick = -1.0f;
@ -824,7 +804,7 @@ namespace hex::ui {
editor->m_state.m_cursorPosition = editor->m_state.m_selection.m_start;
if (editor->isStartOfLine()) {
editor->m_state.m_cursorPosition.m_line--;
editor->m_state.m_cursorPosition.m_column = editor->getLineMaxColumn(editor->m_state.m_cursorPosition.m_line);
editor->m_state.m_cursorPosition.m_column = editor->getLineMaxCharColumn(editor->m_state.m_cursorPosition.m_line);
} else
editor->m_state.m_cursorPosition.m_column--;
}

View File

@ -1,7 +1,37 @@
#include <ui/text_editor.hpp>
#include <hex/helpers/scaling.hpp>
#include <algorithm>
namespace hex::ui {
i32 TextEditor::Line::getColumnIndex(i32 column) const {
i32 idx = 0;
for (i32 col = 0; idx < (i32) size() && col < column; ++col)
idx += TextEditor::utf8CharLength(m_chars[idx]);
return idx;
}
i32 TextEditor::Line::getCharColumn(i32 stringIndex) const {
i32 limit = std::max(0, std::min(stringIndex, (i32) size()));
i32 col = 0;
for (i32 idx = 0; idx < limit; col++)
idx += TextEditor::utf8CharLength(m_chars[idx]);
return col;
}
i32 TextEditor::Line::getStringTextSize(const std::string &str) const {
return ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, str.c_str(), nullptr, nullptr).x;
}
i32 TextEditor::Line::getLineTextSize() {
m_lineTextSize = getStringTextSize(m_chars);
return m_lineTextSize;
}
// https://en.wikipedia.org/wiki/UTF-8
// We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code)
i32 TextEditor::utf8CharLength(u8 c) {
@ -27,20 +57,18 @@ namespace hex::ui {
return count;
}
i32 TextEditor::getLineCharacterCount(i32 lineIndex) {
i32 TextEditor::getLineCharColumn(i32 lineIndex, i32 stringIndex) {
if (lineIndex >= (i64) m_lines.size() || lineIndex < 0)
return 0;
Line &line = m_lines[lineIndex];
if (line.m_lineMaxColumn != -1)
return line.m_lineMaxColumn;
else {
auto str = line.m_chars;
i32 count = 0;
for (u32 idx = 0; idx < str.size(); count++)
idx += TextEditor::utf8CharLength(str[idx]);
line.m_lineMaxColumn = count;
return count;
}
return line.getCharColumn(stringIndex);
}
i32 TextEditor::getLineMaxCharColumn(i32 lineIndex) {
if (lineIndex >= (i64) m_lines.size() || lineIndex < 0)
return 0;
Line &line = m_lines[lineIndex];
return line.getCharColumn(line.size());
}
// "Borrowed" from ImGui source
@ -83,24 +111,13 @@ namespace hex::ui {
return size;
}
static i32 utf8CharCount(const std::string &line, i32 start, i32 numChars) {
if (line.empty())
return 0;
i32 index = 0;
for (i32 column = 0; start + index < (i32) line.size() && column < numChars; ++column)
index += TextEditor::utf8CharLength(line[start + index]);
return index;
}
TextEditor::Coordinates TextEditor::screenPosToCoordinates(const ImVec2 &position) {
ImVec2 local = position - ImGui::GetCursorScreenPos();
i32 lineNo = std::max(0, (i32) floor(local.y / m_charAdvance.y));
if (local.x < (m_leftMargin - 2) || lineNo >= (i32) m_lines.size() || m_lines[lineNo].empty())
if (local.x < (m_leftMargin - 2_scaled) || lineNo >= (i32) m_lines.size() || m_lines[lineNo].empty())
return setCoordinates(std::min(lineNo, (i32) m_lines.size() - 1), 0);
std::string line = m_lines[lineNo].m_chars;
local.x -= (m_leftMargin - 5);
local.x -= (m_leftMargin - 5_scaled);
i32 count = 0;
u64 length;
i32 increase;
@ -123,7 +140,7 @@ namespace hex::ui {
return Invalid;
const auto &line = m_lines[coordinates.m_line];
return Coordinates(coordinates.m_line,utf8CharCount(line.m_chars, 0, coordinates.m_column));
return Coordinates(coordinates.m_line,line.getColumnIndex(coordinates.m_column));
}
i32 TextEditor::lineCoordinatesToIndex(const Coordinates &coordinates) const {
@ -131,29 +148,14 @@ namespace hex::ui {
return -1;
const auto &line = m_lines[coordinates.m_line];
return utf8CharCount(line.m_chars, 0, coordinates.m_column);
}
i32 TextEditor::Line::getCharacterColumn(i32 index) const {
i32 col = 0;
i32 i = 0;
while (i < index && i < (i32) size()) {
auto c = m_chars[i];
i += TextEditor::utf8CharLength(c);
col++;
}
return col;
}
i32 TextEditor::Line::getMaxCharColumn() const {
return getCharacterColumn(size());
return line.getColumnIndex(coordinates.m_column);
}
TextEditor::Coordinates TextEditor::getCharacterCoordinates(i32 lineIndex, i32 strIndex) {
if (lineIndex < 0 || lineIndex >= (i32) m_lines.size())
return Coordinates(0, 0);
auto &line = m_lines[lineIndex];
return setCoordinates(lineIndex, line.getCharacterColumn(strIndex));
return setCoordinates(lineIndex, line.getCharColumn(strIndex));
}
u64 TextEditor::getLineByteCount(i32 lineIndex) const {
@ -164,10 +166,6 @@ namespace hex::ui {
return line.size();
}
i32 TextEditor::getLineMaxColumn(i32 lineIndex) {
return getLineCharacterCount(lineIndex);
}
TextEditor::Coordinates TextEditor::stringIndexToCoordinates(i32 strIndex, const std::string &input) {
if (strIndex < 0 || strIndex > (i32) input.size())
return TextEditor::Coordinates(0, 0);