From bfa97880999a9956156ba6d1cba6dcfd493180cd Mon Sep 17 00:00:00 2001 From: paxcut <53811119+paxcut@users.noreply.github.com> Date: Fri, 12 Dec 2025 08:27:26 -0700 Subject: [PATCH] impr: Various fixes and improvements to the pattern editor (#2559) - fixed crash when utf8 chars were present in text editor - fixed unable to scroll when cursor at line 1 - removed dependencies on thext editor that were not being used. I had to go back to the old code (old for me) and fit in the changes that were applied to the new code.That was only possible by incorporating some of the new structural differences to the text editor. This created new bugs and crashes that I ve have fixed but there may be ones that I couldn't find in the very small amount of time I could spend testing so that this commit wouldn't be delayed. If more crashes are found due to the mixing of old and new code they should be resolved when the new code is brought in. --- .../popup_hex_editor_decoded_string.hpp | 2 - .../include/content/views/view_bookmarks.hpp | 2 - .../content/views/view_pattern_editor.hpp | 4 +- .../popup_hex_editor_decoded_string.cpp | 2 - .../hex_editor/popup_hex_editor_fill.cpp | 1 - .../hex_editor/popup_hex_editor_find.cpp | 2 - .../popup_hex_editor_paste_behaviour.cpp | 2 - .../hex_editor/popup_hex_editor_remove.cpp | 2 - .../text_highlighting/pattern_language.cpp | 2 +- .../source/content/views/view_bookmarks.cpp | 1 + .../source/content/views/view_hex_editor.cpp | 1 - .../content/views/view_pattern_editor.cpp | 6 +- plugins/ui/include/ui/text_editor.hpp | 409 +++++++++--------- plugins/ui/source/ui/text_editor/editor.cpp | 114 ++--- .../ui/source/ui/text_editor/highlighter.cpp | 84 ++-- plugins/ui/source/ui/text_editor/navigate.cpp | 78 ++-- plugins/ui/source/ui/text_editor/render.cpp | 96 ++-- plugins/ui/source/ui/text_editor/support.cpp | 384 ++++++++++------ plugins/ui/source/ui/text_editor/utf8.cpp | 95 +++- 19 files changed, 747 insertions(+), 540 deletions(-) diff --git a/plugins/builtin/include/content/popups/hex_editor/popup_hex_editor_decoded_string.hpp b/plugins/builtin/include/content/popups/hex_editor/popup_hex_editor_decoded_string.hpp index 9da2db17c..a1ca04be7 100644 --- a/plugins/builtin/include/content/popups/hex_editor/popup_hex_editor_decoded_string.hpp +++ b/plugins/builtin/include/content/popups/hex_editor/popup_hex_editor_decoded_string.hpp @@ -3,7 +3,6 @@ #include #include -#include #include namespace hex::plugin::builtin { @@ -21,6 +20,5 @@ namespace hex::plugin::builtin { private: std::string m_decodedString; - ui::TextEditor m_editor; }; } diff --git a/plugins/builtin/include/content/views/view_bookmarks.hpp b/plugins/builtin/include/content/views/view_bookmarks.hpp index d664b462b..aecbb383d 100644 --- a/plugins/builtin/include/content/views/view_bookmarks.hpp +++ b/plugins/builtin/include/content/views/view_bookmarks.hpp @@ -3,8 +3,6 @@ #include #include -#include - #include #include diff --git a/plugins/builtin/include/content/views/view_pattern_editor.hpp b/plugins/builtin/include/content/views/view_pattern_editor.hpp index 7e62d3374..de256e729 100644 --- a/plugins/builtin/include/content/views/view_pattern_editor.hpp +++ b/plugins/builtin/include/content/views/view_pattern_editor.hpp @@ -157,8 +157,8 @@ namespace hex::plugin::builtin { PerProvider m_consoleCursorPosition; PerProvider m_cursorNeedsUpdate; PerProvider m_consoleCursorNeedsUpdate; - PerProvider m_selection; - PerProvider m_consoleSelection; + PerProvider m_selection; + PerProvider m_consoleSelection; PerProvider m_consoleLongestLineLength; PerProvider m_breakpoints; PerProvider> m_lastEvaluationError; diff --git a/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_decoded_string.cpp b/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_decoded_string.cpp index f46771265..ecce598e0 100644 --- a/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_decoded_string.cpp +++ b/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_decoded_string.cpp @@ -8,8 +8,6 @@ #include -#include - namespace hex::plugin::builtin { PopupDecodedString::PopupDecodedString(std::string decodedString) : m_decodedString(std::move(decodedString)) { diff --git a/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_fill.cpp b/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_fill.cpp index a71ceaab5..bf45563c5 100644 --- a/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_fill.cpp +++ b/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_fill.cpp @@ -13,7 +13,6 @@ #include -#include #include using namespace wolv::literals; diff --git a/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_find.cpp b/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_find.cpp index 0dd09e4de..9cdf25a54 100644 --- a/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_find.cpp +++ b/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_find.cpp @@ -18,8 +18,6 @@ #include -#include - namespace hex::plugin::builtin { PerProvider PopupFind::s_inputString; diff --git a/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_paste_behaviour.cpp b/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_paste_behaviour.cpp index 0f2852fdd..a4098e308 100644 --- a/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_paste_behaviour.cpp +++ b/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_paste_behaviour.cpp @@ -3,8 +3,6 @@ #include -#include - namespace hex::plugin::builtin { PopupPasteBehaviour::PopupPasteBehaviour(const Region &selection, const std::function &pasteCallback) diff --git a/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_remove.cpp b/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_remove.cpp index 2155e8c07..a397c32b3 100644 --- a/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_remove.cpp +++ b/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_remove.cpp @@ -6,8 +6,6 @@ #include -#include - namespace hex::plugin::builtin { PopupRemove::PopupRemove(u64 address, size_t size) : m_address(address), m_size(size) {} diff --git a/plugins/builtin/source/content/text_highlighting/pattern_language.cpp b/plugins/builtin/source/content/text_highlighting/pattern_language.cpp index 50cb2aeca..7b0737f45 100644 --- a/plugins/builtin/source/content/text_highlighting/pattern_language.cpp +++ b/plugins/builtin/source/content/text_highlighting/pattern_language.cpp @@ -1247,7 +1247,7 @@ namespace hex::plugin::builtin { if (isLocationValid(error.getLocation())) { auto key = ui::TextEditor::Coordinates(error.getLocation().line, error.getLocation().column); - if (!errorMarkers.contains(key) || errorMarkers[key].first < error.getLocation().length) + if (!errorMarkers.contains(key) || errorMarkers[key].first < (i32) error.getLocation().length) errorMarkers[key] = std::make_pair(error.getLocation().length, processMessage(error.getMessage())); } } diff --git a/plugins/builtin/source/content/views/view_bookmarks.cpp b/plugins/builtin/source/content/views/view_bookmarks.cpp index 48d844d55..52a727edb 100644 --- a/plugins/builtin/source/content/views/view_bookmarks.cpp +++ b/plugins/builtin/source/content/views/view_bookmarks.cpp @@ -20,6 +20,7 @@ #include #include +#include "imgui_internal.h" namespace hex::plugin::builtin { diff --git a/plugins/builtin/source/content/views/view_hex_editor.cpp b/plugins/builtin/source/content/views/view_hex_editor.cpp index 3323a5b90..fbb54aab8 100644 --- a/plugins/builtin/source/content/views/view_hex_editor.cpp +++ b/plugins/builtin/source/content/views/view_hex_editor.cpp @@ -43,7 +43,6 @@ #include #include #include -#include #include using namespace std::literals::string_literals; diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index 72bb1d50e..9dfc1fdc1 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -1352,7 +1352,7 @@ namespace hex::plugin::builtin { auto source = error.getLocation().source; if (source != nullptr && source->mainSource) { auto key = ui::TextEditor::Coordinates(error.getLocation().line, error.getLocation().column); - if (!errorMarkers.contains(key) ||errorMarkers[key].first < error.getLocation().length) + if (!errorMarkers.contains(key) || (u32) errorMarkers[key].first < error.getLocation().length) errorMarkers[key] = std::make_pair(u32(error.getLocation().length), processMessage(error.getMessage())); } } @@ -1860,7 +1860,7 @@ namespace hex::plugin::builtin { if (newProvider != nullptr) { m_textEditor.get(newProvider).setText(wolv::util::preprocessText(m_sourceCode.get(newProvider))); m_textEditor.get(newProvider).setCursorPosition(m_cursorPosition.get(newProvider)); - ui::TextEditor::Selection selection = m_selection.get(newProvider); + ui::TextEditor::Range selection = m_selection.get(newProvider); m_textEditor.get(newProvider).setSelection(selection); m_textEditor.get(newProvider).setBreakpoints(m_breakpoints.get(newProvider)); m_consoleEditor.get(newProvider).setText(wolv::util::combineStrings(m_console.get(newProvider), "\n")); @@ -2100,7 +2100,7 @@ namespace hex::plugin::builtin { /* Add Breakpoint */ ContentRegistry::UserInterface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.add_breakpoint"}, ICON_VS_DEBUG_BREAKPOINT_DATA, 1750, Keys::F8 + AllowWhileTyping, [this] { - const auto line = m_textEditor.get(ImHexApi::Provider::get()).getCursorPosition().m_line + 1; + const auto line = m_textEditor.get(ImHexApi::Provider::get()).getCursorPosition().getLine() + 1; const auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); auto &evaluator = runtime.getInternals().evaluator; diff --git a/plugins/ui/include/ui/text_editor.hpp b/plugins/ui/include/ui/text_editor.hpp index afb11d293..bb00c0a7a 100644 --- a/plugins/ui/include/ui/text_editor.hpp +++ b/plugins/ui/include/ui/text_editor.hpp @@ -14,68 +14,110 @@ #include "imgui.h" #include "imgui_internal.h" #include +#include namespace hex::ui { using strConstIter = std::string::const_iterator; class TextEditor { - public: - // indices of the arrays that contain the lines (vector) and the columns (a string) of the - // text editor. Negative values indicate the distance to the last element of the array. - // When comparing coordinates ensure they have the same sign because coordinates don't have - // information about the size of the array. Currently positive coordinates are always bigger - // than negatives even if that gives a wrong result. - struct Coordinates { - i32 m_line, m_column; - Coordinates() : m_line(0), m_column(0) {} - Coordinates(i32 line, i32 column) : m_line(line), m_column(column) {} - bool operator==(const Coordinates &o) const; - bool operator!=(const Coordinates &o) const; - bool operator<(const Coordinates &o) const; - bool operator>(const Coordinates &o) const; - bool operator<=(const Coordinates &o) const; - bool operator>=(const Coordinates &o) const; - Coordinates operator+(const Coordinates &o) const; - Coordinates operator-(const Coordinates &o) const; - }; + class Range { + public: + friend class TextEditor; + friend class Lines; - inline static const Coordinates Invalid = Coordinates(0x80000000, 0x80000000); + // Coordinates represent 2-dimensional points used to identify locations in the pattern editor. + // as line number and column number pairs. Coordinates can be folded and unfolded. Folded + // lines are called rows. Columns keep their name. You can convert lines to rows and vice versa or + // the supplied functions to convert whole coordinates. Unfolded and folded coordinates come in two types. + // The first one referred to as (plain) coordinates correspond to the line number for the y component + // and the utf8 character index within the line for the x coordinate. The second kind are given the + // name of Index coordinate or just plain index. These correspond directly + // to the indices of the c++ containers holding the editor data. Negative values are used to index + // from the end of the respective c++ container. In any document with N lines and M_N columns on each line + // first character can be described equally by the Coordinates (0,0) or (-M, -N_0) and the last one as either + // (M-1, N_(M-1)-1) or just (-1,-1). + class Coordinates { + public: + friend class TextEditor; + friend class ViewPatternEditor; + Coordinates() : m_line(0), m_column(0) {} + explicit Coordinates(pl::core::Location location) : m_line(location.line - 1), m_column(location.column - 1) {} + Coordinates(i32 lineIndex, i32 column) : m_line(lineIndex), m_column(column) {} + Coordinates(TextEditor *editor, i32 lineIndex, i32 column); + Coordinates sanitize(TextEditor *editor); + bool isValid(TextEditor *editor) const; + bool operator==(const Coordinates &o) const; + bool operator!=(const Coordinates &o) const; + bool operator<(const Coordinates &o) const; + bool operator>(const Coordinates &o) const; + bool operator<=(const Coordinates &o) const; + bool operator>=(const Coordinates &o) const; + Coordinates operator+(const Coordinates &o) const; + Coordinates operator-(const Coordinates &o) const; + i32 getLine() const { return m_line; } + i32 getColumn() const { return m_column; } - struct Selection { - Coordinates m_start; - Coordinates m_end; + private: + i32 m_line, m_column; + }; + + Range() = default; + explicit Range(std::pair coords) : m_start(coords.first), m_end(coords.second) { + if (m_start > m_end) { std::swap(m_start, m_end); }} + Range(Coordinates start, Coordinates end) : m_start(start), m_end(end) { + if (m_start > m_end) { std::swap(m_start, m_end); }} - Selection() = default; - Selection(Coordinates start, Coordinates end) : m_start(start), m_end(end) { - if (m_start > m_end) { - std::swap(m_start, m_end); - } - } Coordinates getSelectedLines(); Coordinates getSelectedColumns(); + Coordinates getStart() { return m_start; } + Coordinates getEnd() { return m_end; } bool isSingleLine(); - bool contains(Coordinates coordinates, int8_t endsInclusive=1); - bool operator==(const Selection &o) const { - return m_start == o.m_start && m_end == o.m_end; + enum class EndsInclusive : u8 { None = 0, Start = 2, End = 1, Both = 3 }; + bool contains(const Coordinates &coordinates, EndsInclusive endsInclusive = EndsInclusive::Both) const; + bool contains(const Range &range, EndsInclusive endsInclusive = EndsInclusive::Both) const; + bool containsLine(i32 value, EndsInclusive endsInclusive = EndsInclusive::Both) const; + bool containsColumn(i32 value, EndsInclusive endsInclusive = EndsInclusive::Both) const; + bool overlaps(const Range &o, EndsInclusive endsInclusive = EndsInclusive::Both) const; + bool operator==(const Range &o) const; + bool operator!=(const Range &o) const; + bool operator<(const Range &o) const { + return m_end < o.m_end; } - bool operator!=(const Selection &o) const { - return m_start != o.m_start || m_end != o.m_end; + bool operator>(const Range &o) const { + return m_end > o.m_end; } + bool operator<=(const Range &o) const { + return !(*this > o); + } + bool operator>=(const Range &o) const { + return !(*this < o); + } + + private: + Coordinates m_start; + Coordinates m_end; }; - struct EditorState { - Selection m_selection; + using Coordinates = Range::Coordinates; + class EditorState { + public: + friend class TextEditor; + bool operator==(const EditorState &o) const; + EditorState() : m_selection(), m_cursorPosition() {} + EditorState(const Range &selection, const Coordinates &cursorPosition) : m_selection(selection), m_cursorPosition(cursorPosition) {} + private: + Range m_selection; Coordinates m_cursorPosition; }; class FindReplaceHandler { public: FindReplaceHandler(); - typedef std::vector Matches; + using Matches = std::vector; Matches &getMatches() { return m_matches; } bool findNext(TextEditor *editor); u32 findMatch(TextEditor *editor, i32 index); @@ -139,153 +181,112 @@ namespace hex::ui { }; enum class PaletteIndex { - Default, - Identifier, - Directive, - Operator, - Separator, - BuiltInType, - Keyword, - NumericLiteral, - StringLiteral, - CharLiteral, - Cursor, - Background, - LineNumber, - Selection, - Breakpoint, - ErrorMarker, - PreprocessorDeactivated, - CurrentLineFill, - CurrentLineFillInactive, - CurrentLineEdge, - ErrorText, - WarningText, - DebugText, - DefaultText, - Attribute, - PatternVariable, - LocalVariable, - CalculatedPointer, - TemplateArgument, - Function, - View, - FunctionVariable, - FunctionParameter, - UserDefinedType, - PlacedVariable, - GlobalVariable, - NameSpace, - TypeDef, - UnkIdentifier, - DocComment, - DocBlockComment, - BlockComment, - GlobalDocComment, - Comment, - PreprocIdentifier, - Max + Default, Identifier, Directive, Operator, Separator, BuiltInType, Keyword, NumericLiteral, StringLiteral, CharLiteral, Cursor, Background, LineNumber, Selection, Breakpoint, ErrorMarker, PreprocessorDeactivated, + CurrentLineFill, CurrentLineFillInactive, CurrentLineEdge, ErrorText, WarningText, DebugText, DefaultText, Attribute, PatternVariable, LocalVariable, CalculatedPointer, TemplateArgument, Function, View, + FunctionVariable, FunctionParameter, UserDefinedType, PlacedVariable, GlobalVariable, NameSpace, TypeDef, UnkIdentifier, DocComment, DocBlockComment, BlockComment, GlobalDocComment, Comment, PreprocIdentifier, Max }; + using RegexList = std::vector>; + using Keywords = std::unordered_set; + using ErrorMarkers = std::map>; + using Breakpoints = std::unordered_set; + using Palette = std::array; + using Glyph = u8; - typedef std::vector> RegexList; - - struct Identifier { - Coordinates m_location; - std::string m_declaration; - }; - - using String = std::string; - using Identifiers = std::unordered_map; - using Keywords = std::unordered_set; - using ErrorMarkers = std::map>; - using Breakpoints = std::unordered_set; - using Palette = std::array; - using Glyph = uint8_t; + struct Identifier { Coordinates m_location; std::string m_declaration;}; + using Identifiers = std::unordered_map; class ActionableBox { - - ImRect m_box; public: - ActionableBox() = default; + ActionableBox() : m_box(ImRect(ImVec2(0, 0), ImVec2(0, 0))) {}; explicit ActionableBox(const ImRect &box) : m_box(box) {} - virtual bool trigger() { - return ImGui::IsMouseHoveringRect(m_box.Min, m_box.Max); - } - + ImRect &getBox() const { return const_cast(m_box);} + virtual bool trigger(); virtual void callback() {} + virtual ~ActionableBox() = default; + void shiftBoxVertically(float lineCount, float lineHeight); + private: + ImRect m_box; }; class CursorChangeBox : public ActionableBox { public: - CursorChangeBox() = default; + CursorChangeBox() : ActionableBox(ImRect(ImVec2(0, 0), ImVec2(0, 0))) {} explicit CursorChangeBox(const ImRect &box) : ActionableBox(box) {} - void callback() override { - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - } + void callback() override { ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);} }; class ErrorGotoBox : public ActionableBox { - Coordinates m_pos; public: ErrorGotoBox() = default; - ErrorGotoBox(const ImRect &box, const Coordinates &pos, TextEditor *editor) : ActionableBox(box), m_pos(pos), m_editor(editor) {} - bool trigger() override { - return ActionableBox::trigger() && ImGui::IsMouseClicked(0); - } - - void callback() override { - m_editor->jumpToCoords(m_pos); - } - + bool trigger() override { return ActionableBox::trigger() && ImGui::IsMouseClicked(0);} + void callback() override { m_editor->jumpToCoords(m_pos);} private: + Coordinates m_pos; TextEditor *m_editor = nullptr; }; - using ErrorGotoBoxes = std::map; - using CursorBoxes = std::map; - class ErrorHoverBox : public ActionableBox { - Coordinates m_pos; - std::string m_errorText; public: ErrorHoverBox() = default; ErrorHoverBox(const ImRect &box, const Coordinates &pos, const char *errorText) : ActionableBox(box), m_pos(pos), m_errorText(errorText) {} - void callback() override { - ImGui::BeginTooltip(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f)); - ImGui::Text("Error at line %d:", m_pos.m_line); - ImGui::PopStyleColor(); - ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.2f, 1.0f)); - ImGui::TextUnformatted(m_errorText.c_str()); - ImGui::PopStyleColor(); - ImGui::EndTooltip(); - } + void callback() override; + private: + Coordinates m_pos; + std::string m_errorText; }; - using ErrorHoverBoxes = std::map; + using ErrorGotoBoxes = std::map; + using CursorBoxes = std::map; + using ErrorHoverBoxes = std::map; + enum class TrimMode : u8 { TrimNone = 0, TrimEnd = 1, TrimStart = 2, TrimBoth = 3 }; // A line of text in the pattern editor consists of three strings; the character encoding, the color encoding and the flags. // The char encoding is utf-8, the color encoding are indices to the color palette and the flags are used to override the colors // depending on priorities; e.g. comments, strings, etc. + class LineIterator { + public: + friend class hex::ui::TextEditor; + LineIterator(const LineIterator &other) : m_charsIter(other.m_charsIter), m_colorsIter(other.m_colorsIter), m_flagsIter(other.m_flagsIter) {} + LineIterator() = default; + + char operator*(); + LineIterator operator++(); + LineIterator operator=(const LineIterator &other); + bool operator!=(const LineIterator &other) const; + bool operator==(const LineIterator &other) const; + LineIterator operator+(i32 n); + i32 operator-(LineIterator l); + private: + strConstIter m_charsIter; + strConstIter m_colorsIter; + strConstIter m_flagsIter; + }; + class Line { public: + friend class TextEditor; + enum class Comments : u8 { + NoComment = 0, + Doc = 0b0001, + Block = 0b0010, + BlockDoc = 0b0011, + Line = 0b0100, + Global = 0b0101, + }; struct FlagBits { - bool comment: 1; - bool blockComment: 1; - bool docComment: 1; - bool blockDocComment: 1; - bool globalDocComment: 1; + bool doc: 1; + bool block: 1; + bool global: 1; bool deactivated: 1; bool preprocessor: 1; - bool matchedBracket: 1; + bool matchedDelimiter: 1; }; union Flags { @@ -295,64 +296,40 @@ namespace hex::ui { char m_value; }; - constexpr static char inComment = 31; + enum class LinePart { Chars, Utf8, Colors, Flags }; - class LineIterator { - public: - strConstIter m_charsIter; - strConstIter m_colorsIter; - strConstIter m_flagsIter; - - LineIterator(const LineIterator &other) : m_charsIter(other.m_charsIter), m_colorsIter(other.m_colorsIter), m_flagsIter(other.m_flagsIter) {} - - LineIterator() = default; - - char operator*(); - LineIterator operator++(); - LineIterator operator=(const LineIterator &other); - bool operator!=(const LineIterator &other) const; - bool operator==(const LineIterator &other) const; - LineIterator operator+(i32 n); - i32 operator-(LineIterator l); - }; - - enum class LinePart { - Chars, - Utf8, - Colors, - Flags - }; + Line() : m_chars(""), m_colors(""), m_flags(""), m_colorized(false), m_lineMaxColumn(-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(maxColumn()) {} + Line(const Line &line) : m_chars(std::string(line.m_chars)), m_colors(std::string(line.m_colors)), m_flags(std::string(line.m_flags)), m_colorized(line.m_colorized), m_lineMaxColumn(line.m_lineMaxColumn) {} + Line(Line &&line) noexcept : m_chars(std::move(line.m_chars)), m_colors(std::move(line.m_colors)), m_flags(std::move(line.m_flags)), m_colorized(line.m_colorized), m_lineMaxColumn(line.m_lineMaxColumn) {} + Line(std::string chars, std::string colors, std::string flags) : m_chars(std::move(chars)), m_colors(std::move(colors)), m_flags(std::move(flags)), m_colorized(false), m_lineMaxColumn(maxColumn()) {} + bool operator==(const Line &o) const; + bool operator!=(const Line &o) const; + i32 indexColumn(i32 stringIndex) const; + i32 maxColumn(); + i32 maxColumn() const; + i32 columnIndex(i32 column) const; + i32 textSize() const; + i32 textSize(u32 index) const; + i32 lineTextSize(TrimMode trimMode = TrimMode::TrimNone); + i32 stringTextSize(const std::string &str) const; + i32 textSizeIndex(float textSize, i32 position); LineIterator begin() const; LineIterator end() const; - - std::string m_chars; - std::string m_colors; - std::string m_flags; - bool m_colorized = false; - i32 m_lineTextSize; - - 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_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 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); Line &operator=(Line &&line) noexcept; u64 size() const; + TextEditor::Line trim(TrimMode trimMode); char front(LinePart part = LinePart::Chars) const; std::string frontUtf8(LinePart part = LinePart::Chars) const; void push_back(char c); bool empty() const; std::string substr(u64 start, u64 length = (u64) -1, LinePart part = LinePart::Chars) const; + Line subLine(u64 start, u64 length = (u64) -1); char operator[](u64 index) const; // C++ can't overload functions based on return type, so use any type other // than u64 to avoid ambiguity. @@ -370,11 +347,18 @@ namespace hex::ui { void insert(LineIterator iter, LineIterator beginLine, LineIterator endLine); void erase(LineIterator begin); void erase(LineIterator begin, u64 count); - void erase(u64 start, u64 length = (u64) -1); + void erase(u64 start, i64 length = -1); void clear(); void setLine(const std::string &text); void setLine(const Line &text); bool needsUpdate() const; + bool isEndOfLine(i32 column); + private: + std::string m_chars; + std::string m_colors; + std::string m_flags; + bool m_colorized = false; + i32 m_lineMaxColumn; }; using Lines = std::vector; @@ -382,9 +366,7 @@ namespace hex::ui { struct LanguageDefinition { typedef std::pair TokenRegexString; typedef std::vector TokenRegexStrings; - - typedef bool(*TokenizeCallback)(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, - strConstIter &out_end, PaletteIndex &paletteIndex); + typedef bool(*TokenizeCallback)(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end, PaletteIndex &paletteIndex); std::string m_name; Keywords m_keywords; @@ -397,11 +379,8 @@ namespace hex::ui { TokenRegexStrings m_tokenRegexStrings; bool m_caseSensitive; - LanguageDefinition() : m_name(""), m_keywords({}), m_identifiers({}), m_preprocIdentifiers({}), - m_singleLineComment(""), m_commentEnd(""), - m_commentStart(""), m_globalDocComment(""), m_docComment(""), m_blockDocComment(""), - m_preprocChar('#'), m_autoIndentation(true), m_tokenize(nullptr), - m_tokenRegexStrings({}), m_caseSensitive(true) {} + LanguageDefinition() : m_name(""), m_keywords({}), m_identifiers({}), m_preprocIdentifiers({}), m_singleLineComment(""), m_commentEnd(""), m_commentStart(""), m_globalDocComment(""), + m_docComment(""), m_blockDocComment(""), m_preprocChar('#'), m_autoIndentation(true), m_tokenize(nullptr), m_tokenRegexStrings({}), m_caseSensitive(true) {} static const LanguageDefinition &CPlusPlus(); static const LanguageDefinition &HLSL(); @@ -411,30 +390,29 @@ namespace hex::ui { static const LanguageDefinition &AngelScript(); static const LanguageDefinition &Lua(); }; + TextEditor(); ~TextEditor(); - private: class UndoRecord { public: + friend class TextEditor; UndoRecord() {} - ~UndoRecord() {} - UndoRecord( const std::string &added, - const TextEditor::Selection addedSelection, + const TextEditor::Range addedRange, const std::string &removed, - const TextEditor::Selection removedSelection, + const TextEditor::Range removedRange, TextEditor::EditorState &before, TextEditor::EditorState &after); void undo(TextEditor *editor); void redo(TextEditor *editor); - + private: std::string m_added; - Selection m_addedSelection; + Range m_addedRange; std::string m_removed; - Selection m_removedSelection; + Range m_removedRange; EditorState m_before; EditorState m_after; }; @@ -521,8 +499,8 @@ namespace hex::ui { static const Palette &getRetroBluePalette(); bool isColorizerEnabled() const { return m_colorizerEnabled; } const LanguageDefinition &getLanguageDefinition() const { return m_languageDefinition; } - void setNeedsUpdate(u32 line, bool needsUpdate); - void setColorizedLine(u64 line, const std::string &tokens); + void setNeedsUpdate(i32 line, bool needsUpdate); + void setColorizedLine(i64 line, const std::string &tokens); private: void colorizeRange(); void colorizeInternal(); @@ -558,8 +536,8 @@ namespace hex::ui { inline void setHandleKeyboardInputs(bool value) { m_handleKeyboardInputs = value; } inline bool isHandleKeyboardInputsEnabled() const { return m_handleKeyboardInputs; } private: - std::string getText(const Selection &selection); - void deleteRange(const Selection &selection); + std::string getText(const Range &selection); + void deleteRange(const Range &selection); i32 insertTextAt(Coordinates &where, const std::string &value); void removeLine(i32 start, i32 end); void removeLine(i32 index); @@ -587,7 +565,7 @@ namespace hex::ui { private: Coordinates setCoordinates(const Coordinates &value); Coordinates setCoordinates(i32 line, i32 column); - Selection setCoordinates(const Selection &value); + Range setCoordinates(const Range &value); void advance(Coordinates &coordinates) const; Coordinates findWordStart(const Coordinates &from); Coordinates findWordEnd(const Coordinates &from); @@ -596,8 +574,8 @@ namespace hex::ui { u32 skipSpaces(const Coordinates &from); //Support public: - void setSelection(const Selection &selection); - Selection getSelection() const; + void setSelection(const Range &selection); + Range getSelection() const; void selectWordUnderCursor(); void selectAll(); bool hasSelection() const; @@ -611,7 +589,7 @@ namespace hex::ui { inline bool isImGuiChildIgnored() const { return m_ignoreImGuiChild; } bool raiseContextMenu() { return m_raiseContextMenu; } void clearRaiseContextMenu() { m_raiseContextMenu = false; } - TextEditor *GetSourceCodeEditor(); + TextEditor *getSourceCodeEditor(); bool isEmpty() const; private: void addUndo(UndoRecord &value); @@ -623,17 +601,17 @@ namespace hex::ui { static i32 imTextCharToUtf8(char *buffer, i32 buf_size, u32 c); static void imTextCharToUtf8(std::string &buffer, u32 c); static i32 utf8CharLength(uint8_t c); - static i32 getStringCharacterCount(const std::string &str); + static i32 stringCharacterCount(const std::string &str); static TextEditor::Coordinates stringIndexToCoordinates(i32 strIndex, const std::string &input); + i32 lineMaxColumn(i32 lineIndex); private: Coordinates screenPosToCoordinates(const ImVec2 &position); Coordinates lineCoordsToIndexCoords(const Coordinates &coordinates) const; i32 lineCoordinatesToIndex(const Coordinates &coordinates) const; Coordinates getCharacterCoordinates(i32 line, i32 index); - i32 getLineCharColumn(i32 lineIndex, i32 stringIndex); + i32 lineIndexColumn(i32 lineIndex, i32 stringIndex); u64 getLineByteCount(i32 line) const; - i32 getLineMaxCharColumn(i32 lineIndex); public: FindReplaceHandler m_findReplaceHandler; @@ -679,7 +657,7 @@ namespace hex::ui { ErrorGotoBoxes m_errorGotoBoxes = {}; CursorBoxes m_cursorBoxes = {}; ImVec2 m_charAdvance = {}; - Selection m_interactiveSelection = {}; + Range m_interactiveSelection = {}; u64 m_startTime = 0; std::vector m_defines; TextEditor *m_sourceCodeEditor = nullptr; @@ -696,6 +674,9 @@ namespace hex::ui { std::vector m_clickableText; + constexpr static char inComment = 7; + inline static const Line m_emptyLine = Line(); + inline static const Coordinates Invalid = Coordinates(0x80000000, 0x80000000); static const i32 s_cursorBlinkInterval; static const i32 s_cursorBlinkOnTime; static ImVec2 s_cursorScreenPosition; diff --git a/plugins/ui/source/ui/text_editor/editor.cpp b/plugins/ui/source/ui/text_editor/editor.cpp index bc2232713..93283bf6b 100644 --- a/plugins/ui/source/ui/text_editor/editor.cpp +++ b/plugins/ui/source/ui/text_editor/editor.cpp @@ -27,7 +27,7 @@ namespace hex::ui { TextEditor::~TextEditor() { } - std::string TextEditor::getText(const Selection &from) { + std::string TextEditor::getText(const Range &from) { std::string result; auto selection = setCoordinates(from); auto columns = selection.getSelectedColumns(); @@ -45,10 +45,10 @@ namespace hex::ui { return result; } - void TextEditor::deleteRange(const Selection &rangeToDelete) { + void TextEditor::deleteRange(const Range &rangeToDelete) { if (m_readOnly) return; - Selection selection = setCoordinates(rangeToDelete); + Range selection = setCoordinates(rangeToDelete); auto columns = selection.getSelectedColumns(); if (selection.isSingleLine()) { @@ -100,7 +100,7 @@ namespace hex::ui { auto stringVector = wolv::util::splitString(value, "\n", false); auto lineCount = (i32) stringVector.size(); where.m_line += lineCount - 1; - where.m_column += getStringCharacterCount(stringVector[lineCount - 1]); + where.m_column += stringCharacterCount(stringVector[lineCount - 1]); stringVector[lineCount - 1].append(line.substr(start.m_column,(u64) -1, Line::LinePart::Utf8)); line.erase(start.m_column, (u64) -1); @@ -117,14 +117,14 @@ namespace hex::ui { void TextEditor::deleteWordLeft() { const auto wordEnd = getCursorPosition(); const auto wordStart = findPreviousWord(getCursorPosition()); - setSelection(Selection(wordStart, wordEnd)); + setSelection(Range(wordStart, wordEnd)); backspace(); } void TextEditor::deleteWordRight() { const auto wordStart = getCursorPosition(); const auto wordEnd = findNextWord(getCursorPosition()); - setSelection(Selection(wordStart, wordEnd)); + setSelection(Range(wordStart, wordEnd)); backspace(); } @@ -214,9 +214,9 @@ namespace hex::ui { if (!m_readOnly && undo) { u.m_before = m_state; u.m_removed = getText(); - u.m_removedSelection.m_start = setCoordinates(0, 0); - u.m_removedSelection.m_end = setCoordinates(-1, -1); - if (u.m_removedSelection.m_start == Invalid || u.m_removedSelection.m_end == Invalid) + u.m_removedRange.m_start = setCoordinates(0, 0); + u.m_removedRange.m_end = setCoordinates(-1, -1); + if (u.m_removedRange.m_start == Invalid || u.m_removedRange.m_end == Invalid) return; } auto vectorString = wolv::util::splitString(text, "\n", false); @@ -236,9 +236,9 @@ namespace hex::ui { } if (!m_readOnly && undo) { u.m_added = text; - u.m_addedSelection.m_start = setCoordinates(0, 0); - u.m_addedSelection.m_end = setCoordinates(-1, -1); - if (u.m_addedSelection.m_start == Invalid || u.m_addedSelection.m_end == Invalid) + u.m_addedRange.m_start = setCoordinates(0, 0); + u.m_addedRange.m_end = setCoordinates(-1, -1); + if (u.m_addedRange.m_start == Invalid || u.m_addedRange.m_end == Invalid) return; } m_textChanged = true; @@ -275,10 +275,10 @@ 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 = getLineMaxCharColumn(end.m_line); + end.m_column = lineMaxColumn(end.m_line); - u.m_removedSelection = Selection(start, end); - u.m_removed = getText(u.m_removedSelection); + u.m_removedRange = Range(start, end); + u.m_removed = getText(u.m_removedRange); bool modified = false; @@ -312,19 +312,19 @@ namespace hex::ui { if (end == Invalid) return; rangeEnd = end; - u.m_added = getText(Selection(start, end)); + u.m_added = getText(Range(start, end)); } else { end = setCoordinates(originalEnd.m_line, 0); rangeEnd = setCoordinates(end.m_line - 1, -1); if (end == Invalid || rangeEnd == Invalid) return; - u.m_added = getText(Selection(start, rangeEnd)); + u.m_added = getText(Range(start, rangeEnd)); } - u.m_addedSelection = Selection(start, rangeEnd); + u.m_addedRange = Range(start, rangeEnd); u.m_after = m_state; - m_state.m_selection = Selection(start, end); + m_state.m_selection = Range(start, end); addUndo(u); m_textChanged = true; @@ -336,13 +336,13 @@ namespace hex::ui { } // c == '\t' else { u.m_removed = getSelectedText(); - u.m_removedSelection = Selection(m_state.m_selection); + u.m_removedRange = Range(m_state.m_selection); deleteSelection(); } } // HasSelection auto coord = setCoordinates(m_state.m_cursorPosition); - u.m_addedSelection.m_start = coord; + u.m_addedRange.m_start = coord; if (m_lines.empty()) m_lines.push_back(Line()); @@ -372,7 +372,7 @@ namespace hex::ui { line.m_colorized = false; setCursorPosition(getCharacterCoordinates(coord.m_line + 1, charPosition)); u.m_added = (char) character; - u.m_addedSelection.m_end = setCoordinates(m_state.m_cursorPosition); + u.m_addedRange.m_end = setCoordinates(m_state.m_cursorPosition); } else if (character == '\t') { auto &line = m_lines[coord.m_line]; auto charIndex = lineCoordinatesToIndex(coord); @@ -384,7 +384,7 @@ namespace hex::ui { line.m_colorized = false; setCursorPosition(getCharacterCoordinates(coord.m_line, charIndex + spacesToInsert)); u.m_added = spaces; - u.m_addedSelection.m_end = setCoordinates(m_state.m_cursorPosition); + u.m_addedRange.m_end = setCoordinates(m_state.m_cursorPosition); } else { auto spacesToRemove = (charIndex % m_tabSize); if (spacesToRemove == 0) spacesToRemove = m_tabSize; @@ -399,11 +399,11 @@ namespace hex::ui { } std::string spaces(spacesRemoved, ' '); u.m_removed = spaces; - u.m_removedSelection = Selection(Coordinates(coord.m_line, charIndex), Coordinates(coord.m_line, charIndex + spacesRemoved)); + u.m_removedRange = Range(Coordinates(coord.m_line, charIndex), Coordinates(coord.m_line, charIndex + spacesRemoved)); line.m_colorized = false; setCursorPosition(getCharacterCoordinates(coord.m_line, std::max(0, charIndex))); } - u.m_addedSelection.m_end = setCoordinates(m_state.m_cursorPosition); + u.m_addedRange.m_end = setCoordinates(m_state.m_cursorPosition); } else { std::string buf = ""; imTextCharToUtf8(buf, character); @@ -414,15 +414,15 @@ namespace hex::ui { if (m_overwrite && charIndex < (i32) line.size()) { i64 column = coord.m_column; std::string c = line[column]; - auto charCount = getStringCharacterCount(c); + auto charCount = stringCharacterCount(c); auto d = c.size(); - u.m_removedSelection = Selection(m_state.m_cursorPosition, getCharacterCoordinates(coord.m_line, coord.m_column + charCount)); + u.m_removedRange = Range(m_state.m_cursorPosition, getCharacterCoordinates(coord.m_line, coord.m_column + charCount)); u.m_removed = std::string(line.m_chars.begin() + charIndex, line.m_chars.begin() + charIndex + d); line.erase(line.begin() + charIndex, d); line.m_colorized = false; } - auto charCount = getStringCharacterCount(buf); + auto charCount = stringCharacterCount(buf); if (buf == "{") buf += "}"; else if (buf == "[") @@ -455,7 +455,7 @@ namespace hex::ui { line.insert(line.begin() + charIndex, buf.begin(), buf.end()); line.m_colorized = false; u.m_added = buf; - u.m_addedSelection.m_end = getCharacterCoordinates(coord.m_line, charIndex + buf.size()); + u.m_addedRange.m_end = getCharacterCoordinates(coord.m_line, charIndex + buf.size()); setCursorPosition(getCharacterCoordinates(coord.m_line, charIndex + charCount)); } else return; @@ -491,7 +491,7 @@ namespace hex::ui { insertTextAt(pos, value); m_lines[pos.m_line].m_colorized = false; - setSelection(Selection(pos, pos)); + setSelection(Range(pos, pos)); setCursorPosition(pos); refreshSearchResults(); colorize(); @@ -504,7 +504,7 @@ namespace hex::ui { deleteRange(m_state.m_selection); - setSelection(Selection(m_state.m_selection.m_start, m_state.m_selection.m_start)); + setSelection(Range(m_state.m_selection.m_start, m_state.m_selection.m_start)); setCursorPosition(m_state.m_selection.m_start); refreshSearchResults(); colorize(); @@ -521,20 +521,20 @@ namespace hex::ui { if (hasSelection()) { u.m_removed = getSelectedText(); - u.m_removedSelection = m_state.m_selection; + u.m_removedRange = m_state.m_selection; deleteSelection(); } else { auto pos = setCoordinates(m_state.m_cursorPosition); setCursorPosition(pos); auto &line = m_lines[pos.m_line]; - if (pos.m_column == getLineMaxCharColumn(pos.m_line)) { + if (pos.m_column == lineMaxColumn(pos.m_line)) { if (pos.m_line == (i32) m_lines.size() - 1) return; u.m_removed = '\n'; - u.m_removedSelection.m_start = u.m_removedSelection.m_end = setCoordinates(m_state.m_cursorPosition); - advance(u.m_removedSelection.m_end); + u.m_removedRange.m_start = u.m_removedRange.m_end = setCoordinates(m_state.m_cursorPosition); + advance(u.m_removedRange.m_end); auto &nextLine = m_lines[pos.m_line + 1]; line.insert(line.end(), nextLine.begin(), nextLine.end()); @@ -543,9 +543,9 @@ namespace hex::ui { } else { i64 charIndex = lineCoordinatesToIndex(pos); - u.m_removedSelection.m_start = u.m_removedSelection.m_end = setCoordinates(m_state.m_cursorPosition); - u.m_removedSelection.m_end.m_column++; - u.m_removed = getText(u.m_removedSelection); + u.m_removedRange.m_start = u.m_removedRange.m_end = setCoordinates(m_state.m_cursorPosition); + u.m_removedRange.m_end.m_column++; + u.m_removed = getText(u.m_removedRange); auto d = utf8CharLength(line[charIndex][0]); line.erase(line.begin() + charIndex, d); @@ -572,7 +572,7 @@ namespace hex::ui { if (hasSelection()) { u.m_removed = getSelectedText(); - u.m_removedSelection = m_state.m_selection; + u.m_removedRange = m_state.m_selection; deleteSelection(); } else { auto pos = setCoordinates(m_state.m_cursorPosition); @@ -583,11 +583,11 @@ namespace hex::ui { return; u.m_removed = '\n'; - u.m_removedSelection.m_start = u.m_removedSelection.m_end = setCoordinates(pos.m_line - 1, -1); - advance(u.m_removedSelection.m_end); + u.m_removedRange.m_start = u.m_removedRange.m_end = setCoordinates(pos.m_line - 1, -1); + advance(u.m_removedRange.m_end); auto &prevLine = m_lines[pos.m_line - 1]; - auto prevSize = getLineMaxCharColumn(pos.m_line - 1); + auto prevSize = lineMaxColumn(pos.m_line - 1); if (prevSize == 0) prevLine = line; else @@ -625,7 +625,7 @@ namespace hex::ui { m_state.m_cursorPosition.m_column += 1; } } - u.m_removedSelection = Selection(pos, m_state.m_cursorPosition); + u.m_removedRange = Range(pos, m_state.m_cursorPosition); u.m_removed = charToRemove; auto charStart = lineCoordinatesToIndex(pos); auto charEnd = lineCoordinatesToIndex(m_state.m_cursorPosition); @@ -666,12 +666,12 @@ namespace hex::ui { auto lineIndex = setCoordinates(m_state.m_cursorPosition).m_line; if (lineIndex < 0 || lineIndex >= (i32) m_lines.size()) return; - setSelection(Selection(setCoordinates(lineIndex, 0), setCoordinates(lineIndex + 1, 0))); + setSelection(Range(setCoordinates(lineIndex, 0), setCoordinates(lineIndex + 1, 0))); } UndoRecord u; u.m_before = m_state; u.m_removed = getSelectedText(); - u.m_removedSelection = m_state.m_selection; + u.m_removedRange = m_state.m_selection; copy(); deleteSelection(); @@ -691,15 +691,15 @@ namespace hex::ui { if (hasSelection()) { u.m_removed = getSelectedText(); - u.m_removedSelection = m_state.m_selection; + u.m_removedRange = m_state.m_selection; deleteSelection(); } u.m_added = clipTextStr; - u.m_addedSelection.m_start = setCoordinates(m_state.m_cursorPosition); + u.m_addedRange.m_start = setCoordinates(m_state.m_cursorPosition); insertText(clipTextStr); - u.m_addedSelection.m_end = setCoordinates(m_state.m_cursorPosition); + u.m_addedRange.m_end = setCoordinates(m_state.m_cursorPosition); u.m_after = m_state; addUndo(u); } @@ -748,7 +748,7 @@ namespace hex::ui { auto end = setCoordinates(-1, -1); if (start == Invalid || end == Invalid) return ""; - return getText(Selection(start, end)); + return getText(Range(start, end)); } std::vector TextEditor::getTextLines() const { @@ -773,25 +773,25 @@ namespace hex::ui { auto endLine = setCoordinates(line, -1); if (sanitizedLine == Invalid || endLine == Invalid) return ""; - return getText(Selection(sanitizedLine, endLine)); + return getText(Range(sanitizedLine, endLine)); } TextEditor::UndoRecord::UndoRecord( const std::string &added, - const TextEditor::Selection addedSelection, + const TextEditor::Range addedSelection, const std::string &removed, - const TextEditor::Selection removedSelection, + const TextEditor::Range removedSelection, TextEditor::EditorState &before, - TextEditor::EditorState &after) : m_added(added), m_addedSelection(addedSelection), m_removed(removed), m_removedSelection(removedSelection), m_before(before), m_after(after) {} + TextEditor::EditorState &after) : m_added(added), m_addedRange(addedSelection), m_removed(removed), m_removedRange(removedSelection), m_before(before), m_after(after) {} void TextEditor::UndoRecord::undo(TextEditor *editor) { if (!m_added.empty()) { - editor->deleteRange(m_addedSelection); + editor->deleteRange(m_addedRange); editor->colorize(); } if (!m_removed.empty()) { - auto start = m_removedSelection.m_start; + auto start = m_removedRange.m_start; editor->insertTextAt(start, m_removed.c_str()); editor->colorize(); } @@ -802,12 +802,12 @@ namespace hex::ui { void TextEditor::UndoRecord::redo(TextEditor *editor) { if (!m_removed.empty()) { - editor->deleteRange(m_removedSelection); + editor->deleteRange(m_removedRange); editor->colorize(); } if (!m_added.empty()) { - auto start = m_addedSelection.m_start; + auto start = m_addedRange.m_start; editor->insertTextAt(start, m_added.c_str()); editor->colorize(); } diff --git a/plugins/ui/source/ui/text_editor/highlighter.cpp b/plugins/ui/source/ui/text_editor/highlighter.cpp index 2bdbd2f6f..f09711a09 100644 --- a/plugins/ui/source/ui/text_editor/highlighter.cpp +++ b/plugins/ui/source/ui/text_editor/highlighter.cpp @@ -15,13 +15,13 @@ namespace hex::ui { return first1 == last1 && first2 == last2; } - void TextEditor::setNeedsUpdate(u32 line, bool needsUpdate) { - if (line < m_lines.size()) + void TextEditor::setNeedsUpdate(i32 line, bool needsUpdate) { + if (line < (i32) m_lines.size()) m_lines[line].setNeedsUpdate(needsUpdate); } - void TextEditor::setColorizedLine(u64 line, const std::string &tokens) { - if (line < m_lines.size()) { + void TextEditor::setColorizedLine(i64 line, const std::string &tokens) { + if (line < (i64)m_lines.size()) { auto &lineTokens = m_lines[line].m_colors; if (lineTokens.size() != tokens.size()) { lineTokens.resize(tokens.size()); @@ -103,7 +103,7 @@ namespace hex::ui { token_color = PaletteIndex::GlobalVariable; } } else { - if ((token_color == PaletteIndex::Identifier || flags.m_bits.preprocessor) && !flags.m_bits.deactivated && !(flags.m_value & Line::inComment)) { + if ((token_color == PaletteIndex::Identifier || flags.m_bits.preprocessor) && !flags.m_bits.deactivated && !(flags.m_value & inComment)) { id.assign(token_begin, token_end); if (m_languageDefinition.m_preprocIdentifiers.count(id) != 0) { token_color = PaletteIndex::Directive; @@ -115,7 +115,7 @@ namespace hex::ui { token_length = token_end - token_begin; } } - if (flags.m_bits.matchedBracket) { + if (flags.m_bits.matchedDelimiter) { token_color = PaletteIndex::WarningText; token_length = token_end - token_begin; } else if (flags.m_bits.preprocessor && !flags.m_bits.deactivated) { @@ -125,7 +125,7 @@ namespace hex::ui { token_color = PaletteIndex::PreprocessorDeactivated; token_begin -= 1; token_offset -= 1; - } else if (id.assign(token_begin, token_end);flags.m_value & Line::inComment && m_languageDefinition.m_preprocIdentifiers.count(id) != 0) { + } else if (id.assign(token_begin, token_end);flags.m_value & inComment && m_languageDefinition.m_preprocIdentifiers.count(id) != 0) { token_color = getColorIndexFromFlags(flags); token_begin -= 1; token_offset -= 1; @@ -202,13 +202,18 @@ namespace hex::ui { auto setGlyphFlags = [&](i32 index) { Line::Flags flags(0); - flags.m_bits.comment = withinComment; - flags.m_bits.blockComment = withinBlockComment; - flags.m_bits.docComment = withinDocComment; - flags.m_bits.globalDocComment = withinGlobalDocComment; - flags.m_bits.blockDocComment = withinBlockDocComment; + if (withinComment) + flags.m_value = (i32) Line::Comments::Line; + else if (withinDocComment) + flags.m_value = (i32) Line::Comments::Doc; + else if (withinBlockComment) + flags.m_value = (i32) Line::Comments::Block; + else if (withinGlobalDocComment) + flags.m_value = (i32) Line::Comments::Global; + else if (withinBlockDocComment) + flags.m_value = (i32) Line::Comments::BlockDoc; flags.m_bits.deactivated = withinNotDef; - flags.m_bits.matchedBracket = matchedBracket; + flags.m_bits.matchedDelimiter = matchedBracket; if (m_lines[currentLine].m_flags[index] != flags.m_value) { m_lines[currentLine].m_colorized = false; m_lines[currentLine].m_flags[index] = flags.m_value; @@ -227,12 +232,31 @@ namespace hex::ui { if (m_matchedBracket.m_nearCursor == getCharacterCoordinates(currentLine, currentIndex) || m_matchedBracket.m_matched == getCharacterCoordinates(currentLine, currentIndex)) matchedBracket = true; } else if (MatchedBracket::s_operators.contains(c) && m_matchedBracket.isActive()) { - if (m_matchedBracket.m_nearCursor == setCoordinates(currentLine, currentIndex) || m_matchedBracket.m_matched == setCoordinates(currentLine, currentIndex)) { - if ((c == '<' && (line.m_colors[currentIndex - 1] == static_cast(PaletteIndex::UserDefinedType))) || - (c == '>' && (m_matchedBracket.m_matched.m_column > 0) && (line.m_colors[lineCoordinatesToIndex(m_matchedBracket.m_matched) - 1] == static_cast(PaletteIndex::UserDefinedType) || - ((m_matchedBracket.m_nearCursor.m_column > 0) && (line.m_colors[lineCoordinatesToIndex(m_matchedBracket.m_nearCursor) - 1] == static_cast(PaletteIndex::UserDefinedType)))))) { - matchedBracket = true; + Coordinates current = setCoordinates(currentLine,currentIndex); + auto udt = static_cast(PaletteIndex::UserDefinedType); + Coordinates cursor = Invalid; + //if (m_matchedBracket.m_nearCursor == setCoordinates(currentLine, currentIndex) || m_matchedBracket.m_matched == setCoordinates(currentLine, currentIndex)) { + if ((c == '<' && m_matchedBracket.m_nearCursor == current) || (c == '>' && m_matchedBracket.m_matched == current)) + cursor = m_matchedBracket.m_nearCursor; + else if ((c == '>' && m_matchedBracket.m_nearCursor == current) || (c == '<' && m_matchedBracket.m_matched == current)) + cursor = m_matchedBracket.m_matched; + + if (cursor != Invalid) { + if (cursor.m_column == 0 && cursor.m_line > 0) { + cursor.m_line--; + cursor.m_column = m_lines[cursor.m_line].m_colors.size() - 1; + } else if (cursor.m_column > 0) { + cursor.m_column--; } + while (std::isblank(m_lines[cursor.m_line].m_colors[cursor.m_column]) && (cursor.m_line != 0 || cursor.m_column != 0)) { + if (cursor.m_column == 0 && cursor.m_line > 0) { + cursor.m_line--; + cursor.m_column = m_lines[cursor.m_line].m_colors.size() - 1; + } else + cursor.m_column--; + } + if (m_lines[cursor.m_line].m_colors[cursor.m_column] == udt && (cursor.m_line != 0 || cursor.m_column != 0)) + matchedBracket = true; } } @@ -240,7 +264,7 @@ namespace hex::ui { if (c != m_languageDefinition.m_preprocChar && !isspace(c)) firstChar = false; - bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= (i64) currentIndex)); + bool isComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= (i64) currentIndex)); if (withinString) { setGlyphFlags(currentIndex); @@ -250,7 +274,7 @@ namespace hex::ui { } else if (c == '\"') withinString = false; } else { - if (firstChar && c == m_languageDefinition.m_preprocChar && !inComment && !withinComment && !withinDocComment && !withinString) { + if (firstChar && c == m_languageDefinition.m_preprocChar && !isComment && !withinComment && !withinDocComment && !withinString) { withinPreproc = true; std::string directive; auto start = currentIndex + 1; @@ -271,21 +295,23 @@ namespace hex::ui { identifier += line[start]; start++; } + auto definesBegin = m_defines.begin(); + auto definesEnd = m_defines.end(); if (directive == "define") { - if (identifier.size() > 0 && !withinNotDef && std::find(m_defines.begin(), m_defines.end(), identifier) == m_defines.end()) + if (identifier.size() > 0 && !withinNotDef && std::find(definesBegin, definesEnd, identifier) == definesEnd) m_defines.push_back(identifier); } else if (directive == "undef") { if (identifier.size() > 0 && !withinNotDef) - m_defines.erase(std::remove(m_defines.begin(), m_defines.end(), identifier), m_defines.end()); + m_defines.erase(std::remove(definesBegin, definesEnd, identifier), definesEnd); } else if (directive == "ifdef") { if (!withinNotDef) { - bool isConditionMet = std::find(m_defines.begin(), m_defines.end(), identifier) != m_defines.end(); + bool isConditionMet = std::find(definesBegin, definesEnd, identifier) != definesEnd; ifDefs.push_back(isConditionMet); } else ifDefs.push_back(false); } else if (directive == "ifndef") { if (!withinNotDef) { - bool isConditionMet = std::find(m_defines.begin(), m_defines.end(), identifier) == m_defines.end(); + bool isConditionMet = std::find(definesBegin, definesEnd, identifier) == definesEnd; ifDefs.push_back(isConditionMet); } else ifDefs.push_back(false); @@ -293,7 +319,7 @@ namespace hex::ui { } } - if (c == '\"' && !withinPreproc && !inComment && !withinComment && !withinDocComment) { + if (c == '\"' && !withinPreproc && !isComment && !withinComment && !withinDocComment) { withinString = true; setGlyphFlags(currentIndex); } else { @@ -307,12 +333,12 @@ namespace hex::ui { return !a.empty() && currentIndex + 1 >= 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 (!isComment && !withinComment && !withinDocComment && !withinPreproc && !withinString) { if (compareForth(m_languageDefinition.m_docComment, line.m_chars)) { - withinDocComment = !inComment; + withinDocComment = !isComment; commentLength = 3; } else if (compareForth(m_languageDefinition.m_singleLineComment, line.m_chars)) { - withinComment = !inComment; + withinComment = !isComment; commentLength = 2; } else { bool isGlobalDocComment = compareForth(m_languageDefinition.m_globalDocComment, line.m_chars); @@ -338,7 +364,7 @@ namespace hex::ui { } } } - inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= (i64) currentIndex)); + isComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= (i64) currentIndex)); } setGlyphFlags(currentIndex); diff --git a/plugins/ui/source/ui/text_editor/navigate.cpp b/plugins/ui/source/ui/text_editor/navigate.cpp index c0487b684..3b5c103c0 100644 --- a/plugins/ui/source/ui/text_editor/navigate.cpp +++ b/plugins/ui/source/ui/text_editor/navigate.cpp @@ -18,7 +18,7 @@ namespace hex::ui { } void TextEditor::jumpToCoords(const Coordinates &coords) { - setSelection(Selection(coords, coords)); + setSelection(Range(coords, coords)); setCursorPosition(coords); ensureCursorVisible(); @@ -38,7 +38,7 @@ namespace hex::ui { else if (oldPos == m_interactiveSelection.m_end) m_interactiveSelection.m_end = newPos; else { - m_interactiveSelection = Selection(newPos, oldPos); + m_interactiveSelection = Range(newPos, oldPos); } } else m_interactiveSelection.m_start = m_interactiveSelection.m_end = newPos; @@ -124,7 +124,7 @@ namespace hex::ui { return; auto lindex = m_state.m_cursorPosition.m_line; - auto lineMaxColumn = getLineMaxCharColumn(lindex); + auto lineMaxColumn = this->lineMaxColumn(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 = getLineMaxCharColumn(lindex); + auto lineMaxColumn = this->lineMaxColumn(lindex); auto column = std::min(m_state.m_cursorPosition.m_column, lineMaxColumn); while (amount-- > 0) { @@ -207,7 +207,7 @@ namespace hex::ui { if (m_state.m_cursorPosition != oldPos) { if (select) { - m_interactiveSelection = Selection(m_state.m_cursorPosition, oldPos); + m_interactiveSelection = Range(m_state.m_cursorPosition, oldPos); } else m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition; setSelection(m_interactiveSelection); @@ -220,7 +220,7 @@ namespace hex::ui { auto newPos = setCoordinates(-1, -1); setCursorPosition(newPos); if (select) { - m_interactiveSelection = Selection(oldPos, newPos); + m_interactiveSelection = Range(oldPos, newPos); } else m_interactiveSelection.m_start = m_interactiveSelection.m_end = newPos; setSelection(m_interactiveSelection); @@ -245,7 +245,7 @@ namespace hex::ui { else { postIdx = postfix.find_first_not_of(" "); if (postIdx == std::string::npos) - home = getLineMaxCharColumn(oldPos.m_line); + home = this->lineMaxColumn(oldPos.m_line); else if (postIdx == 0) home = 0; else @@ -260,7 +260,7 @@ namespace hex::ui { else { postIdx = postfix.find_first_not_of(" "); if (postIdx == std::string::npos) - home = getLineMaxCharColumn(oldPos.m_line); + home = lineMaxColumn(oldPos.m_line); else home = oldPos.m_column + postIdx; } @@ -286,7 +286,7 @@ namespace hex::ui { void TextEditor::moveEnd(bool select) { resetCursorBlinkTime(); auto oldPos = m_state.m_cursorPosition; - setCursorPosition(setCoordinates(m_state.m_cursorPosition.m_line, getLineMaxCharColumn(oldPos.m_line))); + setCursorPosition(setCoordinates(m_state.m_cursorPosition.m_line, lineMaxColumn(oldPos.m_line))); if (m_state.m_cursorPosition != oldPos) { if (select) { @@ -326,24 +326,46 @@ namespace hex::ui { setCursorPosition(m_state.m_selection.m_end); } + TextEditor::Coordinates::Coordinates(TextEditor *editor, i32 line, i32 column) + : m_line(line), m_column(column) { + sanitize(editor); + } + + bool TextEditor::Coordinates::isValid(TextEditor *editor) const { + + auto maxLine = editor->m_lines.size(); + if (std::abs(m_line) > (i32) maxLine) + return false; + auto maxColumn = editor->lineMaxColumn(m_line); + if (std::abs(m_column) > maxColumn) + return false; + return true; + } + + TextEditor::Coordinates TextEditor::Coordinates::sanitize(TextEditor *editor) { + + i32 lineCount = editor->m_lines.size(); + if (m_line < 0) { + m_line = std::clamp(m_line, -lineCount, -1); + m_line = lineCount + m_line; + } else + m_line = std::clamp(m_line, 0, lineCount - 1); + + + auto maxColumn = editor->lineMaxColumn(m_line) + 1; + if (m_column < 0) { + m_column = std::clamp(m_column, -maxColumn, -1); + m_column = maxColumn + m_column; + } else + m_column = std::clamp(m_column, 0, maxColumn); + + return *this; + } + TextEditor::Coordinates TextEditor::setCoordinates(i32 line, i32 column) { if (isEmpty()) return Coordinates(0, 0); - - Coordinates result = Coordinates(0, 0); - auto lineCount = (i32) m_lines.size(); - if (line < 0 && lineCount + line >= 0) - result.m_line = lineCount + line; - else - result.m_line = std::clamp(line, 0, lineCount - 1); - - auto maxColumn = getLineMaxCharColumn(result.m_line) + 1; - if (column < 0 && maxColumn + column >= 0) - result.m_column = maxColumn + column; - else - result.m_column = std::clamp(column, 0, maxColumn - 1); - - return result; + return Coordinates(this, line, column); } TextEditor::Coordinates TextEditor::setCoordinates(const Coordinates &value) { @@ -351,15 +373,15 @@ namespace hex::ui { return sanitized_value; } - TextEditor::Selection TextEditor::setCoordinates(const Selection &value) { + TextEditor::Range TextEditor::setCoordinates(const Range &value) { auto start = setCoordinates(value.m_start); auto end = setCoordinates(value.m_end); if (start == Invalid || end == Invalid) - return Selection(Invalid, Invalid); + return Range(Invalid, Invalid); if (start > end) { std::swap(start, end); } - return Selection(start, end); + return Range(start, end); } void TextEditor::advance(Coordinates &coordinates) const { @@ -373,7 +395,7 @@ namespace hex::ui { auto &line = m_lines[coordinates.m_line]; i64 column = coordinates.m_column; std::string lineChar = line[column]; - auto incr = getStringCharacterCount(lineChar); + auto incr = stringCharacterCount(lineChar); coordinates.m_column += incr; } diff --git a/plugins/ui/source/ui/text_editor/render.cpp b/plugins/ui/source/ui/text_editor/render.cpp index 5f49199f3..0ed48e25c 100644 --- a/plugins/ui/source/ui/text_editor/render.cpp +++ b/plugins/ui/source/ui/text_editor/render.cpp @@ -70,8 +70,7 @@ namespace hex::ui { } float TextEditor::getPageSize() const { - auto height = ImGui::GetCurrentWindow()->InnerClipRect.GetHeight(); - return height / m_charAdvance.y; + return ImGui::GetCurrentWindow()->InnerClipRect.GetHeight() / m_charAdvance.y; } bool TextEditor::isEndOfLine() const { @@ -84,7 +83,7 @@ namespace hex::ui { bool TextEditor::isEndOfLine(const Coordinates &coordinates) const { if (coordinates.m_line < (i32) m_lines.size()) - return coordinates.m_column >= getStringCharacterCount(m_lines[coordinates.m_line].m_chars); + return coordinates.m_column >= stringCharacterCount(m_lines[coordinates.m_line].m_chars); return true; } @@ -196,49 +195,57 @@ namespace hex::ui { auto windowPadding = ImGui::GetStyle().FramePadding * 2.0f; - auto height = ImGui::GetWindowHeight() - m_topMargin - scrollBarSize - m_charAdvance.y; + auto height = ImGui::GetWindowHeight() - m_topMargin - scrollBarSize; auto width = ImGui::GetWindowWidth() - windowPadding.x - scrollBarSize; - auto topPixels = m_topMargin > scrollY ? m_topMargin - scrollY : scrollY; - auto top = (i32) rint(topPixels / m_charAdvance.y) + 1; - top -= (top >= (i32) m_lines.size()); - auto bottom = (i32) rint((topPixels + height) / m_charAdvance.y); + auto top = m_topMargin > scrollY ? m_topMargin - scrollY : scrollY; + auto topRow = (i32) rint(top / m_charAdvance.y); + auto bottomRow = (i32) rint((top + height) / m_charAdvance.y); - auto left = (i32) rint(scrollX / m_charAdvance.x); - auto right = (i32) rint((scrollX + width) / m_charAdvance.x); + auto leftColumnIndex = (i32) rint(scrollX / m_charAdvance.x); + auto rightColumnIndex = (i32) rint((scrollX + width) / m_charAdvance.x); auto pos = setCoordinates(m_state.m_cursorPosition); pos.m_column = (i32) rint(textDistanceToLineStart(pos) / m_charAdvance.x); + auto posColumnIndex = (i32) rint(textDistanceToLineStart(pos) / m_charAdvance.x); + auto posRow = pos.m_line; + bool scrollToCursorX = true; + bool scrollToCursorY = true; - bool mScrollToCursorX = true; - bool mScrollToCursorY = true; + if ((posRow > topRow && posRow < bottomRow) || + (posRow == topRow && topRow == top && scrollY == ImGui::GetScrollMaxY())) + scrollToCursorY = false; - if (pos.m_line >= top && pos.m_line <= bottom) - mScrollToCursorY = false; - if ((pos.m_column >= left) && (pos.m_column <= right)) - mScrollToCursorX = false; - if (!mScrollToCursorX && !mScrollToCursorY && m_oldTopMargin == m_topMargin) { + if ((posColumnIndex >= leftColumnIndex) && (posColumnIndex <= rightColumnIndex)) + scrollToCursorX = false; + + if ((!scrollToCursorX && !scrollToCursorY && m_oldTopMargin == m_topMargin) || pos.m_line < 0) { m_scrollToCursor = false; return; } - if (mScrollToCursorY) { - if (pos.m_line < top) { - ImGui::SetScrollY(std::max(0.0f, (pos.m_line - 1) * m_charAdvance.y)); + if (scrollToCursorY) { + if (posRow <= topRow) { + if (posRow <= 0) { + ImGui::SetScrollY(0.0f); + m_scrollToCursor = false; + return; + } + ImGui::SetScrollY((posRow - 1) * m_charAdvance.y); m_scrollToCursor = true; } - if (pos.m_line > bottom) { - ImGui::SetScrollY(std::max(0.0f, pos.m_line * m_charAdvance.y - height)); + if (posRow >= bottomRow) { + ImGui::SetScrollY((posRow + 1) * m_charAdvance.y - height); m_scrollToCursor = true; } } - if (mScrollToCursorX) { - if (pos.m_column < left) { - ImGui::SetScrollX(std::max(0.0f, pos.m_column * m_charAdvance.x)); + if (scrollToCursorX) { + if (posColumnIndex < leftColumnIndex) { + ImGui::SetScrollX(std::max(0.0f, posColumnIndex * m_charAdvance.x)); m_scrollToCursor = true; } - if (pos.m_column > right) { - ImGui::SetScrollX(std::max(0.0f, pos.m_column * m_charAdvance.x - width)); + if (posColumnIndex > rightColumnIndex) { + ImGui::SetScrollX(std::max(0.0f, posColumnIndex * m_charAdvance.x - width)); m_scrollToCursor = true; } } @@ -285,7 +292,8 @@ namespace hex::ui { if (m_state.m_cursorPosition.m_line == lineNo && m_showCursor && focused) renderCursor(lineNo, drawList); - renderGotoButtons(lineNo); + if (!m_showLineNumbers) + renderGotoButtons(lineNo); // Render colorized text @@ -298,13 +306,13 @@ namespace hex::ui { continue; } auto colors = m_lines[lineNo].m_colors; - auto lineSize = line.getLineTextSize(); + auto lineSize = line.lineTextSize(); 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()); + auto maxColumn = line.indexColumn(line.size()); if (textSize < start) { while (textSize < start && head.m_column < maxColumn) { head.m_column += 1; @@ -330,8 +338,8 @@ namespace hex::ui { } } - u64 i = line.getColumnIndex(head.m_column); - u64 maxI = line.getColumnIndex(current.m_column); + u64 i = line.columnIndex(head.m_column); + u64 maxI = line.columnIndex(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); @@ -343,7 +351,7 @@ namespace hex::ui { u32 tokenLength = std::clamp((u64) index,(u64) 1, maxI - i); if (m_updateFocus) setFocus(); - auto lineStart = setCoordinates(lineNo, line.getCharColumn(i)); + auto lineStart = setCoordinates(lineNo, line.indexColumn(i)); drawText(lineStart, i, tokenLength, color); @@ -398,7 +406,7 @@ namespace hex::ui { void TextEditor::drawSelection(float lineNo) { ImVec2 lineStartScreenPos = s_cursorScreenPosition + ImVec2(m_leftMargin, m_topMargin + std::floor(lineNo) * m_charAdvance.y); - Selection lineCoords = Selection(setCoordinates(lineNo, 0), setCoordinates(lineNo, -1)); + Range lineCoords = Range(setCoordinates(lineNo, 0), setCoordinates(lineNo, -1)); auto drawList = ImGui::GetWindowDrawList(); if (m_state.m_selection.m_start <= lineCoords.m_end && m_state.m_selection.m_end > lineCoords.m_start) { @@ -485,6 +493,8 @@ namespace hex::ui { } void TextEditor::renderGotoButtons(float lineNo) { + if (isEmpty()) + return; ImVec2 lineStartScreenPos = s_cursorScreenPosition + ImVec2(m_leftMargin, m_topMargin + std::floor(lineNo) * m_charAdvance.y); auto lineText = getLineText(lineNo); Coordinates gotoKey = setCoordinates(lineNo + 1, 0); @@ -510,13 +520,13 @@ namespace hex::ui { if (!errorColumn.empty()) currColumn = std::stoi(errorColumn) - 1; } - TextEditor::Coordinates errorPos = GetSourceCodeEditor()->setCoordinates(currLine, currColumn); + TextEditor::Coordinates errorPos = getSourceCodeEditor()->setCoordinates(currLine, currColumn); if (errorPos != Invalid) { ImVec2 errorStart = ImVec2(lineStartScreenPos.x, lineStartScreenPos.y); auto lineEnd = setCoordinates(lineNo, -1); if (lineEnd != Invalid) { ImVec2 errorEnd = ImVec2(lineStartScreenPos.x + textDistanceToLineStart(lineEnd), lineStartScreenPos.y + m_charAdvance.y); - ErrorGotoBox box = ErrorGotoBox(ImRect({errorStart, errorEnd}), errorPos, GetSourceCodeEditor()); + ErrorGotoBox box = ErrorGotoBox(ImRect({errorStart, errorEnd}), errorPos, getSourceCodeEditor()); m_errorGotoBoxes[gotoKey] = box; CursorChangeBox cursorBox = CursorChangeBox(ImRect({errorStart, errorEnd})); m_cursorBoxes[gotoKey] = cursorBox; @@ -621,16 +631,10 @@ namespace hex::ui { } float TextEditor::textDistanceToLineStart(const Coordinates &aFrom) { - auto &line = m_lines[aFrom.m_line]; - i32 colIndex = lineCoordinatesToIndex(aFrom); - 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; - } + auto line = m_lines[aFrom.m_line]; + if (line.empty() || aFrom.m_column == 0) + return 0.0f; - return line.getStringTextSize(substr1.c_str()); + return line.textSize(line.columnIndex(aFrom.m_column)); } } \ No newline at end of file diff --git a/plugins/ui/source/ui/text_editor/support.cpp b/plugins/ui/source/ui/text_editor/support.cpp index 958fe4154..a7ec416da 100644 --- a/plugins/ui/source/ui/text_editor/support.cpp +++ b/plugins/ui/source/ui/text_editor/support.cpp @@ -3,70 +3,88 @@ #include namespace hex::ui { - bool TextEditor::Coordinates::operator==(const Coordinates &o) const { - return m_line == o.m_line && m_column == o.m_column; + using Coordinates = TextEditor::Coordinates; + using Line = TextEditor::Line; + using LineIterator = TextEditor::LineIterator; + using Range = TextEditor::Range; + using FindReplaceHandler = TextEditor::FindReplaceHandler; + bool Coordinates::operator==(const Coordinates &o) const { + return m_line == o.m_line && m_column == o.m_column; } - bool TextEditor::Coordinates::operator!=(const Coordinates &o) const { + bool Coordinates::operator!=(const Coordinates &o) const { return m_line != o.m_line || m_column != o.m_column; } - bool TextEditor::Coordinates::operator<(const Coordinates &o) const { + bool Coordinates::operator<(const Coordinates &o) const { if (m_line != o.m_line) return m_line < o.m_line; return m_column < o.m_column; } - bool TextEditor::Coordinates::operator>(const Coordinates &o) const { + bool Coordinates::operator>(const Coordinates &o) const { if (m_line != o.m_line) return m_line > o.m_line; return m_column > o.m_column; } - bool TextEditor::Coordinates::operator<=(const Coordinates &o) const { - if (m_line != o.m_line) - return m_line < o.m_line; - return m_column <= o.m_column; + bool Coordinates::operator<=(const Coordinates &o) const { + return !(*this > o); } - bool TextEditor::Coordinates::operator>=(const Coordinates &o) const { - if (m_line != o.m_line) - return m_line > o.m_line; - return m_column >= o.m_column; + bool Coordinates::operator>=(const Coordinates &o) const { + return !(*this < o); } - TextEditor::Coordinates TextEditor::Coordinates::operator+(const Coordinates &o) const { + Coordinates Coordinates::operator+(const Coordinates &o) const { return Coordinates(m_line + o.m_line, m_column + o.m_column); } - TextEditor::Coordinates TextEditor::Coordinates::operator-(const Coordinates &o) const { + Coordinates Coordinates::operator-(const Coordinates &o) const { return Coordinates(m_line - o.m_line, m_column - o.m_column); } - TextEditor::Coordinates TextEditor::Selection::getSelectedLines() { + bool Range::operator==(const Range &o) const { + return m_start == o.m_start && m_end == o.m_end; + } + bool Range::operator!=(const Range &o) const { + return m_start != o.m_start || m_end != o.m_end; + } + + Coordinates Range::getSelectedLines() { return Coordinates(m_start.m_line, m_end.m_line); } - TextEditor::Coordinates TextEditor::Selection::getSelectedColumns() { + Coordinates Range::getSelectedColumns() { if (isSingleLine()) return Coordinates(m_start.m_column, m_end.m_column - m_start.m_column); return Coordinates(m_start.m_column, m_end.m_column); } - bool TextEditor::Selection::isSingleLine() { + bool Range::isSingleLine() { return m_start.m_line == m_end.m_line; } - bool TextEditor::Selection::contains(Coordinates coordinates, int8_t endsInclusive) { + bool Range::contains(const Range &range, EndsInclusive endsInclusive) const { + return contains(range.m_start, endsInclusive) && contains(range.m_end, endsInclusive); + } + + bool Range::overlaps(const Range &o, EndsInclusive endsInclusive) const { + return contains(o.m_start, endsInclusive) || contains(o.m_end, endsInclusive) || + o.contains(m_start, endsInclusive) || o.contains(m_end, endsInclusive); + } + + // 0 = exclude both ends, 1 = include end, exclude start, 2 = include start, exclude end, 3 = include both ends + bool Range::contains(const Coordinates &coordinates, EndsInclusive endsInclusive) const { bool result = true; - if (endsInclusive & 2) + if ((u8)endsInclusive & 2) result &= m_start <= coordinates; else result &= m_start < coordinates; if (!result) return false; - if (endsInclusive & 1) + if ((u8)endsInclusive & 1) result &= coordinates <= m_end; else result &= coordinates < m_end; @@ -74,11 +92,55 @@ namespace hex::ui { return result; } - char TextEditor::Line::LineIterator::operator*() { + bool Range::containsLine(i32 value, EndsInclusive endsInclusive) const { + bool result = true; + if ((u8)endsInclusive & 2) + result &= m_start.m_line <= value; + else + result &= m_start.m_line < value; + + if (!result) + return false; + if ((u8)endsInclusive & 1) + result &= value <= m_end.m_line; + else + result &= value < m_end.m_line; + + return result; + } + + bool Range::containsColumn(i32 value, EndsInclusive endsInclusive) const { + bool result = true; + if ((u8)endsInclusive & 2) + result &= m_start.m_column <= value; + else + result &= m_start.m_column < value; + + if (!result) + return false; + if ((u8)endsInclusive & 1) + result &= value <= m_end.m_column; + else + result &= value < m_end.m_column; + + return result; + } + + bool Line::operator==(const Line &line) const { + return 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; + } + + bool Line::operator!=(const Line &line) const { + return 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; + } + + char LineIterator::operator*() { return *m_charsIter; } - TextEditor::Line::LineIterator TextEditor::Line::LineIterator::operator++() { + LineIterator LineIterator::operator++() { LineIterator iter = *this; ++iter.m_charsIter; ++iter.m_colorsIter; @@ -86,24 +148,24 @@ namespace hex::ui { return iter; } - TextEditor::Line::LineIterator TextEditor::Line::LineIterator::operator=(const LineIterator &other) { + LineIterator LineIterator::operator=(const LineIterator &other) { m_charsIter = other.m_charsIter; m_colorsIter = other.m_colorsIter; m_flagsIter = other.m_flagsIter; return *this; } - bool TextEditor::Line::LineIterator::operator!=(const LineIterator &other) const { + bool LineIterator::operator!=(const LineIterator &other) const { return m_charsIter != other.m_charsIter || m_colorsIter != other.m_colorsIter || m_flagsIter != other.m_flagsIter; } - bool TextEditor::Line::LineIterator::operator==(const LineIterator &other) const { + bool LineIterator::operator==(const LineIterator &other) const { return m_charsIter == other.m_charsIter && m_colorsIter == other.m_colorsIter && m_flagsIter == other.m_flagsIter; } - TextEditor::Line::LineIterator TextEditor::Line::LineIterator::operator+(i32 n) { + LineIterator LineIterator::operator+(i32 n) { LineIterator iter = *this; iter.m_charsIter += n; iter.m_colorsIter += n; @@ -111,11 +173,11 @@ namespace hex::ui { return iter; } - i32 TextEditor::Line::LineIterator::operator-(LineIterator l) { + i32 LineIterator::operator-(LineIterator l) { return m_charsIter - l.m_charsIter; } - TextEditor::Line::LineIterator TextEditor::Line::begin() const { + LineIterator Line::begin() const { LineIterator iter; iter.m_charsIter = m_chars.begin(); iter.m_colorsIter = m_colors.begin(); @@ -123,7 +185,7 @@ namespace hex::ui { return iter; } - TextEditor::Line::LineIterator TextEditor::Line::end() const { + LineIterator Line::end() const { LineIterator iter; iter.m_charsIter = m_chars.end(); iter.m_colorsIter = m_colors.end(); @@ -131,7 +193,7 @@ namespace hex::ui { return iter; } - TextEditor::Line::LineIterator TextEditor::Line::begin() { + LineIterator Line::begin() { LineIterator iter; iter.m_charsIter = m_chars.begin(); iter.m_colorsIter = m_colors.begin(); @@ -139,7 +201,7 @@ namespace hex::ui { return iter; } - TextEditor::Line::LineIterator TextEditor::Line::end() { + LineIterator Line::end() { LineIterator iter; iter.m_charsIter = m_chars.end(); iter.m_colorsIter = m_colors.end(); @@ -147,29 +209,29 @@ namespace hex::ui { return iter; } - TextEditor::Line &TextEditor::Line::operator=(const Line &line) { + Line &Line::operator=(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; + m_lineMaxColumn = line.m_lineMaxColumn; return *this; } - TextEditor::Line &TextEditor::Line::operator=(Line &&line) noexcept { + Line &Line::operator=(Line &&line) noexcept { m_chars = std::move(line.m_chars); m_colors = std::move(line.m_colors); m_flags = std::move(line.m_flags); m_colorized = line.m_colorized; - m_lineTextSize = line.m_lineTextSize; + m_lineMaxColumn = line.m_lineMaxColumn; return *this; } - u64 TextEditor::Line::size() const { + u64 Line::size() const { return m_chars.size(); } - char TextEditor::Line::front(LinePart part) const { + char Line::front(LinePart part) const { if (part == LinePart::Chars && !m_chars.empty()) return m_chars.front(); if (part == LinePart::Colors && !m_colors.empty()) @@ -179,7 +241,7 @@ namespace hex::ui { return 0x00; } - std::string TextEditor::Line::frontUtf8(LinePart part) const { + std::string Line::frontUtf8(LinePart part) const { if (part == LinePart::Chars && !m_chars.empty()) return m_chars.substr(0, TextEditor::utf8CharLength(m_chars[0])); if (part == LinePart::Colors && !m_colors.empty()) @@ -189,33 +251,42 @@ namespace hex::ui { return ""; } - void TextEditor::Line::push_back(char c) { + void Line::push_back(char c) { m_chars.push_back(c); m_colors.push_back(0x00); m_flags.push_back(0x00); m_colorized = false; - m_lineTextSize = -1; + m_lineMaxColumn = -1; } - bool TextEditor::Line::empty() const { + bool Line::empty() const { return m_chars.empty(); } - std::string TextEditor::Line::substr(u64 start, u64 length, LinePart part) const { - if (start >= m_chars.size() || m_colors.size() != m_chars.size() || m_flags.size() != m_chars.size()) - return ""; - if (length == (u64) -1 || start + length >= m_chars.size()) - length = m_chars.size() - start; - if (length == 0) - return ""; + std::string Line::substr(u64 start, u64 length, LinePart part) const { + + if (part != LinePart::Utf8) { + if (start >= m_chars.size() || m_colors.size() != m_chars.size() || m_flags.size() != m_chars.size()) + return ""; + if (length == (u64) -1 || start + length >= m_chars.size()) + length = m_chars.size() - start; + if (length == 0) + return ""; + + if (part == LinePart::Chars) + return m_chars.substr(start, length); + if (part == LinePart::Colors) + return m_colors.substr(start, length); + if (part == LinePart::Flags) + return m_flags.substr(start, length); + } else { + if (start >= (u64) maxColumn()) + return ""; + if (length == (u64) -1 || start + length >= (u64) maxColumn()) + length = maxColumn() - start; + if (length == 0) + return ""; - if (part == LinePart::Chars) - return m_chars.substr(start, length); - if (part == LinePart::Colors) - return m_colors.substr(start, length); - if (part == LinePart::Flags) - return m_flags.substr(start, length); - if (part == LinePart::Utf8) { u64 utf8Start = 0; for (u64 utf8Index = 0; utf8Index < start; ++utf8Index) { utf8Start += TextEditor::utf8CharLength(m_chars[utf8Start]); @@ -229,52 +300,76 @@ namespace hex::ui { return ""; } - char TextEditor::Line::operator[](u64 index) const { - index = std::clamp(index, (u64) 0, (u64) (m_chars.size() - 1)); - return m_chars[index]; + Line Line::subLine(u64 start, u64 length) { + if (start >= m_chars.size()) + return const_cast(m_emptyLine); + if (m_colors.size() != m_chars.size()) + m_colors.resize(m_chars.size(), 0x00); + if (m_flags.size() != m_chars.size()) + m_flags.resize(m_chars.size(), 0x00); + if (length == (u64) -1 || start + length >= m_chars.size()) + length = m_chars.size() - start; + if (length == 0) + return const_cast(m_emptyLine); + + std::string chars = m_chars.substr(start, length); + std::string colors = m_colors.substr(start, length); + std::string flags = m_flags.substr(start, length); + Line result(chars, colors, flags); + result.m_colorized = m_colorized; + result.m_lineMaxColumn = result.maxColumn(); + return result; + } + + char Line::operator[](u64 index) const { + i64 signedIndex = std::clamp((i64) index,0 - (i64) m_chars.size(), (i64) (m_chars.size() - 1)); + if (signedIndex < 0) + return m_chars[m_chars.size() + signedIndex]; + return m_chars[signedIndex]; } // C++ can't overload functions based on return type, so use any type other // than u64 to avoid ambiguity. - std::string TextEditor::Line::operator[](i64 column) const { - u64 utf8Length = TextEditor::getStringCharacterCount(m_chars); - u64 index = static_cast(column); - index = std::clamp(index, (u64) 0, utf8Length - 1); - u64 utf8Start = 0; - for (u64 utf8Index = 0; utf8Index < index; ++utf8Index) { + std::string Line::operator[](i64 index) const { + i64 utf8Length = TextEditor::stringCharacterCount(m_chars); + index = std::clamp(index, (i64) -utf8Length, (i64) utf8Length - 1); + if (index < 0) + index = utf8Length + index; + i64 utf8Start = 0; + for (i64 utf8Index = 0; utf8Index < index; ++utf8Index) { utf8Start += TextEditor::utf8CharLength(m_chars[utf8Start]); } - u64 utf8CharLen = TextEditor::utf8CharLength(m_chars[utf8Start]); - if (utf8Start + utf8CharLen > m_chars.size()) + i64 utf8CharLen = TextEditor::utf8CharLength(m_chars[utf8Start]); + if (utf8Start + utf8CharLen > (i64) m_chars.size()) utf8CharLen = m_chars.size() - utf8Start; return m_chars.substr(utf8Start, utf8CharLen); } - void TextEditor::Line::setNeedsUpdate(bool needsUpdate) { + void Line::setNeedsUpdate(bool needsUpdate) { m_colorized = m_colorized && !needsUpdate; } - void TextEditor::Line::append(const char *text) { + void Line::append(const char *text) { append(std::string(text)); } - void TextEditor::Line::append(const char text) { + void Line::append(const char text) { append(std::string(1, text)); } - void TextEditor::Line::append(const std::string &text) { + void Line::append(const std::string &text) { Line line(text); append(line); } - void TextEditor::Line::append(const Line &line) { + void Line::append(const Line &line) { append(line.begin(), line.end()); } - void TextEditor::Line::append(LineIterator begin, LineIterator end) { + void Line::append(LineIterator begin, LineIterator end) { if (begin.m_charsIter < end.m_charsIter) { m_chars.append(begin.m_charsIter, end.m_charsIter); - m_lineTextSize = -1; + m_lineMaxColumn = -1; } if (begin.m_colorsIter < end.m_colorsIter) m_colors.append(begin.m_colorsIter, end.m_colorsIter); @@ -283,24 +378,24 @@ namespace hex::ui { m_colorized = false; } - void TextEditor::Line::insert(LineIterator iter, const std::string &text) { + void Line::insert(LineIterator iter, const std::string &text) { insert(iter, text.begin(), text.end()); } - void TextEditor::Line::insert(LineIterator iter, const char text) { + void Line::insert(LineIterator iter, const char text) { insert(iter, std::string(1, text)); } - void TextEditor::Line::insert(LineIterator iter, strConstIter beginString, strConstIter endString) { + void Line::insert(LineIterator iter, strConstIter beginString, strConstIter endString) { Line line(std::string(beginString, endString)); insert(iter, line); } - void TextEditor::Line::insert(LineIterator iter, const Line &line) { + void Line::insert(LineIterator iter, const Line &line) { insert(iter, line.begin(), line.end()); } - void TextEditor::Line::insert(LineIterator iter, LineIterator beginLine, LineIterator endLine) { + void Line::insert(LineIterator iter, LineIterator beginLine, LineIterator endLine) { if (iter == end()) append(beginLine, endLine); else { @@ -308,19 +403,19 @@ 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; - m_lineTextSize = -1; + m_lineMaxColumn = -1; } } - void TextEditor::Line::erase(LineIterator begin) { + void Line::erase(LineIterator begin) { m_chars.erase(begin.m_charsIter); m_colors.erase(begin.m_colorsIter); m_flags.erase(begin.m_flagsIter); m_colorized = false; - m_lineTextSize = -1; + m_lineMaxColumn = -1; } - void TextEditor::Line::erase(LineIterator begin, u64 count) { + void Line::erase(LineIterator begin, u64 count) { if (count == (u64) -1) count = m_chars.size() - (begin.m_charsIter - m_chars.begin()); else @@ -329,85 +424,113 @@ 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; - m_lineTextSize = -1; + m_lineMaxColumn = -1; } - void TextEditor::Line::erase(u64 start, u64 length) { - if (length == (u64) -1 || start + length >= m_chars.size()) - length = m_chars.size() - start; + void Line::erase(u64 start, i64 length) { + if (m_chars.empty() || start >= (u64) maxColumn()) + return; + if (length < 0 || start >= (u64) maxColumn() - length) + length = maxColumn() - start; u64 utf8Start = 0; for (u64 utf8Index = 0; utf8Index < start; ++utf8Index) { utf8Start += TextEditor::utf8CharLength(m_chars[utf8Start]); } u64 utf8Length = 0; - for (u64 utf8Index = 0; utf8Index < length; ++utf8Index) { + for (i64 utf8Index = 0; utf8Index < length; ++utf8Index) { utf8Length += TextEditor::utf8CharLength(m_chars[utf8Start + utf8Length]); } utf8Length = std::min(utf8Length, (u64) (m_chars.size() - utf8Start)); erase(begin() + utf8Start, utf8Length); } - void TextEditor::Line::clear() { + void Line::clear() { m_chars.clear(); m_colors.clear(); m_flags.clear(); m_colorized = false; - m_lineTextSize = -1; + m_lineMaxColumn = -1; } - void TextEditor::Line::setLine(const std::string &text) { + void Line::setLine(const std::string &text) { m_chars = text; m_colors = std::string(text.size(), 0x00); m_flags = std::string(text.size(), 0x00); m_colorized = false; - m_lineTextSize = -1; + m_lineMaxColumn = -1; } - void TextEditor::Line::setLine(const Line &text) { + void Line::setLine(const Line &text) { m_chars = text.m_chars; m_colors = text.m_colors; m_flags = text.m_flags; m_colorized = text.m_colorized; - m_lineTextSize = text.m_lineTextSize; + m_lineMaxColumn = text.m_lineMaxColumn; } - bool TextEditor::Line::needsUpdate() const { + bool Line::needsUpdate() const { return !m_colorized; } - TextEditor *TextEditor::GetSourceCodeEditor() { + bool TextEditor::ActionableBox::trigger() { + auto mousePos = ImGui::GetMousePos(); + if (mousePos.x <= m_box.Min.x || mousePos.x >= m_box.Max.x || + mousePos.y < m_box.Min.y || mousePos.y > m_box.Max.y) + return false; + return true; + } + + void TextEditor::ActionableBox::shiftBoxVertically(float lineCount, float lineHeight) { + m_box.Min.y += lineCount * lineHeight; + m_box.Max.y += lineCount * lineHeight; + } + + void TextEditor::ErrorHoverBox::callback() { + ImGui::BeginTooltip(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f)); + ImGui::Text("Error at line %d:", m_pos.m_line); + ImGui::PopStyleColor(); + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.2f, 1.0f)); + ImGui::TextUnformatted(m_errorText.c_str()); + ImGui::PopStyleColor(); + ImGui::EndTooltip(); + } + + TextEditor *TextEditor::getSourceCodeEditor() { if (m_sourceCodeEditor != nullptr) return m_sourceCodeEditor; return this; } bool TextEditor::isEmpty() const { - if (m_lines.empty()) - return true; - if (m_lines.size() == 1) { - if (m_lines[0].empty()) - return true; - if (m_lines[0].size() == 1 && m_lines[0].front() == '\n') - return true; + auto size = m_lines.size(); + for (u32 i = 0; i < size; ++i) { + if (!m_lines[i].empty()) + return false; } - return false; + return true; } - void TextEditor::setSelection(const Selection &selection) { + bool TextEditor::EditorState::operator==(const EditorState &o) const { + return m_selection == o.m_selection && m_cursorPosition == o.m_cursorPosition; + } + + void TextEditor::setSelection(const Range &selection) { m_state.m_selection = setCoordinates(selection); } - TextEditor::Selection TextEditor::getSelection() const { + Range TextEditor::getSelection() const { return m_state.m_selection; } void TextEditor::selectWordUnderCursor() { auto wordStart = findWordStart(getCursorPosition()); - setSelection(Selection(wordStart, findWordEnd(wordStart))); + setSelection(Range(wordStart, findWordEnd(wordStart))); } void TextEditor::selectAll() { - setSelection(Selection(setCoordinates(0, 0), setCoordinates(-1, -1))); + setSelection(Range(Coordinates(this, 0, 0), Coordinates(this, -1, -1))); } bool TextEditor::hasSelection() const { @@ -424,15 +547,16 @@ namespace hex::ui { } TextEditor::PaletteIndex TextEditor::getColorIndexFromFlags(Line::Flags flags) { - if (flags.m_bits.globalDocComment) + auto commentBits = flags.m_value & inComment; + if (commentBits == (i32) Line::Comments::Global) return PaletteIndex::GlobalDocComment; - if (flags.m_bits.blockDocComment) + if (commentBits == (i32) Line::Comments::BlockDoc) return PaletteIndex::DocBlockComment; - if (flags.m_bits.docComment) + if (commentBits == (i32) Line::Comments::Doc) return PaletteIndex::DocComment; - if (flags.m_bits.blockComment) + if (commentBits == (i32) Line::Comments::Block) return PaletteIndex::BlockComment; - if (flags.m_bits.comment) + if (commentBits == (i32) Line::Comments::Line) return PaletteIndex::Comment; if (flags.m_bits.deactivated) return PaletteIndex::PreprocessorDeactivated; @@ -498,7 +622,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, getLineMaxCharColumn(line)); + m_state.m_selection.m_end = setCoordinates(line, lineMaxColumn(line)); } m_lastClick = -1.0f; @@ -565,7 +689,7 @@ namespace hex::ui { } // the index here is array index so zero based - void TextEditor::FindReplaceHandler::selectFound(TextEditor *editor, i32 found) { + void FindReplaceHandler::selectFound(TextEditor *editor, i32 found) { if (found < 0 || found >= (i64) m_matches.size()) return; editor->setSelection(m_matches[found].m_selection); @@ -574,7 +698,7 @@ namespace hex::ui { // The returned index is shown in the form // 'index of count' so 1 based - u32 TextEditor::FindReplaceHandler::findMatch(TextEditor *editor, i32 index) { + u32 FindReplaceHandler::findMatch(TextEditor *editor, i32 index) { if (editor->m_textChanged || m_optionsChanged) { std::string findWord = getFindWord(); @@ -632,7 +756,7 @@ namespace hex::ui { } // returns 1 based index - u32 TextEditor::FindReplaceHandler::findPosition(TextEditor *editor, Coordinates pos, bool isNext) { + u32 FindReplaceHandler::findPosition(TextEditor *editor, Coordinates pos, bool isNext) { if (editor->m_textChanged || m_optionsChanged) { std::string findWord = getFindWord(); if (findWord.empty()) @@ -646,14 +770,14 @@ namespace hex::ui { return 0; if (isNext) { for (i32 i = 0; i < count; i++) { - auto interval = Selection(m_matches[i==0 ? count-1 : i - 1].m_selection.m_end,m_matches[i].m_selection.m_end); + auto interval = Range(m_matches[i == 0 ? count - 1 : i - 1].m_selection.m_end,m_matches[i].m_selection.m_end); if (interval.contains(pos)) return i + 1; } } else { for (i32 i = 0; i < count; i++) { - auto interval = Selection(m_matches[i == 0 ? count - 1 : i - 1].m_selection.m_start, m_matches[i].m_selection.m_start); - if (interval.contains(pos, 2)) + auto interval = Range(m_matches[i == 0 ? count - 1 : i - 1].m_selection.m_start,m_matches[i].m_selection.m_start); + if (interval.contains(pos, Range::EndsInclusive::Start)) return i == 0 ? count : i; } } @@ -681,10 +805,10 @@ namespace hex::ui { } // Performs actual search to fill mMatches - bool TextEditor::FindReplaceHandler::findNext(TextEditor *editor) { + bool FindReplaceHandler::findNext(TextEditor *editor) { Coordinates curPos = m_matches.empty() ? editor->m_state.m_cursorPosition : editor->lineCoordsToIndexCoords(m_matches.back().m_cursorPosition); - u64 matchLength = getStringCharacterCount(m_findWord); + u64 matchLength = stringCharacterCount(m_findWord); u64 matchBytes = m_findWord.size(); u64 byteIndex = 0; @@ -757,13 +881,13 @@ namespace hex::ui { if (textLoc == std::string::npos) return false; TextEditor::EditorState state; - state.m_selection = Selection(TextEditor::stringIndexToCoordinates(textLoc, textSrc), TextEditor::stringIndexToCoordinates(textLoc + matchBytes, textSrc)); + state.m_selection = Range(TextEditor::stringIndexToCoordinates(textLoc, textSrc), TextEditor::stringIndexToCoordinates(textLoc + matchBytes, textSrc)); state.m_cursorPosition = state.m_selection.m_end; m_matches.push_back(state); return true; } - void TextEditor::FindReplaceHandler::findAllMatches(TextEditor *editor, std::string findWord) { + void FindReplaceHandler::findAllMatches(TextEditor *editor, std::string findWord) { if (findWord.empty()) { editor->ensureCursorVisible(); @@ -809,7 +933,7 @@ namespace hex::ui { } - bool TextEditor::FindReplaceHandler::replace(TextEditor *editor, bool right) { + bool FindReplaceHandler::replace(TextEditor *editor, bool right) { if (m_matches.empty() || m_findWord == m_replaceWord || m_findWord.empty()) return false; @@ -819,7 +943,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->getLineMaxCharColumn(editor->m_state.m_cursorPosition.m_line); + editor->m_state.m_cursorPosition.m_column = editor->lineMaxColumn(editor->m_state.m_cursorPosition.m_line); } else editor->m_state.m_cursorPosition.m_column--; } @@ -829,7 +953,7 @@ namespace hex::ui { UndoRecord u; u.m_before = editor->m_state; u.m_removed = editor->getSelectedText(); - u.m_removedSelection = editor->m_state.m_selection; + u.m_removedRange = editor->m_state.m_selection; editor->deleteSelection(); if (getFindRegEx()) { std::string replacedText = std::regex_replace(editor->getText(), std::regex(m_findWord), m_replaceWord, std::regex_constants::format_first_only | std::regex_constants::format_no_copy); @@ -837,12 +961,12 @@ namespace hex::ui { } else u.m_added = m_replaceWord; - u.m_addedSelection.m_start = editor->setCoordinates(editor->m_state.m_cursorPosition); + u.m_addedRange.m_start = editor->setCoordinates(editor->m_state.m_cursorPosition); editor->insertText(u.m_added); editor->setCursorPosition(editor->m_state.m_selection.m_end); - u.m_addedSelection.m_end = editor->setCoordinates(editor->m_state.m_cursorPosition); + u.m_addedRange.m_end = editor->setCoordinates(editor->m_state.m_cursorPosition); editor->ensureCursorVisible(); ImGui::SetKeyboardFocusHere(0); @@ -857,7 +981,7 @@ namespace hex::ui { return false; } - bool TextEditor::FindReplaceHandler::replaceAll(TextEditor *editor) { + bool FindReplaceHandler::replaceAll(TextEditor *editor) { u32 count = m_matches.size(); for (u32 i = 0; i < count; i++) diff --git a/plugins/ui/source/ui/text_editor/utf8.cpp b/plugins/ui/source/ui/text_editor/utf8.cpp index dfc9ae54e..5619c36a0 100644 --- a/plugins/ui/source/ui/text_editor/utf8.cpp +++ b/plugins/ui/source/ui/text_editor/utf8.cpp @@ -1,10 +1,29 @@ #include #include +#include +#include #include namespace hex::ui { - i32 TextEditor::Line::getColumnIndex(i32 column) const { + TextEditor::Line TextEditor::Line::trim(TrimMode trimMode) { + if (m_chars.empty()) + return m_emptyLine; + std::string trimmed = wolv::util::trim(m_chars); + auto idx = m_chars.find(trimmed); + if (idx == std::string::npos) + return m_emptyLine; + if (trimMode == TrimMode::TrimNone) + return *this; + else if (trimMode == TrimMode::TrimEnd) + return subLine(0, idx + trimmed.size()); + else if (trimMode == TrimMode::TrimStart) + return subLine(idx, size() - idx); + else + return subLine(idx, trimmed.size()); + } + + i32 TextEditor::Line::columnIndex(i32 column) const { i32 idx = 0; for (i32 col = 0; idx < (i32) size() && col < column; ++col) @@ -13,7 +32,20 @@ namespace hex::ui { return idx; } - i32 TextEditor::Line::getCharColumn(i32 stringIndex) const { + i32 TextEditor::Line::maxColumn() { + if (m_lineMaxColumn > 0) + return m_lineMaxColumn; + m_lineMaxColumn = indexColumn((i32) size()); + return m_lineMaxColumn; + } + + i32 TextEditor::Line::maxColumn() const { + if (m_lineMaxColumn > 0) + return m_lineMaxColumn; + return indexColumn((i32) size()); + } + + i32 TextEditor::Line::indexColumn(i32 stringIndex) const { i32 limit = std::max(0, std::min(stringIndex, (i32) size())); i32 col = 0; @@ -23,13 +55,44 @@ namespace hex::ui { return col; } - i32 TextEditor::Line::getStringTextSize(const std::string &str) const { + i32 TextEditor::Line::stringTextSize(const std::string &str) const { + i32 result = 0; + if (str.empty()) + return 0; + if (ImGui::GetFont() == nullptr) { + fonts::CodeEditor().push(); + result = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, str.c_str(), nullptr, nullptr).x; + fonts::CodeEditor().pop(); + return result; + } 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; + i32 TextEditor::Line::textSize(u32 index) const { + if (m_chars.empty()) + return 0; + return stringTextSize(m_chars.substr(0, index)); + } + + i32 TextEditor::Line::textSize() const { + if (m_chars.empty()) + return 0; + return stringTextSize(m_chars); + } + + i32 TextEditor::Line::lineTextSize(TrimMode trimMode) { + auto trimmedLine = trim(trimMode); + return trimmedLine.textSize(); + } + + i32 TextEditor::Line::textSizeIndex(float textSize, i32 position) { + i32 result = textSize / ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x; + auto currentSize = stringTextSize(m_chars.substr(position, result)); + while (currentSize < textSize && (u32)(position + result) < size()) { + result += TextEditor::utf8CharLength(m_chars[position + result]); + currentSize = stringTextSize(m_chars.substr(position, result)); + } + return result; } // https://en.wikipedia.org/wiki/UTF-8 @@ -48,7 +111,7 @@ namespace hex::ui { return 1; } - i32 TextEditor::getStringCharacterCount(const std::string &str) { + i32 TextEditor::stringCharacterCount(const std::string &str) { if (str.empty()) return 0; i32 count = 0; @@ -57,18 +120,18 @@ namespace hex::ui { return count; } - i32 TextEditor::getLineCharColumn(i32 lineIndex, i32 stringIndex) { + i32 TextEditor::lineIndexColumn(i32 lineIndex, i32 stringIndex) { if (lineIndex >= (i64) m_lines.size() || lineIndex < 0) return 0; Line &line = m_lines[lineIndex]; - return line.getCharColumn(stringIndex); + return line.indexColumn(stringIndex); } - i32 TextEditor::getLineMaxCharColumn(i32 lineIndex) { + i32 TextEditor::lineMaxColumn(i32 lineIndex) { if (lineIndex >= (i64) m_lines.size() || lineIndex < 0) return 0; - Line &line = m_lines[lineIndex]; - return line.getCharColumn(line.size()); + + return m_lines[lineIndex].maxColumn(); } // "Borrowed" from ImGui source @@ -142,7 +205,7 @@ namespace hex::ui { return Invalid; const auto &line = m_lines[coordinates.m_line]; - return Coordinates(coordinates.m_line,line.getColumnIndex(coordinates.m_column)); + return Coordinates(coordinates.m_line,line.columnIndex(coordinates.m_column)); } i32 TextEditor::lineCoordinatesToIndex(const Coordinates &coordinates) const { @@ -150,14 +213,14 @@ namespace hex::ui { return -1; const auto &line = m_lines[coordinates.m_line]; - return line.getColumnIndex(coordinates.m_column); + return line.columnIndex(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.getCharColumn(strIndex)); + return setCoordinates(lineIndex, line.indexColumn(strIndex)); } u64 TextEditor::getLineByteCount(i32 lineIndex) const { @@ -175,7 +238,7 @@ namespace hex::ui { auto line = std::count(str.begin(), str.end(), '\n'); auto index = str.find_last_of('\n'); str = str.substr(index + 1); - auto col = TextEditor::getStringCharacterCount(str); + auto col = TextEditor::stringCharacterCount(str); return TextEditor::Coordinates(line, col); }