diff --git a/dist/ImHex.run.xml b/dist/ImHex.run.xml index 2eca23ed4..275097e51 100644 --- a/dist/ImHex.run.xml +++ b/dist/ImHex.run.xml @@ -1,5 +1,5 @@ - + diff --git a/plugins/builtin/include/content/text_highlighting/pattern_language.hpp b/plugins/builtin/include/content/text_highlighting/pattern_language.hpp index b764170f3..9745e01bd 100644 --- a/plugins/builtin/include/content/text_highlighting/pattern_language.hpp +++ b/plugins/builtin/include/content/text_highlighting/pattern_language.hpp @@ -1,26 +1,79 @@ #pragma once #include #include -#include #include #include #include -#include - namespace hex::plugin::builtin { class ViewPatternEditor; class TextHighlighter { public: - class Interval; + /// Intervals are the sets finite contiguous non-negative integer that + /// are described by their endpoints. The sets must have the following + /// properties: + /// 1. Any two elements of the set can either have an empty intersection or + /// 2. their intersection is equal to one of the two sets (i.e. one is + /// a subset of the other). + /// An interval is defined to be smaller than another if: + /// 1. The interval lies entirely to the left of the other interval or + /// 2. The interval is a proper subset of the other interval. + /// Two intervals are equal if they have identical start and end values. + /// This ordering is used for things like code blocks or the token + /// ranges that are defined by the blocks. + + class TokenInterval { + public: + friend class TextHighlighter; + TokenInterval() : m_start(0), m_end(0) {} + TokenInterval(i32 start, i32 end) : m_start(start), m_end(end) { + if (m_start > m_end) + std::swap(m_start,m_end); + } + bool operator<(const TokenInterval &other) const { + return other.m_end > m_end; + } + bool operator>(const TokenInterval &other) const { + return m_end > other.m_end; + } + bool operator==(const TokenInterval &other) const { + return m_start == other.m_start && m_end == other.m_end; + } + bool operator!=(const TokenInterval &other) const { + return m_start != other.m_start || m_end != other.m_end; + } + bool operator<=(const TokenInterval &other) const { + return other.m_end >= m_end; + } + bool operator>=(const TokenInterval &other) const { + return m_end >= other.m_end; + } + bool contains(const TokenInterval &other) const { + return other.m_start >= m_start && other.m_end <= m_end; + } + bool contains(i32 value) const { + return value >= m_start && value <= m_end; + } + bool contiguous(const TokenInterval &other) const { + auto highEndDiff = m_start - other.m_end; + auto lowEndDiff = other.m_start - m_end; + return highEndDiff == 0 || highEndDiff == 1 || lowEndDiff == 0 || lowEndDiff == 1; + } + private: + i32 m_start; + i32 m_end; + }; + + using Coordinates = ui::TextEditor::Coordinates; + using TokenInterval = TextHighlighter::TokenInterval; using Token = pl::core::Token; using ASTNode = pl::core::ast::ASTNode; using CompileError = pl::core::err::CompileError; using Identifier = Token::Identifier; using IdentifierType = Identifier::IdentifierType; - using UnorderedBlocks = std::map; - using OrderedBlocks = std::map; - using Scopes = std::set; + using UnorderedBlocks = std::map; + using OrderedBlocks = std::map; + using Scopes = std::set; using Location = pl::core::Location; using VectorString = std::vector; using TokenIter = pl::hlp::SafeIterator::const_iterator>; @@ -36,6 +89,7 @@ namespace hex::plugin::builtin { using TokenSequence = std::vector; using TokenIdVector = std::vector; using Instances = std::map>; + using CodeFoldBlocks = ui::TextEditor::CodeFoldBlocks; struct ParentDefinition; struct Definition { @@ -83,6 +137,7 @@ namespace hex::plugin::builtin { using Variables = std::map>; /// to define UDT and function variables using VariableMap = std::map; + inline static const Coordinates Invalid = Coordinates(0x80000000, 0x80000000); private: VectorString m_lines; @@ -123,43 +178,20 @@ namespace hex::plugin::builtin { VariableScopes m_UDTBlocks; VariableScopes m_functionBlocks; Scopes m_globalBlocks; + CodeFoldBlocks m_foldEndPoints; Inheritances m_inheritances; const static IdentifierTypeColor m_identifierTypeColor; const static TokenTypeColor m_tokenTypeColor; public: - - /// Intervals are the sets finite contiguous non-negative integer that - /// are described by their endpoints. The sets must have the following - /// properties: - /// 1. Any two elements of the set can either have an empty intersection or - /// 2. their intersection is equal to one of the two sets (i.e. one is - /// a subset of the other). - /// An interval is defined to be smaller than another if: - /// 1. The interval lies entirely to the left of the other interval or - /// 2. The interval is a proper subset of the other interval. - /// Two intervals are equal if they have identical start and end values. - /// This ordering is used for things like code blocks or the token - /// ranges that are defined by the blocks. - class Interval { - public: - i32 start; - i32 end; - Interval() : start(0), end(0) {} - Interval(i32 start, i32 end); - bool operator<(const Interval &other) const; - bool operator>(const Interval &other) const; - bool operator==(const Interval &other) const; - bool operator!=(const Interval &other) const; - bool operator<=(const Interval &other) const; - bool operator>=(const Interval &other) const; - bool contains(const Interval &other) const; - bool contains(i32 value) const ; - bool contiguous(const Interval &other) const; - }; constexpr static u32 Normal = 0; constexpr static u32 Not = 1; + //std::atomic m_needsToUpdateColors = true; + //std::atomic m_wasInterrupted = false; + //std::atomic m_interrupt = false; + //std::atomic m_completed = false; + pl::PatternLanguage *getPatternLanguage(); void updateRequiredInputs(); RequiredInputs& getRequiredInputs(); @@ -199,9 +231,11 @@ namespace hex::plugin::builtin { * @brief Renders compile errors in real time */ void renderErrors(); + /// A token range is the set of token indices of a definition. The namespace token /// ranges are obtained first because they are needed to obtain unique identifiers. void getTokenRanges(IdentifierType identifierTypeToSearch); + std::vector searchRangeForBlocks(TokenInterval interval); /// The global scope is the complement of the union of all the function and UDT token ranges void getGlobalTokenRanges(); /// If the current token is a function or UDT, creates a map entry from the name to the token range. These are ordered alphabetically by name. @@ -253,7 +287,7 @@ namespace hex::plugin::builtin { /// If context is empty search for the variable, if it isnt use the variable map. bool findOrContains(std::string &context, UnorderedBlocks tokenRange, VariableMap variableMap); /// Search for instances inside some block - void setBlockInstancesColor(const std::string &name, const Definition &definition, const Interval &block); + void setBlockInstancesColor(const std::string &name, const Definition &definition, const TokenInterval &block); /// Convenience functions. void skipAttribute(); void skipArray(i32 maxSkipCount, bool forward = true); diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index de0f16aee..1217b3902 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -769,7 +769,6 @@ "hex.builtin.view.bookmarks.tooltip.open_in_view": "Open in new View", "hex.builtin.view.bookmarks.tooltip.unlock": "Unlock", "hex.builtin.view.command_palette.name": "Command Palette", - "hex.builtin.view.find.constants": "Constants", "hex.builtin.view.data_inspector.menu.copy": "Copy Value", "hex.builtin.view.data_inspector.menu.edit": "Edit Value", "hex.builtin.view.data_inspector.execution_error": "Custom row evaluation error", @@ -1125,6 +1124,12 @@ "hex.builtin.view.pattern_editor.shortcut.move_bottom": "Move Cursor to the End of the File", "hex.builtin.view.pattern_editor.shortcut.delete_word_left": "Delete One Word to the Left of the Cursor", "hex.builtin.view.pattern_editor.shortcut.delete_word_right": "Delete One Word to the Right of the Cursor", + "hex.builtin.view.pattern_editor.shortcut.code_fold_expand": "Expand Code Fold at Cursor", + "hex.builtin.view.pattern_editor.shortcut.code_fold_expand_recursively": "Recursively Expand Code Fold at Cursor", + "hex.builtin.view.pattern_editor.shortcut.code_fold_expand_all": "Expand All Code Folds", + "hex.builtin.view.pattern_editor.shortcut.code_fold_collapse": "Collapse Code Fold at Cursor", + "hex.builtin.view.pattern_editor.shortcut.code_fold_collapse_recursively": "Recursively Collapse Code Fold at Cursor", + "hex.builtin.view.pattern_editor.shortcut.code_fold_collapse_all": "Collapse All Code Folds", "hex.builtin.view.pattern_editor.menu.edit.run_pattern": "Run Pattern", "hex.builtin.view.pattern_editor.menu.edit.step_debugger": "Step Debugger", "hex.builtin.view.pattern_editor.menu.edit.continue_debugger": "Continue Debugger", diff --git a/plugins/builtin/source/content/text_highlighting/pattern_language.cpp b/plugins/builtin/source/content/text_highlighting/pattern_language.cpp index 8076a572e..24b622933 100644 --- a/plugins/builtin/source/content/text_highlighting/pattern_language.cpp +++ b/plugins/builtin/source/content/text_highlighting/pattern_language.cpp @@ -11,61 +11,21 @@ #include - namespace hex::plugin::builtin { using namespace pl::core; - using Identifier = Token::Identifier; - using Keyword = Token::Keyword; - using Separator = Token::Separator; - using Operator = Token::Operator; - using Comment = Token::Comment; - using DocComment = Token::DocComment; - using Literal = Token::Literal; - using ValueType = Token::ValueType; - using RequiredInputs = TextHighlighter::RequiredInputs; - using Interval = TextHighlighter::Interval; - - Interval::Interval(i32 start, i32 end) : start(start), end(end) { - if (start > end) - throw std::invalid_argument("Interval start must be less than or equal to end"); - } - - bool Interval::operator<(const Interval &other) const { - return other.end > end; - } - - bool Interval::operator>(const Interval &other) const { - return end > other.end; - } - - bool Interval::operator==(const Interval &other) const { - return start == other.start && end == other.end; - } - - bool Interval::operator!=(const Interval &other) const { - return start != other.start || end != other.end; - } - - bool Interval::operator<=(const Interval &other) const { - return other.end >= end; - } - - bool Interval::operator>=(const Interval &other) const { - return end >= other.end; - } - - bool Interval::contains(const Interval &other) const { - return other.start >= start && other.end <= end; - } - - bool Interval::contains(i32 value) const { - return value >= start && value <= end; - } - - bool Interval::contiguous(const Interval &other) const { - return ((start - other.end) == 1 || (other.start - end) == 1); - } + using Identifier = Token::Identifier; + using Keyword = Token::Keyword; + using Directive = Token::Directive; + using Separator = Token::Separator; + using Operator = Token::Operator; + using Comment = Token::Comment; + using DocComment = Token::DocComment; + using Literal = Token::Literal; + using ValueType = Token::ValueType; + using TokenInterval = TextHighlighter::TokenInterval; + using CodeFoldBlocks = TextHighlighter::CodeFoldBlocks; + using Coordinates = TextHighlighter::Coordinates; void TextHighlighter::next(i32 count) { if (m_viewPatternEditor->interrupted()) { @@ -84,7 +44,7 @@ namespace hex::plugin::builtin { } pl::PatternLanguage *TextHighlighter::getPatternLanguage() { - return m_viewPatternEditor->getPatternLanguage()->get(); + return m_viewPatternEditor->getPatternLanguage()->get(); } void TextHighlighter::RequiredInputs::setTypes() { @@ -147,7 +107,7 @@ namespace hex::plugin::builtin { m_requiredInputs.setRequiredInputs(); } - RequiredInputs& TextHighlighter::getRequiredInputs() { + TextHighlighter::RequiredInputs& TextHighlighter::getRequiredInputs() { return m_requiredInputs; } @@ -405,12 +365,46 @@ namespace hex::plugin::builtin { return true; } + std::vector TextHighlighter::searchRangeForBlocks(TokenInterval interval) { + m_curr = m_startToken + interval.m_start; + std::vector result; + + u32 nestedLevel = 0; + std::vector tokenStack; + while (m_curr != m_startToken + interval.m_end) { + + if (sequence(tkn::Separator::LeftBrace)) { + auto tokenId = getTokenId(m_curr[-1].location); + tokenStack.push_back(tokenId); + nestedLevel++; + } else if (sequence(tkn::Separator::RightBrace)) { + nestedLevel--; + + if (tokenStack.empty()) + return result; + TokenInterval range(tokenStack.back(), getTokenId(m_curr[-1].location)); + tokenStack.pop_back(); + + result.push_back(range); + + if (nestedLevel == 0) { + skipAttribute(); + break; + } + + } else if (peek(tkn::Separator::EndOfProgram)) + return result; + else + next(); + } + return result; + } + // Finds the token range of a function, namespace or UDT bool TextHighlighter::getTokenRange(std::vector keywords, UnorderedBlocks &tokenRange, OrderedBlocks &tokenRangeInv, bool fullName, VariableScopes *blocks) { bool addArgumentBlock = !fullName; - std::vector tokenStack; if (getTokenId(m_curr->location) < 1) return false; std::string name; @@ -454,53 +448,27 @@ namespace hex::plugin::builtin { result = result && !peek(keyword); if (result) return false; - u32 nestedLevel = 0; - next(); - auto endToken = TokenIter(m_requiredInputs.fullTokens.begin() + tokenCount, m_requiredInputs.fullTokens.end()); - while (endToken > m_curr) { + std::vector blocksFound = searchRangeForBlocks(TokenInterval(index1, tokenCount-1)); + if (blocks != nullptr) + for (auto range: blocksFound) + blocks->operator[](name).insert(range); + //if (peek(tkn::Separator::EndOfProgram),-1) + // return false; - if (sequence(tkn::Separator::LeftBrace)) { - auto tokenId = getTokenId(m_curr[-1].location); - tokenStack.push_back(tokenId); - nestedLevel++; - } else if (sequence(tkn::Separator::RightBrace)) { - nestedLevel--; - - if (tokenStack.empty()) - return false; - Interval range(tokenStack.back(), getTokenId(m_curr[-1].location)); - tokenStack.pop_back(); - - if (nestedLevel == 0) { - range.end -= 1; - if (blocks != nullptr) - blocks->operator[](name).insert(range); - skipAttribute(); - break; - } - if (blocks != nullptr) - blocks->operator[](name).insert(range); - } else if (sequence(tkn::Separator::EndOfProgram)) - return false; - else - next(); - } - if (m_curr > endToken || endToken == m_curr) - return false; i32 index2 = getTokenId(m_curr->location); if (index2 > index1 && index2 < tokenCount) { if (fullName) { - tokenRangeInv[Interval(index1, index2)] = name; + tokenRangeInv[TokenInterval(index1, index2)] = name; } else { - tokenRange[name] = Interval(index1, index2); + tokenRange[name] = TokenInterval(index1, index2); } if (blocks != nullptr) { if (addArgumentBlock) { - auto tokenIndex = blocks->operator[](name).begin()->start; - blocks->operator[](name).insert(Interval(index1, tokenIndex)); + auto tokenIndex = blocks->operator[](name).begin()->m_start; + blocks->operator[](name).insert(TokenInterval(index1, tokenIndex)); } - blocks->operator[](name).insert(Interval(index1, index2)); + blocks->operator[](name).insert(TokenInterval(index1, index2)); } return true; } @@ -703,7 +671,7 @@ namespace hex::plugin::builtin { variableParentType = currentName; if (!nameSpace.empty() && !variableParentType.contains(nameSpace)) - variableParentType.insert(0, nameSpace); + variableParentType = nameSpace + variableParentType; else if (findNamespace(nameSpace) && !variableParentType.contains(nameSpace)) variableParentType = fmt::format("{}::{}", nameSpace, variableParentType); @@ -735,7 +703,7 @@ namespace hex::plugin::builtin { return variableMap.contains(context); } - void TextHighlighter::setBlockInstancesColor(const std::string &name, const Definition &definition, const Interval &block) { + void TextHighlighter::setBlockInstancesColor(const std::string &name, const Definition &definition, const TokenInterval &block) { if (definition.idType == IdentifierType::Unknown) return; @@ -762,7 +730,7 @@ namespace hex::plugin::builtin { std::vector identifiers; getFullName(identifierName, identifiers); } - Interval tokenRange; + TokenInterval tokenRange; Scopes blocks; Scopes::iterator blocksIterBegin, blocksIterEnd; @@ -780,9 +748,10 @@ namespace hex::plugin::builtin { blocksIterBegin = m_functionBlocks[name].begin(); blocksIterEnd = m_functionBlocks[name].end(); --blocksIterEnd; + } else if (m_globalVariables.contains(identifierName)) { definitions = m_globalVariables[identifierName]; - tokenRange = Interval(0, m_requiredInputs.fullTokens.size()); + tokenRange = TokenInterval(0, m_requiredInputs.fullTokens.size()); blocks.insert(tokenRange); blocksIterBegin = blocks.begin(); blocksIterEnd = blocks.end(); @@ -795,7 +764,7 @@ namespace hex::plugin::builtin { if (isFunction) { for (auto block = blocksIterBegin; block != blocksIterEnd; block++) { - if (tokenId > block->start && tokenId < block->end) { + if (tokenId > block->m_start && tokenId < block->m_end) { blocksIterBegin = block; break; } @@ -803,7 +772,7 @@ namespace hex::plugin::builtin { for (const auto &definition : definitions) { for (auto block = blocksIterBegin; block != blocksIterEnd; block++) { - if (definition.tokenIndex > block->start && definition.tokenIndex < block->end) { + if (definition.tokenIndex > block->m_start && definition.tokenIndex < block->m_end) { result = definition; m_curr = curr; @@ -814,7 +783,7 @@ namespace hex::plugin::builtin { } } auto it = std::ranges::find_if(definitions, [&](const Definition &definition) { - return definition.tokenIndex > tokenRange.start && definition.tokenIndex < tokenRange.end; + return definition.tokenIndex > tokenRange.m_start && definition.tokenIndex < tokenRange.m_end; }); if (it != definitions.end()) { @@ -828,7 +797,7 @@ namespace hex::plugin::builtin { } else { for (auto block = blocksIterBegin; block != blocksIterEnd; block++) { - if (tokenId > block->start && tokenId < block->end) { + if (tokenId > block->m_start && tokenId < block->m_end) { blocksIterBegin = block; break; } @@ -836,7 +805,7 @@ namespace hex::plugin::builtin { for (auto block = blocksIterBegin; block != blocksIterEnd; block++) { for (const auto &definition: definitions) { - if (definition.tokenIndex > block->start && definition.tokenIndex < block->end) { + if (definition.tokenIndex > block->m_start && definition.tokenIndex < block->m_end) { result = definition; m_curr = curr; @@ -921,7 +890,7 @@ namespace hex::plugin::builtin { if (auto *identifier = std::get_if(&m_requiredInputs.fullTokens.at(instance).value); identifier != nullptr && identifier->getType() == IdentifierType::TemplateArgument) { auto tokenRange = m_UDTTokenRange[result.typeStr]; auto tokenIndex = m_firstTokenIdOfLine.at(getLocation(parentDefinition.tokenIndex).line - 1); - i32 argNumber = getArgumentNumber(tokenRange.start, instance); + i32 argNumber = getArgumentNumber(tokenRange.m_start, instance); getTokenIdForArgument(tokenIndex, argNumber, tkn::Operator::BoolLessThan); if (auto *identifier2 = std::get_if(&m_curr->value); identifier2 != nullptr) { templateName = identifier2->get(); @@ -1118,7 +1087,7 @@ namespace hex::plugin::builtin { for (auto [interval, name]: m_namespaceTokenRange) { i32 tokenId = optionalTokenId == -1 ? getTokenId(m_curr->location) : optionalTokenId; - if (tokenId > interval.start && tokenId < interval.end) { + if (tokenId > interval.m_start && tokenId < interval.m_end) { if (nameSpace.empty()) nameSpace = name; @@ -1319,8 +1288,8 @@ namespace hex::plugin::builtin { } else { auto curr = m_curr; for (const auto &[name, range] : m_UDTTokenRange) { - auto startToken = TokenIter(m_requiredInputs.fullTokens.begin()+range.start,m_requiredInputs.fullTokens.begin()+range.end); - auto endToken = TokenIter(m_requiredInputs.fullTokens.begin()+range.end,m_requiredInputs.fullTokens.end()); + auto startToken = TokenIter(m_requiredInputs.fullTokens.begin()+range.m_start,m_requiredInputs.fullTokens.begin()+range.m_end); + auto endToken = TokenIter(m_requiredInputs.fullTokens.begin()+range.m_end,m_requiredInputs.fullTokens.end()); for ( m_curr = startToken; endToken > m_curr; next()) { if (auto *identifier = std::get_if(&m_curr->value); identifier != nullptr) { @@ -1645,23 +1614,18 @@ namespace hex::plugin::builtin { return -1; auto line1 = location.line - 1; auto tokenCount = m_requiredInputs.fullTokens.size(); - i32 tokenStart = m_firstTokenIdOfLine.at(line1); - i32 tokenEnd = -1; - if (line1 == m_firstTokenIdOfLine.size() -1) { - tokenEnd = tokenCount - 1; - } else { - auto line2 = nextLine(line1); - tokenEnd = m_firstTokenIdOfLine.at(line2) - 1; - } - - if (tokenEnd >= (i32) tokenCount) - tokenEnd = tokenCount - 1; + auto lineCount = m_firstTokenIdOfLine.size(); + if (line1 >= lineCount || tokenCount == 0) + return -1; + auto line2 = nextLine(line1); + i32 tokenStart = m_firstTokenIdOfLine[line1]; + i32 tokenEnd = tokenCount - 1; + if (line2 < lineCount) + tokenEnd = m_firstTokenIdOfLine[line2] - 1; if (tokenStart == -1 || tokenEnd == -1 || tokenStart >= (i32) tokenCount) return -1; - for (i32 i = tokenStart; i <= tokenEnd; i++) { - if (m_requiredInputs.fullTokens.at(i).location.column >= location.column) return i; } @@ -1681,7 +1645,6 @@ namespace hex::plugin::builtin { if (token->type == Token::Type::Identifier && (!m_tokenColors.contains(tokenId) || m_tokenColors.at(tokenId) == ui::TextEditor::PaletteIndex::Default || m_tokenColors.at(tokenId) == ui::TextEditor::PaletteIndex::UnkIdentifier)) m_tokenColors[tokenId] = m_identifierTypeColor.at(type); - } void TextHighlighter::setColor(i32 tokenId, const IdentifierType &type) { @@ -1722,6 +1685,8 @@ namespace hex::plugin::builtin { } } } while (sequence(tkn::Literal::Identifier,tkn::Separator::Dot)); + if (peek(tkn::Separator::EndOfProgram)) + return; next(); if (sequence(tkn::Keyword::As, tkn::Literal::Identifier)) { next(-1); @@ -1733,6 +1698,8 @@ namespace hex::plugin::builtin { } } } + if (peek(tkn::Separator::EndOfProgram)) + return; if (peek(tkn::Literal::Identifier)) { auto identifier = getValue(0); Token::Identifier::IdentifierType identifierType; @@ -1869,8 +1836,8 @@ namespace hex::plugin::builtin { definitions = m_ImportedUDTVariables[inheritance]; for (const auto &[variableName, variableDefinitions]: definitions) { auto tokenRange = m_UDTTokenRange[name]; - u32 tokenIndex = tokenRange.start; - for (auto token = tokenRange.start; token < tokenRange.end; token++) { + u32 tokenIndex = tokenRange.m_start; + for (auto token = tokenRange.m_start; token < tokenRange.m_end; token++) { if (auto operatorTkn = std::get_if(&m_requiredInputs.fullTokens.at(token).value); operatorTkn != nullptr && *operatorTkn == Token::Operator::Colon) @@ -1941,8 +1908,17 @@ namespace hex::plugin::builtin { if ( col < 0 || col > (i32) m_lines[line].size()) return false; + if (length < 0) + return false; + if (length > (i32) m_lines[line].size()-col) + length -= (i32)( m_lines[line].size()-col); + while (m_firstTokenIdOfLine[line] == m_firstTokenIdOfLine[line + 1]) { + length -= (i32) m_lines[line].size(); + line++; + } - if (length < 0 || length > (i32) m_lines[line].size()-col) + + if (length > (i32) m_lines[line].size()-col && m_firstTokenIdOfLine[line + 1] != -1) return false; return true; } @@ -2010,8 +1986,8 @@ namespace hex::plugin::builtin { m_startToken = m_originalPosition = m_partOriginalPosition = TokenIter(m_requiredInputs.fullTokens.begin(), m_requiredInputs.fullTokens.end()); for (auto range: tokenRangeSet) { - auto startToken = TokenIter(m_requiredInputs.fullTokens.begin()+range.start,m_requiredInputs.fullTokens.begin()+range.end); - auto endToken = TokenIter(m_requiredInputs.fullTokens.begin()+range.end,m_requiredInputs.fullTokens.end()); + auto startToken = TokenIter(m_requiredInputs.fullTokens.begin()+range.m_start,m_requiredInputs.fullTokens.begin()+range.m_end); + auto endToken = TokenIter(m_requiredInputs.fullTokens.begin()+range.m_end,m_requiredInputs.fullTokens.end()); for ( m_curr = startToken; endToken > m_curr; next()) { @@ -2042,11 +2018,12 @@ namespace hex::plugin::builtin { for (const auto &[name, range]: tokenRangeMap) { m_curr = m_startToken; auto endToken = m_startToken; - next(range.start); + next(range.m_start); if (isArgument) { while (!peek(delimiter1)) { - + if (peek(tkn::Separator::EndOfProgram)) + return; if (peek(tkn::Separator::LeftBrace)) break; next(); @@ -2056,7 +2033,8 @@ namespace hex::plugin::builtin { continue; endToken = m_curr; while (!peek(delimiter2)) { - + if (peek(tkn::Separator::EndOfProgram)) + return; if (peek(tkn::Separator::LeftBrace)) break; next(); @@ -2068,11 +2046,12 @@ namespace hex::plugin::builtin { m_curr = endToken; endToken = temp; } else - endToken = endToken + range.end; + endToken = endToken + range.m_end; Keyword *keyword; - for (keyword = std::get_if(&m_requiredInputs.fullTokens.at(range.start).value); endToken > m_curr; next()) { - + for (keyword = std::get_if(&m_requiredInputs.fullTokens.at(range.m_start).value); endToken > m_curr; next()) { + if (peek(tkn::Separator::EndOfProgram)) + return; if (peek(tkn::Literal::Identifier)) { auto identifier = getValue(0); @@ -2087,7 +2066,7 @@ namespace hex::plugin::builtin { if (keyword != nullptr && (*keyword == Keyword::Enum)) { typeStr = name; } else if (isArgument) { - typeStr = getArgumentTypeName(range.start, delimiter1); + typeStr = getArgumentTypeName(range.m_start, delimiter1); } else { typeStr = getVariableTypeName(); if (typeStr.empty() && keyword != nullptr && *keyword == Keyword::Bitfield) @@ -2111,7 +2090,7 @@ namespace hex::plugin::builtin { void TextHighlighter::loadTypeDefinitions( UnorderedBlocks tokenRangeMap, std::vector identifierTypes, Definitions &types) { for (const auto &[name, range]: tokenRangeMap) { - m_curr = m_startToken + range.start+1; + m_curr = m_startToken + range.m_start+1; if (!peek(tkn::Literal::Identifier)) continue; @@ -2172,6 +2151,9 @@ namespace hex::plugin::builtin { // the text into lines and creates a lookup table for the // first token id of each line. void TextHighlighter::loadText() { + u32 tokenCount = m_requiredInputs.fullTokens.size(); + if (tokenCount == 0) + return; if (!m_lines.empty()) m_lines.clear(); @@ -2188,22 +2170,15 @@ namespace hex::plugin::builtin { m_lines.push_back(""); m_firstTokenIdOfLine.clear(); - u32 tokenId = 0; - u32 tokenCount = m_requiredInputs.fullTokens.size(); - u32 lineIndex; - u32 count; - - if (tokenCount == 0) - return; - lineIndex = m_requiredInputs.fullTokens.at(tokenId).location.line - 1; - count = m_requiredInputs.fullTokens.at(tokenCount - 1).location.line + 1; + u32 lineIndex = m_requiredInputs.fullTokens.at(tokenId).location.line - 1; + u32 count = m_requiredInputs.fullTokens.at(tokenCount - 1).location.line + 1; m_firstTokenIdOfLine.resize(count, -1); m_firstTokenIdOfLine.at(lineIndex) = 0; tokenId++; u32 currentLine = lineIndex; while ( currentLine < count) { - while (lineIndex <= currentLine && tokenId <= tokenCount - 1) { + while (lineIndex <= currentLine && tokenId + 1 <= tokenCount) { lineIndex = m_requiredInputs.fullTokens.at(tokenId).location.line - 1; tokenId++; } @@ -2269,29 +2244,29 @@ namespace hex::plugin::builtin { // global token ranges are the complement (aka inverse) of the union // of the UDT and function token ranges void TextHighlighter::invertGlobalTokenRange() { - std::set ranges; + std::set ranges; auto size = m_globalTokenRange.size(); auto tokenCount = m_requiredInputs.fullTokens.size(); if (size == 0) { - ranges.insert(Interval(0, tokenCount)); + ranges.insert(TokenInterval(0, tokenCount)); } else { auto it = m_globalTokenRange.begin(); auto it2 = std::next(it); - if (it->start != 0) - ranges.insert(Interval(0, it->start)); + if (it->m_start != 0) + ranges.insert(TokenInterval(0, it->m_start - 1)); while (it2 != m_globalTokenRange.end()) { - if (it->end < it2->start) - ranges.insert(Interval(it->end, it2->start)); + if (it->m_end < it2->m_start) + ranges.insert(TokenInterval(it->m_end + 1, it2->m_start - 1)); else - ranges.insert(Interval(it->start, it2->end)); + ranges.insert(TokenInterval(it->m_start, it2->m_end)); it = it2; it2 = std::next(it); } - if (it->end < (i32) (tokenCount-1)) - ranges.insert(Interval(it->end, tokenCount-1)); + if (it->m_end < (i32) (tokenCount-1)) + ranges.insert(TokenInterval(it->m_end + 1, tokenCount-1)); } m_globalTokenRange = ranges; } @@ -2315,8 +2290,11 @@ namespace hex::plugin::builtin { void TextHighlighter::getTokenIdForArgument(i32 start, i32 argNumber, Token delimiter) { m_curr = m_startToken; next(start); - while (!peek(delimiter)) + while (!peek(delimiter)) { + if (peek(tkn::Separator::EndOfProgram)) + return; next(); + } next(); i32 count = 0; while (count < argNumber && !peek(tkn::Separator::EndOfProgram)) { @@ -2336,7 +2314,7 @@ namespace hex::plugin::builtin { for (auto &definition: definitions) { if (definition.typeStr == "auto" && (definition.idType == Token::Identifier::IdentifierType::TemplateArgument || definition.idType == Token::Identifier::IdentifierType::FunctionParameter)) { - auto argumentIndex = getArgumentNumber(tokenRange[name].start, definition.tokenIndex); + auto argumentIndex = getArgumentNumber(tokenRange[name].m_start, definition.tokenIndex); if (tokenRange == m_UDTTokenRange || !m_attributeFunctionArgumentType.contains(name) || m_attributeFunctionArgumentType[name].empty()) { @@ -2413,35 +2391,40 @@ namespace hex::plugin::builtin { // Calculates the union of all the UDT and function token ranges // and inverts the result. void TextHighlighter::getGlobalTokenRanges() { - std::set ranges; + std::set ranges; for (const auto &[name, range]: m_UDTTokenRange) ranges.insert(range); for (const auto &[name, range]: m_functionTokenRange) ranges.insert(range); - if (ranges.empty()) - return; - - auto it = ranges.begin(); - auto next = std::next(it); - while (next != ranges.end()) { - - if (next->start - it->end < 2) { - auto &range = const_cast(*it); - range.end = next->end; - ranges.erase(next); - next = std::next(it); - } else { - it++; - next = std::next(it); + if (!ranges.empty()) { + auto rit = ranges.rbegin(); + auto rnext = std::next(rit); + while (rnext != ranges.rend()) { + if (rit->contains(*rnext)) { + ranges.erase(*rnext); + rnext = std::next(rit); + } else if (rnext->contiguous(*rit)) { + TokenInterval range = *rnext; + range.m_end = rit->m_end; + ranges.erase(*rnext); + ranges.erase(*rit); + ranges.insert(range); + rit = std::set::reverse_iterator(ranges.find(range)); + rit--; + rnext = std::next(rit); + } else { + rit++; + rnext = std::next(rit); + } } } m_globalTokenRange = ranges; invertGlobalTokenRange(); for (auto tokenRange: m_globalTokenRange) { - if ((u32) tokenRange.end == m_requiredInputs.fullTokens.size()) { - tokenRange.end -= 1; + if ((u32) tokenRange.m_end == m_requiredInputs.fullTokens.size()) { + tokenRange.m_end -= 1; m_globalBlocks.insert(tokenRange); } } @@ -2452,8 +2435,8 @@ namespace hex::plugin::builtin { void TextHighlighter::fixGlobalVariables() { m_startToken = m_originalPosition = m_partOriginalPosition = TokenIter(m_requiredInputs.fullTokens.begin(), m_requiredInputs.fullTokens.end()); for (auto range: m_globalTokenRange) { - auto startToken = TokenIter(m_requiredInputs.fullTokens.begin() + range.start, m_requiredInputs.fullTokens.begin() + range.end); - auto endToken = TokenIter(m_requiredInputs.fullTokens.begin() + range.end, m_requiredInputs.fullTokens.end()); + auto startToken = TokenIter(m_requiredInputs.fullTokens.begin() + range.m_start, m_requiredInputs.fullTokens.begin() + range.m_end); + auto endToken = TokenIter(m_requiredInputs.fullTokens.begin() + range.m_end, m_requiredInputs.fullTokens.end()); for (m_curr = startToken; endToken > m_curr; next()) { @@ -2526,7 +2509,7 @@ namespace hex::plugin::builtin { m_globalTokenRange.clear(); if (m_requiredInputs.fullTokens.size() > 1) { - m_globalTokenRange.insert(Interval(0, m_requiredInputs.fullTokens.size() - 1)); + m_globalTokenRange.insert(TokenInterval(0, m_requiredInputs.fullTokens.size() - 1)); getTokenRanges(IdentifierType::NameSpace); getTokenRanges(IdentifierType::UDT); getTokenRanges(IdentifierType::Function); diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index c440f1836..e1a1ccbfd 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -19,9 +19,11 @@ #include #include + #include #include #include +#include #include #include @@ -1474,6 +1476,7 @@ namespace hex::plugin::builtin { TaskManager::createBackgroundTask("HighlightSourceCode", [this,provider](auto &) { m_textHighlighter.get(provider).highlightSourceCode(); }); } else if (m_changesWereColored && !m_allStepsCompleted) { m_textHighlighter.get(provider).setRequestedIdentifierColors(); + m_textEditor.get(provider).getLines().readHiddenLines(); m_allStepsCompleted = true; } @@ -1639,6 +1642,7 @@ namespace hex::plugin::builtin { this->evaluatePattern(code, provider); m_textEditor.get(provider).setText(code, true); + m_textEditor.get(provider).readHiddenLines(); m_sourceCode.get(provider) = code; if (trackFile) { m_changeTracker.get(provider) = wolv::io::ChangeTracker(file); @@ -1853,6 +1857,7 @@ namespace hex::plugin::builtin { m_consoleEditor.get(provider).setReadOnly(true); m_consoleEditor.get(provider).setShowCursor(false); m_consoleEditor.get(provider).setShowLineNumbers(false); + m_consoleEditor.get(provider).getLines().enableCodeFolds(false); m_consoleEditor.get(provider).setSourceCodeEditor(&m_textEditor.get(provider)); std::string sourcecode = pl::api::Source::DefaultSource; std::string error = "E: "; @@ -1869,7 +1874,7 @@ namespace hex::plugin::builtin { EventProviderChanged::subscribe(this, [this](prv::Provider *oldProvider, prv::Provider *newProvider) { if (oldProvider != nullptr) { - m_sourceCode.get(oldProvider) = m_textEditor.get(oldProvider).getText(); + m_sourceCode.get(oldProvider) = m_textEditor.get(oldProvider).getText(true); m_scroll.get(oldProvider) = m_textEditor.get(oldProvider).getScroll(); m_cursorPosition.get(oldProvider) = m_textEditor.get(oldProvider).getCursorPosition(); m_selection.get(oldProvider) = m_textEditor.get(oldProvider).getSelection(); @@ -1882,7 +1887,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),false); + m_textEditor.get(newProvider).setCursorPosition(m_cursorPosition.get(newProvider),false,false); m_textEditor.get(newProvider).setScroll(m_scroll.get(newProvider)); m_textEditor.get(newProvider).setSelection(m_selection.get(newProvider)); m_textEditor.get(newProvider).setBreakpoints(m_breakpoints.get(newProvider)); @@ -2535,7 +2540,37 @@ namespace hex::plugin::builtin { ShortcutManager::addShortcut(this, CTRLCMD + SHIFT + Keys::M + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.move_matched_bracket", [this] { if (auto editor = getEditorFromFocusedWindow(); editor != nullptr) - editor->moveToMatchedBracket(false); + editor->moveToMatchedDelimiter(false); + }); + + ShortcutManager::addShortcut(this, CTRLCMD + Keys::KeyPadAdd + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.code_fold_expand", [this] { + if (m_focusedSubWindowName.contains(TextEditorView)) + m_textEditor.get(ImHexApi::Provider::get()).codeFoldExpand(); + }); + + ShortcutManager::addShortcut(this, CTRLCMD + ALT + Keys::KeyPadAdd + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.code_fold_expand_recursively", [this] { + if (m_focusedSubWindowName.contains(TextEditorView)) + m_textEditor.get(ImHexApi::Provider::get()).codeFoldExpand(0,true,false); + }); + + ShortcutManager::addShortcut(this, CTRLCMD + SHIFT + Keys::KeyPadAdd + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.code_fold_expand_all", [this] { + if (m_focusedSubWindowName.contains(TextEditorView)) + m_textEditor.get(ImHexApi::Provider::get()).codeFoldExpand(0, false, true); + }); + + ShortcutManager::addShortcut(this, CTRLCMD + Keys::KeyPadSubtract + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.code_fold_collapse", [this] { + if (m_focusedSubWindowName.contains(TextEditorView)) + m_textEditor.get(ImHexApi::Provider::get()).codeFoldCollapse(); + }); + + ShortcutManager::addShortcut(this, CTRLCMD + ALT + Keys::KeyPadSubtract + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.code_fold_collapse_recursively", [this] { + if (m_focusedSubWindowName.contains(TextEditorView)) + m_textEditor.get(ImHexApi::Provider::get()).codeFoldCollapse(0, true, false); + }); + + ShortcutManager::addShortcut(this, CTRLCMD + SHIFT + Keys::KeyPadSubtract + AllowWhileTyping, "hex.builtin.view.pattern_editor.shortcut.code_fold_collapse_all", [this] { + if (m_focusedSubWindowName.contains(TextEditorView)) + m_textEditor.get(ImHexApi::Provider::get()).codeFoldCollapse(0, false, true); }); // Generate pattern code report @@ -2706,7 +2741,7 @@ namespace hex::plugin::builtin { fs::DialogMode::Save, { {"Pattern File", "hexpat"}, {"Pattern Import File", "pat"} }, [this, provider, trackFile](const auto &path) { wolv::io::File file(path, wolv::io::File::Mode::Create); - file.writeString(wolv::util::trim(m_textEditor.get(provider).getText())); + file.writeString(wolv::util::trim(m_textEditor.get(provider).getText(true))); m_patternFileDirty.get(provider) = false; auto loadedPath = m_changeTracker.get(provider).getPath(); if ((loadedPath.empty() && loadedPath != path) || (!loadedPath.empty() && !trackFile) || loadedPath == path) @@ -2729,7 +2764,7 @@ namespace hex::plugin::builtin { wolv::io::File file(path, wolv::io::File::Mode::Create); if (file.isValid() && trackFile) { if (isPatternDirty(provider)) { - file.writeString(wolv::util::trim(m_textEditor.get(provider).getText())); + file.writeString(wolv::util::trim(m_textEditor.get(provider).getText(true))); m_patternFileDirty.get(provider) = false; } return; diff --git a/plugins/ui/CMakeLists.txt b/plugins/ui/CMakeLists.txt index 6994b0988..0f8206440 100644 --- a/plugins/ui/CMakeLists.txt +++ b/plugins/ui/CMakeLists.txt @@ -21,6 +21,7 @@ add_imhex_plugin( source/ui/text_editor/render.cpp source/ui/text_editor/support.cpp source/ui/text_editor/utf8.cpp + source/ui/text_editor/codeFolder.cpp INCLUDES include LIBRARIES diff --git a/plugins/ui/include/ui/text_editor.hpp b/plugins/ui/include/ui/text_editor.hpp index a13d82223..9b9bc6b3e 100644 --- a/plugins/ui/include/ui/text_editor.hpp +++ b/plugins/ui/include/ui/text_editor.hpp @@ -1,5 +1,5 @@ #pragma once -#include "imgui_internal.h" + #include #include #include @@ -8,9 +8,17 @@ #include #include #include +#include #include #include +#include +#include +#include "imgui.h" +#include "imgui_internal.h" #include +#include +#include +#include #include namespace hex::ui { @@ -18,6 +26,8 @@ namespace hex::ui { class TextEditor { public: + class Interval; + class Lines; class Range { public: @@ -42,9 +52,8 @@ namespace hex::ui { 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; + Coordinates sanitize(Lines &lines); + bool isValid(Lines &lines); bool operator==(const Coordinates &o) const; bool operator!=(const Coordinates &o) const; bool operator<(const Coordinates &o) const; @@ -65,7 +74,8 @@ namespace hex::ui { 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); }} - + explicit Range(const Interval &lines) : m_start(Coordinates(lines.m_start, 0)), m_end(Coordinates(lines.m_end, 0)) { + if (m_start > m_end) { std::swap(m_start, m_end); }} Coordinates getSelectedLines(); @@ -100,6 +110,36 @@ namespace hex::ui { }; using Coordinates = Range::Coordinates; + + class Interval { + public: + friend class TextEditor; + friend class Range; + Interval() : m_start(0), m_end(0) {} + Interval(i32 start, i32 end) : m_start(start), m_end(end) { + if (m_start > m_end) std::swap(m_start, m_end);} + Interval(const Interval &other) : m_start(other.m_start), m_end(other.m_end) {} + explicit Interval(ImVec2 vec) : m_start((i32)vec.x), m_end((i32)vec.y) { + if (m_start > m_end) std::swap(m_start, m_end);} + + Interval &operator=(const Interval &interval); + Interval &operator=(Interval &&interval) noexcept; + bool operator<(const Interval &other) const { return other.m_end > m_end; } + bool operator>(const Interval &other) const { return m_end > other.m_end; } + bool operator==(const Interval &other) const { return m_start == other.m_start && m_end == other.m_end; } + bool operator!=(const Interval &other) const { return m_start != other.m_start || m_end != other.m_end; } + bool operator<=(const Interval &other) const { return other.m_end >= m_end; } + bool operator>=(const Interval &other) const { return m_end >= other.m_end; } + bool contains_or_equals(const Interval &other) const { return other.m_start >= m_start && other.m_end <= m_end; } + bool contains(const Interval &other) const { return (other.m_start >= m_start && other.m_end < m_end) || (other.m_start > m_start && other.m_end <= m_end); } + bool contains(i32 value, bool inclusive = true) const; + bool contiguous(const Interval &other) const; + operator ImVec2() const { return ImVec2((float)m_start, (float)m_end); } + private: + i32 m_start; + i32 m_end; + }; + class EditorState { public: friend class TextEditor; @@ -112,40 +152,42 @@ namespace hex::ui { }; class UndoRecord; - class UndoAction; - using UndoBuffer = std::vector; - using UndoRecords = std::vector; class FindReplaceHandler { public: FindReplaceHandler(); using Matches = std::vector; Matches &getMatches() { return m_matches; } - bool findNext(TextEditor *editor, u64 &byteIndex); - u32 findMatch(TextEditor *editor, i32 index); - bool replace(TextEditor *editor, bool right); - bool replaceAll(TextEditor *editor); + bool findNext(Lines *lines, u64 &byteIndex); + bool findNext(TextEditor *editor, u64 &byteIndex) { return findNext(&editor->m_lines, byteIndex); }; + u32 findMatch(Lines *lines, i32 index); + u32 findMatch(TextEditor *editor, i32 index) { return findMatch(&editor->m_lines, index); }; + bool replace(Lines *lines, bool right); + bool replace(TextEditor *editor, bool right) { return replace(&editor->m_lines, right); }; + bool replaceAll(Lines *lines); + bool replaceAll(TextEditor *editor) { return replaceAll(&editor->m_lines); }; std::string &getFindWord() { return m_findWord; } - - void setFindWord(TextEditor *editor, const std::string &findWord); - + void setFindWord(Lines *lines, const std::string &findWord); + void setFindWord(TextEditor *editor, const std::string &findWord) { setFindWord(&editor->m_lines, findWord); }; std::string &getReplaceWord() { return m_replaceWord; } void setReplaceWord(const std::string &replaceWord) { m_replaceWord = replaceWord; } - void selectFound(TextEditor *editor, i32 found); - void findAllMatches(TextEditor *editor, std::string findWord); - u32 findPosition(TextEditor *editor, Coordinates pos, bool isNext); + void selectFound(Lines *lines, i32 found); + void selectFound(TextEditor *editor, i32 found) { selectFound(&editor->m_lines, found); }; + void findAllMatches(Lines *lines, std::string findWord); + void findAllMatches(TextEditor *editor, std::string findWord) { findAllMatches(&editor->m_lines, findWord); }; + u32 findPosition(Lines *lines, Coordinates pos, bool isNext); + u32 findPosition(TextEditor *editor, Coordinates pos, bool isNext) { return findPosition(&editor->m_lines, pos, isNext); }; bool getMatchCase() const { return m_matchCase; } - - void setMatchCase(TextEditor *editor, bool matchCase); - + void setMatchCase(Lines *lines, bool matchCase); + void setMatchCase(TextEditor *editor, bool matchCase) { setMatchCase(&editor->m_lines, matchCase); }; bool getWholeWord() const { return m_wholeWord; } - void setWholeWord(TextEditor *editor, bool wholeWord); - + void setWholeWord(Lines *lines, bool wholeWord); + void setWholeWord(TextEditor *editor, bool wholeWord) { setWholeWord(&editor->m_lines, wholeWord); }; bool getFindRegEx() const { return m_findRegEx; } - void setFindRegEx(TextEditor *editor, bool findRegEx); - + void setFindRegEx(Lines *lines, bool findRegEx); + void setFindRegEx(TextEditor *editor, bool findRegEx) { setFindRegEx(&editor->m_lines, findRegEx); }; void resetMatches(); - UndoRecords m_undoBuffer; + std::vector m_undoBuffer; private: std::string m_findWord; std::string m_replaceWord; @@ -162,12 +204,16 @@ namespace hex::ui { FunctionVariable, FunctionParameter, UserDefinedType, PlacedVariable, GlobalVariable, NameSpace, TypeDef, UnkIdentifier, DocComment, DocBlockComment, BlockComment, GlobalDocComment, Comment, PreprocIdentifier, Max }; + using Token = pl::core::Token; + using TokenIter = pl::hlp::SafeIterator::const_iterator>; + using Location = pl::core::Location; 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; + using CodeFoldBlocks = std::map; struct Identifier { Coordinates m_location; std::string m_declaration;}; using Identifiers = std::unordered_map; @@ -217,9 +263,55 @@ namespace hex::ui { std::string m_errorText; }; + + class CodeFold : public ActionableBox { + public: + CodeFold() = default; + explicit CodeFold(Lines *lines, Range keys, const ImRect &startBox, const ImRect &endBox); + + bool trigger() override; + void callback() override; + bool startHovered(); + bool endHovered(); + bool isDetected(); + bool isOpen(); + void open(); + void close(); + void moveFold(float lineCount, float lineHeight); + private: + Lines *lines; + Range key; + CursorChangeBox codeFoldStartCursorBox; + ActionableBox codeFoldEndActionBox; + CursorChangeBox codeFoldEndCursorBox; + }; + + class CodeFoldTooltip : public ActionableBox { + public: + CodeFoldTooltip(Lines *lines,const Range &key, const ImRect &box) : ActionableBox(box), m_lines(lines), m_key(key) {} + + bool trigger() override; + void callback() override; + private: + Lines *m_lines; + Range m_key; + const std::string popupLineNumbers = "##popupLineNumbers"; + const std::string popupText = "##popupText"; + }; + using ErrorGotoBoxes = std::map; using CursorBoxes = std::map; using ErrorHoverBoxes = std::map; + using CodeFoldKeys = std::set; + using CodeFoldDelimiters = std::map>; + using CodeFoldKeyMap = std::map; + using CodeFoldKeyLineMap = std::multimap; + using CodeFoldState = std::map; + // using RowsToCodeFoldMap = std::map; + using LineIndexToScreen = std::map; + using MultiLinesToRow = std::map; + using RowCodeFoldTooltips = 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. @@ -229,11 +321,12 @@ namespace hex::ui { class LineIterator { public: friend class hex::ui::TextEditor; + friend class hex::ui::TextEditor::Lines; 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++(); LineIterator operator=(const LineIterator &other); bool operator!=(const LineIterator &other) const; bool operator==(const LineIterator &other) const; @@ -266,32 +359,36 @@ namespace hex::ui { }; union Flags { - explicit Flags(char value) : m_value(value) {} - explicit Flags(FlagBits bits) : m_bits(bits) {} + Flags(char value) : m_value(value) {} + Flags(FlagBits bits) : m_bits(bits) {} FlagBits m_bits; char m_value; }; - enum class LinePart: u8 { Chars, Utf8, Colors, Flags }; + enum class LinePart { Chars, Utf8, Colors, Flags }; - Line() : m_lineMaxColumn(-1) {} - explicit Line(const char *line) : Line(std::string(line)) {} + 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 &line) const; - bool operator!=(const Line &line) const; + bool operator==(const Line &o) const; + bool operator!=(const Line &o) const; [[nodiscard]] i32 indexColumn(i32 stringIndex) const; [[nodiscard]] i32 maxColumn(); [[nodiscard]] i32 maxColumn() const; [[nodiscard]] i32 columnIndex(i32 column) const; [[nodiscard]] i32 textSize() const; [[nodiscard]] i32 textSize(u32 index) const; + ImVec2 intervalToScreen(Interval stringIndices) const; i32 lineTextSize(TrimMode trimMode = TrimMode::TrimNone); [[nodiscard]] i32 stringTextSize(const std::string &str) const; i32 textSizeIndex(float textSize, i32 position); + Line trim(TrimMode trimMode = TrimMode::TrimNone); + void print(i32 lineIndex, i32 maxLineIndex, std::optional pos = std::nullopt); + u32 skipSpaces(i32 index); [[nodiscard]] LineIterator begin() const; [[nodiscard]] LineIterator end() const; LineIterator begin(); @@ -299,7 +396,6 @@ namespace hex::ui { Line &operator=(const Line &line); Line &operator=(Line &&line) noexcept; [[nodiscard]] u64 size() const; - TextEditor::Line trim(TrimMode trimMode); [[nodiscard]] char front(LinePart part = LinePart::Chars) const; [[nodiscard]] std::string frontUtf8(LinePart part = LinePart::Chars) const; void push_back(char c); @@ -307,9 +403,11 @@ namespace hex::ui { [[nodiscard]] 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; + char at(u64 index) const; // C++ can't overload functions based on return type, so use any type other // than u64 to avoid ambiguity. std::string operator[](i64 column) const; + std::string at(i64 column) const; void setNeedsUpdate(bool needsUpdate); void append(const char *text); void append(const char text); @@ -337,7 +435,80 @@ namespace hex::ui { i32 m_lineMaxColumn; }; - using Lines = std::vector; + class FoldedLine { + public: + friend class Lines; + friend class TextEditor; + enum class FoldType : u8 { + NoDelimiters = 0, + AddsFirstLine = 1, // does not imply having open delimiter (ex. import). + HasOpenDelimiter = 2, // implies first line is added. + FirstLineNeedsDelimiter = 4, // implies having open delimiter. Needed when open delimiter is not on first line.. + AddsLastLine = 8, + HasCloseDelimiter = 16, + Invalid = 32 + }; + + FoldedLine() = default; + explicit FoldedLine(Lines *lines); + + void insertKey(const Range &key); + void removeKey(const Range &key); + void loadSegments(); + bool firstLineNeedsDelimiter(); + bool addsLastLineToFold(); + bool addsFullFirstLineToFold(); + Range findDelimiterCoordinates(Range key); + private: + Line m_foldedLine; + Lines *m_lines; + Range m_full; + i32 m_row; + FoldType m_type = FoldType::Invalid; + std::string m_brackets; + std::vector m_keys; + std::vector m_keysToRemove; + std::vector m_ellipsisIndices; + std::vector m_foldedSegments; + std::vector m_unfoldedSegments; + Coordinates m_cursorPosition; + Range m_selection; + bool m_built = false; + }; + + class MatchedDelimiter { + public: + friend class Lines; + friend class TextEditor; + MatchedDelimiter() : m_active(false), m_changed(false), m_nearCursor(0, 0), m_matched(0, 0) {} + MatchedDelimiter(bool active, bool changed, const Coordinates &nearCursor, const Coordinates &matched) : m_active(active), m_changed(changed), m_nearCursor(nearCursor), m_matched(matched) {} + + bool checkPosition(Lines *lines, Coordinates &from); + bool setNearCursor(Lines *lines, const Coordinates &from); + bool coordinatesNearDelimiter(Lines *lines, Coordinates &from); + i32 detectDirection(Lines *lines, const Coordinates &from); + void findMatchingDelimiter(Lines *lines, bool folded = true); + Coordinates findMatchingDelimiter(Lines *lines, Coordinates &from, bool folded = true); + [[nodiscard]] bool isActive() const { return m_active; } + [[nodiscard]] bool hasChanged() const { return m_changed; } + private: + bool m_active = false; + bool m_changed = false; + Coordinates m_nearCursor = {}; + Coordinates m_matched = {}; + }; + + class Segment { + public: + friend class TextEditor; + Segment(Coordinates foldEnd, const Interval &segment) : m_foldEnd(foldEnd), m_segment(segment) {} + bool operator==(const Segment &o) const { + return m_foldEnd == o.m_foldEnd && m_segment == o.m_segment; + } + private: + Coordinates m_foldEnd; + Interval m_segment; + }; struct LanguageDefinition { typedef std::pair TokenRegexString; @@ -393,9 +564,11 @@ namespace hex::ui { EditorState m_after; }; + using UndoRecords = std::vector; + class UndoAction { public: - UndoAction() = default; + UndoAction() {} ~UndoAction() {} explicit UndoAction(const UndoRecords &records) : m_records(records) {} void undo(TextEditor *editor); @@ -404,94 +577,341 @@ namespace hex::ui { UndoRecords m_records; }; - - - struct MatchedBracket { - bool m_active = false; - bool m_changed = false; - Coordinates m_nearCursor; - Coordinates m_matched; - static const std::string s_separators; - static const std::string s_operators; - - MatchedBracket(const MatchedBracket &other) : m_active(other.m_active), m_changed(other.m_changed), - m_nearCursor(other.m_nearCursor), - m_matched(other.m_matched) {} - - MatchedBracket() : m_nearCursor(0, 0), m_matched(0, 0) {} - MatchedBracket(bool active, bool changed, const Coordinates &nearCursor, const Coordinates &matched) - : m_active(active), m_changed(changed), m_nearCursor(nearCursor), m_matched(matched) {} - - bool checkPosition(TextEditor *editor, const Coordinates &from); - bool isNearABracket(TextEditor *editor, const Coordinates &from); - i32 detectDirection(TextEditor *editor, const Coordinates &from); - void findMatchingBracket(TextEditor *editor); - [[nodiscard]] bool isActive() const { return m_active; } - [[nodiscard]] bool hasChanged() const { return m_changed; } + class HiddenLine { + public: + friend class Lines; + HiddenLine() = default; + HiddenLine(i32 lineIndex, const std::string &lineContent) : m_lineIndex(lineIndex), m_line(lineContent) {} + private: + i32 m_lineIndex; + std::string m_line; }; + using CodeFolds = std::map; + using Segments = std::vector; + using RowToSegments = std::map; + using UndoBuffer = std::vector; + using RowToFoldedLine = std::map; - public: + + class Lines { + public: + friend class TextEditor; + Lines() : m_unfoldedLines({}), m_foldedLines({}), m_hiddenLines({}), m_matchedDelimiter(), m_colorizerEnabled(true), m_defines({}) {} + + enum class FoldSymbol { Line, Up, Down, Square }; + + Line &at(i32 index); + Line &operator[](i32 index); + i32 size() const; + void colorizeRange(); + void colorizeInternal(); + bool isEmpty(); + void moveToMatchedDelimiter(bool select = false); + bool isTrueMatchingDelimiter(); + void clearErrorMarkers(); + void clearActionables(); + bool isEndOfLine(const Coordinates &coordinates); + bool isEndOfFile(const Coordinates &coordinates); + bool isEndOfLine(); + bool isStartOfLine() const; + bool lineNeedsDelimiter(i32 lineIndex); + FindReplaceHandler *getFindReplaceHandler() { return &m_findReplaceHandler; } + void clearGotoBoxes() { m_errorGotoBoxes.clear(); } + void clearCursorBoxes() { m_cursorBoxes.clear(); } + void clearCodeFolds(); + void addClickableText(std::string text) { m_clickableText.push_back(text); } + //void setErrorMarkers(const ErrorMarkers &markers) { m_errorMarkers = markers; } + Breakpoints &getBreakpoints() { return m_breakpoints; } + void saveCodeFoldStates(); + void applyCodeFoldStates(); + float lineIndexToRow(i32 lineNumber); + float rowToLineIndex(i32 row); + void getRowSegments(); + void initializeCodeFolds(); + bool updateCodeFolds(); + ImRect getBoxForRow(u32 lineNumber); + void setFirstRow(); + i32 lineMaxColumn(i32 lineNumber); + Interval indexScreenPosition(i32 lineIndex, Interval stringIndices); + bool isMultiLineRow(i32 row); + void enableCodeFolds(bool enable) { m_codeFoldsDisabled = !enable; }; + void moveRight(i32 amount, bool select, bool wordMode); + void moveLeft(i32 amount, bool select, bool wordMode); + void moveDown(i32 amount, bool select); + void moveUp(i32 amount, bool select); + void moveHome(bool select); + void moveEnd(bool select); + std::vector removeEmbeddedFolds(); + bool isLastLine(i32 lineIndex); + bool isLastLine(); + Coordinates find(const std::string &text, const Coordinates &start); + Coordinates rfind(const std::string &text, const Coordinates &start); + void setRowToLineIndexMap(); + Coordinates lineCoordinates(const Coordinates &coordinates); + Coordinates lineCoordinates(i32 lineIndex, i32 column); + Range lineCoordinates(const Range &value); + friend bool Range::Coordinates::isValid(Lines &lines); + friend TextEditor::Coordinates Range::Coordinates::sanitize(Lines &lines); + void appendLine(const std::string &value); + void readHiddenLines(); + void writeHiddenLines(); + void setSelection(const Range &selection); + Range getSelection() const; + ImVec2 getLineStartScreenPos(float leftMargin, float lineNumber); + ImVec2 &getCharAdvance() { return m_charAdvance; } + std::vector getDeactivatedBlocks(); + std::string getSelectedText(); + void deleteSelection(); + inline void setTextChanged(bool value) { m_textChanged = value; } + inline bool isTextChanged() { return m_textChanged; } + void setLanguageDefinition(const LanguageDefinition &aLanguageDef); + const LanguageDefinition &getLanguageDefinition() const { return m_languageDefinition; } + TextEditor::PaletteIndex getColorIndexFromFlags(Line::Flags flags); + void insertLine(i32 index, const std::string &text); + Coordinates lineIndexCoords(i32 lineNumber, i32 stringIndex); + void colorize(); + i32 insertTextAt(Coordinates &where, const std::string &value); + float getMaxDisplayedRow(); + float getGlobalRowMax(); + ImVec2 foldedCoordsToScreen(Coordinates coordinates); + i32 lineCoordsIndex(const Coordinates &coordinates); + u32 skipSpaces(const Coordinates &from); + void closeCodeFold(const Range &key, bool userTriggered = false); + void openCodeFold(const Range &key); + void removeKeys(); + ImVec2 indexCoordsToScreen(Coordinates indexCoords); + inline void setImGuiChildIgnored(bool value) { m_ignoreImGuiChild = value; } + inline bool isImGuiChildIgnored() const { return m_ignoreImGuiChild; } + ImVec2 lineIndexToScreen(i32 lineIndex, Interval stringIndices); + void printCodeFold(const Range &key); + void resetCursorBlinkTime(); + void setUnfoldIfNeeded(bool unfoldIfNeeded) {m_unfoldIfNeeded = unfoldIfNeeded;} + std::string getRange(const Range &rangeToGet); + void setCursorPosition(const Coordinates &position, bool unfoldIfNeeded = true, bool scrollToCursor = true); + void setFocusAtCoords(const Coordinates &coords, bool ensureVisible = false); + void ensureCursorVisible(); + std::vector unfoldedEllipsisCoordinates(Range delimiterCoordinates); + Coordinates unfoldedToFoldedCoords(const Coordinates &coords); + Coordinates foldedToUnfoldedCoords(const Coordinates &coords); + void setScrollY(); + Coordinates findPreviousWord(const Coordinates &from); + Coordinates findNextWord(const Coordinates &from); + Line &insertLine(i32 index); + void deleteRange(const Range &rangeToDelete); + inline void clearBreakpointsChanged() { m_breakPointsChanged = false; } + inline bool isBreakpointsChanged() { return m_breakPointsChanged; } + Coordinates stringIndexCoords(i32 strIndex, const std::string &input); + void refreshSearchResults(); + inline void setReadOnly(bool value) { m_readOnly = value; } + void removeLines(i32 start, i32 end); + void removeLine(i32 index); + float textDistanceToLineStart(const Coordinates &from); + std::string getText(bool addHiddenLines = false); + void setCursorPosition(); + void ensureSelectionNotFolded(); + bool hasSelection(); + i32 insertTextAtCursor(const std::string &value); + void addUndo(std::vector value); + void insertText(const std::string &value); + void insertText(const char *value); + Interval findBlockInRange(Interval interval); + std::vector searchRangeForBlocks(Interval interval); + std::pair getDelimiterLineNumbers(i32 start, i32 end, const std::string &delimiters); + void nonDelimitedFolds(); + std::pair convertIndexToLineNumbers(Interval interval); + std::pair findMatchingDelimiter(i32 from); + CodeFoldBlocks foldPointsFromSource(); + Coordinates findCommentEndCoord(i32 tokenId); + std::set blocksFromGlobal(); + void skipAttribute(); + bool isTokenIdValid(i32 tokenId); + bool isLocationValid(Location location); + pl::core::Location getLocation(i32 tokenId); + i32 getTokenId(pl::core::Location location); + void loadFirstTokenIdOfLine(); + i32 nextLine(i32 line); + void setAllCodeFolds(); + void advanceToNextLine(i32 &lineIndex, i32 ¤tTokenId, Location &location); + void advanceTokenId(i32 &lineIndex, i32 ¤tTokenId, Location &location); + void moveToLocationColumn(i32 locationColumn, i32 ¤tTokenId, Location &location); + void resetToTokenId(i32 &lineIndex, i32 ¤tTokenId, Location &location); + + constexpr static u32 Normal = 0; + constexpr static u32 Not = 1; + template T *getValue(const i32 index); + void next(i32 count = 1); + bool begin(); + void partBegin(); + void reset(); + void partReset(); + bool resetIfFailed(const bool value); + template bool sequenceImpl(); + template bool matchOne(const Token &token); + template bool sequenceImpl(const auto &... args); + template bool sequence(const Token &token, const auto &... args); + bool isValid(); + bool peek(const Token &token, const i32 index = 0); + + friend bool Coordinates::operator==(const Coordinates &o) const; + friend bool Coordinates::operator!=(const Coordinates &o) const; + friend bool Coordinates::operator<(const Coordinates &o) const; + friend bool Coordinates::operator>(const Coordinates &o) const; + friend bool Coordinates::operator<=(const Coordinates &o) const; + friend bool Coordinates::operator>=(const Coordinates &o) const; + friend Coordinates Coordinates::operator+(const Coordinates &o) const; + friend Coordinates Coordinates::operator-(const Coordinates &o) const; + + private: + std::vector m_unfoldedLines; + RowToFoldedLine m_foldedLines; + std::vector m_hiddenLines; + std::map m_rowToFoldSymbol = {}; + MatchedDelimiter m_matchedDelimiter = {}; + bool m_colorizerEnabled = true; + std::vector m_defines; + FindReplaceHandler m_findReplaceHandler; + RowToSegments m_rowToSegments = {}; + EditorState m_state = {}; + UndoBuffer m_undoBuffer = {}; + std::vector m_leadingLineSpaces = {}; + i32 m_undoIndex = 0; + bool m_updateFlags = true; + Breakpoints m_breakpoints = {}; + ErrorMarkers m_errorMarkers = {}; + ErrorHoverBoxes m_errorHoverBoxes = {}; + ErrorGotoBoxes m_errorGotoBoxes = {}; + CursorBoxes m_cursorBoxes = {}; + CodeFoldKeys m_codeFoldKeys = {}; + CodeFolds m_codeFolds = {}; + CodeFoldKeyMap m_codeFoldKeyMap = {}; + CodeFoldKeyMap m_codeFoldValueMap = {}; + CodeFoldKeyLineMap m_codeFoldKeyLineMap = {}; + CodeFoldKeyLineMap m_codeFoldValueLineMap = {}; + CodeFoldDelimiters m_codeFoldDelimiters = {}; + Range m_codeFoldHighlighted = {}; + CodeFoldState m_codeFoldState = {}; + bool m_codeFoldsDisabled = false; + std::map m_rowToLineIndex = {}; + std::map m_lineIndexToRow = {}; + ImVec2 m_cursorScreenPosition = {}; + ImVec2 m_lineNumbersStartPos = {}; + LineIndexToScreen m_lineIndexToScreen = {}; + MultiLinesToRow m_multiLinesToRow = {}; + RowCodeFoldTooltips m_rowCodeFoldTooltips = {}; + Range m_interactiveSelection = {}; + std::vector m_clickableText; + float m_topRow = 0.0F; + bool m_setTopRow = false; + bool m_restoreSavedFolds = true; + ImVec2 m_charAdvance = {}; + float m_leftMargin = 0.0F; + float m_topMargin = 0.0F; + float m_lineNumberFieldWidth = 0.0F; + bool m_textChanged = false; + LanguageDefinition m_languageDefinition = {}; + RegexList m_regexList; + float m_numberOfLinesDisplayed = 0; + bool m_withinRender = false; + bool m_initializedCodeFolds = false; + bool m_ignoreImGuiChild = false; + std::string m_title; + bool m_unfoldIfNeeded = false; + bool m_scrollToCursor = false; + Coordinates m_focusAtCoords = {}; + bool m_updateFocus = false; + float m_oldTopMargin = 0.0F; + float m_scrollYIncrement = 0.0F; + bool m_setScrollY = false; + bool m_breakPointsChanged = false; + bool m_readOnly = false; + u64 m_startTime = 0; + bool m_codeFoldStateLoaded = false; + bool m_saveCodeFoldStateRequested = false; + bool m_useSavedFoldStatesRequested = false; + bool m_foldsAreInstalled = false; + std::vector m_tokens; + TokenIter m_curr; + TokenIter m_startToken, m_originalPosition, m_partOriginalPosition; + bool m_interrupt = false; + std::vector m_firstTokenIdOfLine; + CodeFoldBlocks m_foldPoints; + std::set m_globalBlocks; + }; + + private: // Rendering - ImVec2 underwaves(ImVec2 pos, u32 nChars, ImColor color = ImGui::GetStyleColorVec4(ImGuiCol_Text), const ImVec2 &size_arg = ImVec2(0, 0)); + ImVec2 underWavesAt(ImVec2 pos, i32 nChars, ImColor color = ImGui::GetStyleColorVec4(ImGuiCol_Text), const ImVec2 &size_arg = ImVec2(0, 0)); + void renderText(const ImVec2 &textEditorSize); + public: + TextEditor::Coordinates nextCoordinate(TextEditor::Coordinates coordinate); + bool testfoldMaps(TextEditor::Range toTest); void setTabSize(i32 value); float getPageSize() const; - bool isEndOfLine(const Coordinates &coordinates) const; - bool isEndOfFile(const Coordinates &coordinates) const; - bool isEndOfLine() const; - bool isStartOfLine() const; - void setTopLine(); void render(const char *title, const ImVec2 &size = ImVec2(), bool border = false); - + float getTopLineNumber(); + float getMaxLineNumber(); void setShowCursor(bool value) { m_showCursor = value; } void setShowLineNumbers(bool value) { m_showLineNumbers = value; } void setShowWhitespaces(bool value) { m_showWhitespaces = value; } bool isShowingWhitespaces() const { return m_showWhitespaces; } i32 getTabSize() const { return m_tabSize; } - ImVec2 &getCharAdvance() { return m_charAdvance; } - void clearGotoBoxes() { m_errorGotoBoxes.clear(); } - void clearCursorBoxes() { m_cursorBoxes.clear(); } - void addClickableText(std::string text) { m_clickableText.emplace_back(std::move(text)); } - void setErrorMarkers(const ErrorMarkers &markers) { m_errorMarkers = markers; } - Breakpoints &getBreakpoints() { return m_breakpoints; } - void setBreakpoints(const Breakpoints &markers) { m_breakpoints = markers; } + ImVec2 &getCharAdvance() { return m_lines.getCharAdvance(); } + void addClickableText(const std::string &text) {m_lines.addClickableText(text); } + void setErrorMarkers(const ErrorMarkers &markers) { m_lines.m_errorMarkers = markers; } + Breakpoints &getBreakpoints() { return m_lines.getBreakpoints(); } + void setBreakpoints(const Breakpoints &markers) { m_lines.m_breakpoints = markers; } void setLongestLineLength(u64 line) { m_longestLineLength = line; } u64 getLongestLineLength() const { return m_longestLineLength; } void setTopMarginChanged(i32 newMargin); - void setFocusAtCoords(const Coordinates &coords, bool ensureVisible = false); + ImVec2 coordsToScreen(Coordinates coordinates); + bool isBreakpointsChanged() { return m_lines.isBreakpointsChanged(); } + void clearBreakpointsChanged() { m_lines.clearBreakpointsChanged(); } + float screenPosToRow(const ImVec2 &position) const; + float rowToLineIndex(i32 row); + float lineIndexToRow(i32 lineNumber); void clearErrorMarkers(); - void clearActionables(); - private: - void ensureCursorVisible(); - void resetCursorBlinkTime(); - void renderText(const char *title, const ImVec2 &lineNumbersStartPos, const ImVec2 &textEditorSize); - void setFocus(); - void preRender(); - void drawSelection(float lineNo); - void drawLineNumbers(ImVec2 position, float lineNo, const ImVec2 &contentSize, bool focused, ImDrawList *drawList); - void renderCursor(float lineNo, ImDrawList *drawList); - void renderGotoButtons(float lineNo); - void drawText(Coordinates &lineStart, u64 i, u32 tokenLength, char color); - void postRender(const char *title, ImVec2 position, float lineNo); - ImVec2 calculateCharAdvance() const; - float textDistanceToLineStart(const Coordinates &from); + void clearActionables() {return m_lines.clearActionables();} + void saveCodeFoldStates(); + void applyCodeFoldStates(); + void readHiddenLines() { m_lines.readHiddenLines(); }; + void writeHiddenLines() { m_lines.writeHiddenLines(); }; + // Highlighting + private: + void preRender(); + void drawSelection(float row, ImDrawList *drawList); + void renderBottomHorizontal(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color); + void renderTopHorizontal(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color); + void renderPointingDown(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color); + void renderPointingUp(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color); + void renderVerticals(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color); + void renderSquare(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color); + void renderMinus(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color); + void renderPlus(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color); + void renderCodeFolds(i32 row, ImDrawList *drawList, i32 color, Lines::FoldSymbol state); + void drawLineNumbers(float lineNumber); + void drawBreakpoints(float lineIndex, const ImVec2 &contentSize, ImDrawList *drawList, std::string title); + void drawCodeFolds(float lineIndex, ImDrawList *drawList); + void drawCursor(float lineIndex, const ImVec2 &contentSize, bool focused, ImDrawList *drawList); + void drawButtons(float lineNumber); + void drawText(Coordinates &lineStart, u32 tokenLength, char color); + i64 drawColoredText(i32 lineIndex, const ImVec2 &textEditorSize); + void postRender(float lineNumber, std::string textWindowName); + ImVec2 calculateCharAdvance() const; + void openCodeFoldAt(Coordinates line); public: - void colorize(); - void setLanguageDefinition(const LanguageDefinition &aLanguageDef); static const Palette &getPalette(); static void setPalette(const Palette &value); static const Palette &getDarkPalette(); static const Palette &getLightPalette(); static const Palette &getRetroBluePalette(); - bool isColorizerEnabled() const { return m_colorizerEnabled; } - const LanguageDefinition &getLanguageDefinition() const { return m_languageDefinition; } void setNeedsUpdate(i32 line, bool needsUpdate); void setColorizedLine(i64 line, const std::string &tokens); - private: - void colorizeRange(); - void colorizeInternal(); + //Editing + private: + void enterCharacter(ImWchar character, bool shift); public: void deleteWordLeft(); void deleteWordRight(); @@ -505,34 +925,35 @@ namespace hex::ui { void paste(); void doPaste(const char *clipText); void deleteChar(); - void insertText(const std::string &value); - void insertText(const char *value); + void setReadOnly(bool value) { m_lines.setReadOnly(value); }; void appendLine(const std::string &value); void setOverwrite(bool value) { m_overwrite = value; } bool isOverwrite() const { return m_overwrite; } void setText(const std::string &text, bool undo = false); - std::string getText(); + void setImGuiChildIgnored(bool value) { m_lines.setImGuiChildIgnored(value); } std::vector getTextLines() const; - std::string getSelectedText(); + void setLanguageDefinition(const LanguageDefinition &aLanguageDef) { m_lines.setLanguageDefinition(aLanguageDef); } std::string getLineText(i32 line); - void setTextChanged(bool value) { m_textChanged = value; } - bool isTextChanged() { return m_textChanged; } - void setReadOnly(bool value) { m_readOnly = value; } + void setTextChanged(bool value) { m_lines.setTextChanged(value); } + std::string getText(bool addHiddenLines = false) { return m_lines.getText(addHiddenLines); } + void addUndo(std::vector value) { m_lines.addUndo(value); } + bool isTextChanged() { return m_lines.isTextChanged(); } void setHandleMouseInputs(bool value) { m_handleMouseInputs = value; } - bool isHandleMouseInputsEnabled() const { return m_handleMouseInputs; } + bool isHandleMouseInputsEnabled() const { return m_handleKeyboardInputs; } void setHandleKeyboardInputs(bool value) { m_handleKeyboardInputs = value; } bool isHandleKeyboardInputsEnabled() const { return m_handleKeyboardInputs; } - private: - std::string getText(const Range &from); - void deleteRange(const Range &rangeToDelete); - i32 insertTextAt(Coordinates &where, const std::string &value); - void removeLine(i32 start, i32 end); - void removeLine(i32 index); - Line &insertLine(i32 index); - void insertLine(i32 index, const std::string &text); - void enterCharacter(ImWchar character, bool shift); - void deleteSelection(); + Lines &getLines() { return m_lines; } + const Lines &getLines() const { return m_lines; } // Navigating + private: + Coordinates lineCoordinates(const Coordinates &value); + Coordinates lineCoordinates(i32 lineIndex, i32 column); + Range lineCoordinates(const Range &value); + void advance(Coordinates &coordinates); + Coordinates findWordStart(const Coordinates &from); + Coordinates findWordEnd(const Coordinates &from); + + public: void jumpToLine(i32 line = -1); void jumpToCoords(const Coordinates &coords); @@ -544,133 +965,91 @@ namespace hex::ui { void moveBottom(bool select = false); void moveHome(bool select = false); void moveEnd(bool select = false); - void moveToMatchedBracket(bool select = false); - void setScrollY(); + void moveToMatchedDelimiter(bool select = false); + void setCursorPosition(const Coordinates &position, bool unfoldIfNeeded = true, bool scrollToCursor = true) { + return m_lines.setCursorPosition(position, unfoldIfNeeded, scrollToCursor); + }; void setScroll(ImVec2 scroll); - ImVec2 getScroll() const { return m_scroll; } - Coordinates getCursorPosition() { return setCoordinates(m_state.m_cursorPosition); } - void setCursorPosition(const Coordinates &position, bool scrollToCursor = true); - void setCursorPosition(); - private: - Coordinates setCoordinates(const Coordinates &value); - Coordinates setCoordinates(i32 line, i32 column); - Range setCoordinates(const Range &value); - void advance(Coordinates &coordinates) const; - Coordinates findWordStart(const Coordinates &from); - Coordinates findWordEnd(const Coordinates &from); - Coordinates findPreviousWord(const Coordinates &from); - Coordinates findNextWord(const Coordinates &from); - u32 skipSpaces(const Coordinates &from); + ImVec2 getScroll() const {return m_scroll;} + Coordinates getCursorPosition() { return m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition); } + //Support + private: + void handleKeyboardInputs(); + void handleMouseInputs(); public: void setSelection(const Range &selection); Range getSelection() const; void selectWordUnderCursor(); void selectAll(); - bool hasSelection() const; - void refreshSearchResults(); - i32 getTotalLines() const { return (i32) m_lines.size(); } - FindReplaceHandler *getFindReplaceHandler() { return &m_findReplaceHandler; } + bool hasSelection() { return m_lines.hasSelection(); } + std::string getSelectedText() { return m_lines.getSelectedText(); } + u32 getfirstNonWhite(u32 lineIndex); + i32 getTotalLines() const; + FindReplaceHandler *getFindReplaceHandler() { return m_lines.getFindReplaceHandler(); } void setSourceCodeEditor(TextEditor *editor) { m_sourceCodeEditor = editor; } - void clearBreakpointsChanged() { m_breakPointsChanged = false; } - bool isBreakpointsChanged() { return m_breakPointsChanged; } - void setImGuiChildIgnored(bool value) { m_ignoreImGuiChild = value; } - bool isImGuiChildIgnored() const { return m_ignoreImGuiChild; } bool raiseContextMenu() { return m_raiseContextMenu; } void clearRaiseContextMenu() { m_raiseContextMenu = false; } TextEditor *getSourceCodeEditor(); - bool isEmpty() const; - void addUndo(UndoRecords &value); - private: - TextEditor::PaletteIndex getColorIndexFromFlags(Line::Flags flags); - void handleKeyboardInputs(); - void handleMouseInputs(); + void codeFoldExpand(i32 level=1, bool recursive=false, bool all=false); + void codeFoldCollapse(i32 level=1, bool recursive=false, bool all=false); + i32 getCodeFoldLevel(i32 line) const; + void resetFoldedSelections(); + void computeLPSArray(const std::string &pattern, std::vector & lps); + std::vector KMPSearch(const std::string& text, const std::string& pattern); + bool isEmpty(); + // utf8 + private: + Coordinates screenPosCoordinates(const ImVec2 &position); + i32 lineIndexColumn(i32 lineNumber, i32 stringIndex); public: 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 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 lineIndexColumn(i32 lineIndex, i32 stringIndex); - u64 getLineByteCount(i32 line) const; - - public: - FindReplaceHandler m_findReplaceHandler; + Coordinates lineCoordsToIndexCoords(const Coordinates &coordinates); private: float m_lineSpacing = 1.0F; - Lines m_lines; - EditorState m_state; - UndoBuffer m_undoBuffer; - i32 m_undoIndex = 0; - bool m_scrollToBottom = false; - float m_topMargin = 0.0F; + Lines m_lines = {}; float m_newTopMargin = 0.0F; - float m_oldTopMargin = 0.0F; bool m_topMarginChanged = false; - i32 m_tabSize = 4; bool m_overwrite = false; - bool m_readOnly = false; - bool m_withinRender = false; - bool m_scrollToCursor = false; - bool m_scrollToTop = false; - bool m_textChanged = false; - bool m_colorizerEnabled = true; - float m_lineNumberFieldWidth = 0.0F; + u64 m_longestDrawnLineLength = 0; + float m_topLineNumber = 0.0F; + bool m_showWhitespaces = true; u64 m_longestLineLength = 0; - float m_leftMargin = 10.0; - float m_topLine = 0.0F; - bool m_setTopLine = false; - bool m_breakPointsChanged = false; bool m_handleKeyboardInputs = true; bool m_handleMouseInputs = true; - bool m_ignoreImGuiChild = false; - bool m_showWhitespaces = true; + bool m_drawMatchedBracket = false; - MatchedBracket m_matchedBracket; - Palette m_palette = {}; - LanguageDefinition m_languageDefinition; - RegexList m_regexList; - bool m_updateFlags = true; - Breakpoints m_breakpoints; - ErrorMarkers m_errorMarkers; - ErrorHoverBoxes m_errorHoverBoxes; - ErrorGotoBoxes m_errorGotoBoxes; - CursorBoxes m_cursorBoxes; - ImVec2 m_charAdvance; - Range m_interactiveSelection; - u64 m_startTime = 0; - std::vector m_defines; TextEditor *m_sourceCodeEditor = nullptr; float m_shiftedScrollY = 0; - ImVec2 m_scroll=ImVec2(0, 0); - float m_scrollYIncrement = 0.0F; bool m_setScroll = false; - bool m_setScrollY = false; - float m_numberOfLinesDisplayed = 0; + ImVec2 m_scroll = {}; + float m_scrollOffset = 0; + float m_maxScroll =0; + bool m_scrollFromLines = false; + bool m_newMouseWheel = false; float m_lastClick = -1.0F; bool m_showCursor = true; bool m_showLineNumbers = true; bool m_raiseContextMenu = false; - Coordinates m_focusAtCoords; - bool m_updateFocus = false; - std::vector m_clickableText; constexpr static char inComment = 7; + inline static Palette m_palette = {}; + inline static Line Ellipsis = Line({'.','.','.'},{(i32)TextEditor::PaletteIndex::Operator,(i32)TextEditor::PaletteIndex::Operator,(i32)TextEditor::PaletteIndex::Operator},{0,0,0}); inline static const Line m_emptyLine = Line(); + inline static const std::string s_delimiters = "()[]{}<>"; + inline static const std::string s_separators = "()[]{}"; + inline static const std::string s_operators = "<>"; inline static const Coordinates Invalid = Coordinates(0x80000000, 0x80000000); - static const i32 s_cursorBlinkInterval; - static const i32 s_cursorBlinkOnTime; - static ImVec2 s_cursorScreenPosition; + inline static const Interval NotValid = Interval(0x80000000, 0x80000000); + inline static const Range NoCodeFoldSelected = Range(Invalid, Invalid); + inline static const i32 s_cursorBlinkInterval = 1200; + inline static const i32 s_cursorBlinkOnTime = 800; }; bool tokenizeCStyleString(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end); diff --git a/plugins/ui/source/ui/text_editor/codeFolder.cpp b/plugins/ui/source/ui/text_editor/codeFolder.cpp new file mode 100644 index 000000000..057424e23 --- /dev/null +++ b/plugins/ui/source/ui/text_editor/codeFolder.cpp @@ -0,0 +1,919 @@ +#include +#include +#include +#include +#include +#include + +namespace hex::ui { + using namespace pl::core; + using Interval = TextEditor::Interval; + using Token = pl::core::Token; + using Coordinates = TextEditor::Coordinates; + +/* + std::pair TextEditor::convertIndexToLineNumbers(Interval interval) { + if (m_tokens[interval.m_start].location.source->mainSource && m_tokens[interval.m_end].location.source->mainSource) + return std::make_pair(m_tokens[interval.m_start].location.line, m_tokens[interval.m_end].location.line); + return std::make_pair(0, 0); + } +*/ + void TextEditor::Lines::skipAttribute() { + + if (sequence(tkn::Separator::LeftBracket, tkn::Separator::LeftBracket)) { + while (!sequence(tkn::Separator::RightBracket, tkn::Separator::RightBracket)) + next(); + } + } + + std::vector TextEditor::Lines::searchRangeForBlocks(Interval interval) { + m_curr = m_startToken + interval.m_start; + std::vector result; + + u32 nestedLevel = 0; + std::vector tokenStack; + while (m_curr != m_startToken + interval.m_end) { + + if (sequence(tkn::Separator::LeftBrace)) { + auto tokenId = getTokenId(m_curr[-1].location); + tokenStack.push_back(tokenId); + nestedLevel++; + } else if (sequence(tkn::Separator::RightBrace)) { + nestedLevel--; + + if (tokenStack.empty()) + return result; + Interval range(tokenStack.back(), getTokenId(m_curr[-1].location)); + tokenStack.pop_back(); + + result.push_back(range); + + if (nestedLevel == 0) { + skipAttribute(); + break; + } + + } else if (peek(tkn::Separator::EndOfProgram)) + return result; + else + next(); + } + return result; + } + + Interval TextEditor::Lines::findBlockInRange(Interval interval) { + Interval result = NotValid; + auto tokenStart = TokenIter(m_tokens.begin(), m_tokens.end()); + + bool foundKey = false; + bool foundComment = false; + m_curr = tokenStart + interval.m_start; + while (interval.m_end >= getTokenId(m_curr->location)) { + if (peek(tkn::Separator::EndOfProgram)) + return NotValid; + if (result.m_start = getTokenId(m_curr->location); result.m_start < 0) + return NotValid; + + while (true) { + if (const auto *docComment = const_cast(getValue(0)); docComment != nullptr) { + if (foundKey) + break; + if (docComment->singleLine) { + foundComment = true; + next(); + } else { + if (foundComment) + break; + return {result.m_start, result.m_start}; + } + } else if (const auto *comment = const_cast(getValue(0)); comment != nullptr) { + if (foundKey) + break; + if (comment->singleLine) { + foundComment = true; + next(); + } else { + if (foundComment) + break; + return {result.m_start, result.m_start}; + } + } else if (const auto *keyword = const_cast(getValue(0));keyword != nullptr && *keyword == Token::Keyword::Import) { + if (foundComment) + break; + foundKey = true; + while (!peek(tkn::Separator::Semicolon) && !peek(tkn::Separator::EndOfProgram)) + next(); + next(); + } else if (const auto *directive = const_cast(getValue(0));directive != nullptr && *directive == Token::Directive::Include) { + if (foundComment) + break; + foundKey = true; + u32 line = m_curr->location.line; + while (m_curr->location.line == line && !peek(tkn::Separator::EndOfProgram)) + next(); + } else + break; + } + if (foundKey || foundComment) { + auto currentId = getTokenId(m_curr->location); + if (peek(tkn::Separator::EndOfProgram) || (currentId > 0 && currentId < (i32) m_tokens.size())) { + next(-1); + if (result.m_end = getTokenId(m_curr->location); result.m_end < 0) + return NotValid; + + return result; + } else + return NotValid; + } + next(); + } + return NotValid; + } + + Coordinates TextEditor::Lines::findCommentEndCoord(i32 tokenId) { + Coordinates result = Invalid; + auto save = m_curr; + m_curr = TokenIter(m_tokens.begin(), m_tokens.end()) + tokenId; + if (peek(tkn::Literal::Comment)) { + auto comment = getValue(0); + if (comment != nullptr) { + auto location = m_curr->location; + if (comment->singleLine) + return Coordinates(location.line - 1, location.column + location.length - 2); + + std::string commentText = comment->comment; + auto vectorString = wolv::util::splitString(commentText, "\n"); + size_t lineCount = vectorString.size(); + auto endColumn = (lineCount == 1) ? location.column + location.length - 1 : vectorString.back().length() + 1; + return Coordinates(location.line + lineCount - 2, endColumn); + } + + } else if (peek(tkn::Literal::DocComment)) { + auto docComment = getValue(0); + if (docComment != nullptr) { + auto location = m_curr->location; + if (docComment->singleLine) + return Coordinates(location.line - 1, location.column + location.length - 2); + + std::string commentText = docComment->comment; + auto vectorString = wolv::util::splitString(commentText, "\n"); + size_t lineCount = vectorString.size(); + auto endColumn = (lineCount == 1) ? location.column + location.length - 1 : vectorString.back().length() + 1; + return Coordinates(location.line + lineCount - 2, endColumn); + } + } + m_curr = save; + return result; + } + + std::set TextEditor::Lines::blocksFromGlobal() { + std::set result; + if (m_globalBlocks.size() == 1) + return m_globalBlocks; + auto globalsIter = m_globalBlocks.begin(); + bool absorbPreviousToken = false; + while (globalsIter != m_globalBlocks.end()) { + if (absorbPreviousToken && globalsIter->m_start > 0) { + result.insert(Interval(globalsIter->m_start - 1, globalsIter->m_end)); + absorbPreviousToken = false; + } else if (globalsIter->m_start == globalsIter->m_end) { + absorbPreviousToken = true; + } else + result.insert(*globalsIter); + auto nextIter = std::next(globalsIter); + + if (nextIter != m_globalBlocks.end() && absorbPreviousToken) { + result.insert(Interval(globalsIter->m_end, nextIter->m_start - 1)); + absorbPreviousToken = false; + } else if (nextIter != m_globalBlocks.end() && globalsIter->m_end + 1 < nextIter->m_start - 1) + result.insert(Interval(globalsIter->m_end + 1, nextIter->m_start - 1)); + else if (nextIter != m_globalBlocks.end() && globalsIter->m_end + 1 == nextIter->m_start - 1) + absorbPreviousToken = true; + else if (globalsIter->m_end + 1 < (i32) m_tokens.size() - 1) + result.insert(Interval(globalsIter->m_end + 1, (i32) m_tokens.size() - 1)); + + globalsIter++; + } + return result; + } + +//comments imports and includes + void TextEditor::Lines::nonDelimitedFolds() { + //auto allBlocks = blocksFromGlobal(); + auto size = m_tokens.size(); + if (size > 0) { + Interval block = {0,static_cast(size-1)}; + //for (auto block: allBlocks) { + while (true) { + auto interval = findBlockInRange(block); + + if (interval == NotValid) + break; + + Coordinates endCoord, startCoord = Coordinates(m_tokens[interval.m_start].location); + + if (interval.m_end == interval.m_start) + endCoord = findCommentEndCoord(interval.m_start); + else + endCoord = Coordinates(m_tokens[interval.m_end].location); + + if (startCoord.getLine() != endCoord.getLine()) + m_foldPoints[startCoord] = endCoord; + + if (interval.m_end >= block.m_end) + break; + block.m_start = interval.m_end + 1; + } + } + } + + std::pair TextEditor::Lines::getDelimiterLineNumbers(i32 start, i32 end, const std::string &delimiters) { + std::pair result = {Invalid, Invalid}; + Coordinates first = Invalid; + auto tokenStart = TokenIter(m_tokens.begin(), m_tokens.end()); + m_curr = tokenStart + start; + Token::Separator openSeparator = Token::Separator::EndOfProgram; + Token::Separator closeSeparator = Token::Separator::EndOfProgram; + Token::Operator openOperator, closeOperator; + if (delimiters == "{}") { + openSeparator = Token::Separator::LeftBrace; + closeSeparator = Token::Separator::RightBrace; + } else if (delimiters == "[]") { + openSeparator = Token::Separator::LeftBracket; + closeSeparator = Token::Separator::RightBracket; + } else if (delimiters == "()") { + openSeparator = Token::Separator::LeftParenthesis; + closeSeparator = Token::Separator::RightParenthesis; + } else if (delimiters == "<>") { + openOperator = Token::Operator::BoolLessThan; + closeOperator = Token::Operator::BoolGreaterThan; + } + + Token::Separator *separator; + if (separator = const_cast(getValue(0)); separator == nullptr || *separator != openSeparator) { + if (const auto *opener = const_cast(getValue(0)); opener == nullptr || *opener != openOperator) + return result; + } + if (!m_curr->location.source->mainSource) + return result; + if (start > 0) { + Location location1 = m_curr->location; + Location location2; + auto save = m_curr; + while (peek(tkn::Literal::Comment, -1) || peek(tkn::Literal::DocComment, -1)) { + if (getTokenId(m_curr->location) == 0) + break; + next(-1); + } + next(-1); + if (separator != nullptr && *separator == Token::Separator::LeftParenthesis) { + if (const auto *separator2 = const_cast(getValue(0)); separator2 != nullptr && (*separator2 == Token::Separator::Semicolon || *separator2 == Token::Separator::LeftBrace || *separator2 == Token::Separator::RightBrace)) { + m_curr = save; + location2 = m_curr->location; + } else { + location2 = m_curr->location; + m_curr = save; + } + } else { + location2 = m_curr->location; + m_curr = save; + } + if (location1.line != location2.line) { + Coordinates coord(location2); + first = coord + Coordinates(0, location2.length); + } else + first = Coordinates(location1); + } else + first = Coordinates(m_curr->location); + m_curr = tokenStart + end; + if (separator = const_cast(getValue(0)); separator == nullptr || *separator != closeSeparator) { + if (const auto *closer = const_cast(getValue(0)); closer == nullptr || *closer != closeOperator) { + if (const auto *separator2 = const_cast(getValue(1)); separator2 != nullptr && *separator2 == closeSeparator) { + next(); + } else if (const auto *closer2 = const_cast(getValue(1)); closer2 != nullptr && *closer2 == closeOperator) { + next(); + } else + return result; + } + } + if (!m_curr->location.source->mainSource) + return result; + + result.first = first; + result.second = Coordinates(m_curr->location); + return result; + } + + void TextEditor::Lines::advanceToNextLine(i32 &lineIndex, i32 ¤tTokenId, Location &location) { + if (lineIndex = nextLine(lineIndex); lineIndex >= size()) + return; + currentTokenId = m_firstTokenIdOfLine[lineIndex]; + m_curr = m_startToken + currentTokenId; + location = m_curr->location; + } + + void TextEditor::Lines::advanceTokenId(i32 &lineIndex, i32 ¤tTokenId, Location &location) { + currentTokenId++; + m_curr = m_startToken + currentTokenId; + location = m_curr->location; + lineIndex = location.line - 1; + } + + void TextEditor::Lines::moveToLocationColumn(i32 locationColumn, i32 ¤tTokenId, Location &location) { + location.column = locationColumn; + location.length = 1; + if (currentTokenId = getTokenId(location); currentTokenId < 0) + return; + m_curr = m_startToken + currentTokenId; + } + + void TextEditor::Lines::resetToTokenId(i32 &lineIndex, i32 ¤tTokenId, Location &location) { + m_curr = m_startToken + currentTokenId; + location = m_curr->location; + lineIndex = location.line - 1; + } + + + TextEditor::CodeFoldBlocks TextEditor::Lines::foldPointsFromSource() { + loadFirstTokenIdOfLine(); + if (m_firstTokenIdOfLine.empty()) + return m_foldPoints; + m_foldPoints.clear(); + nonDelimitedFolds(); + std::string blockDelimiters = "{[(<"; + size_t topLine = 0; + i32 bottomLine = size(); + m_startToken = TokenIter(m_tokens.begin(), m_tokens.end()); + m_curr = m_startToken; + auto location = m_curr->location; + i32 lineIndex = topLine; + i32 currentTokenId = 0; + while (lineIndex < bottomLine) { + auto line = operator[](lineIndex); + if (line.empty()) { + advanceToNextLine(lineIndex, currentTokenId, location); + if (lineIndex >= bottomLine) { + return m_foldPoints; + } + continue; + } + + if (size_t columnIndex = line.m_chars.find_first_of(blockDelimiters, location.column - 1); columnIndex != std::string::npos) { + std::string openDelimiter = std::string(1, line[columnIndex]); + moveToLocationColumn(columnIndex + 1, currentTokenId, location); + if (currentTokenId < 0) { + return m_foldPoints; + } + + if (m_curr[0].getFormattedType() != "Operator" && m_curr[0].getFormattedType() != "Separator") { + if (currentTokenId >= (i32) m_tokens.size()) { + return m_foldPoints; + } + advanceTokenId(lineIndex, currentTokenId, location); + continue; + } + + if (auto idx = blockDelimiters.find(openDelimiter); idx != std::string::npos) { + if (idx == 3) { + if (currentTokenId == 0) { + return m_foldPoints; + } + next(-1); + if (const auto *identifier = const_cast(getValue(0)); identifier == nullptr || identifier->getType() != Token::Identifier::IdentifierType::UDT) { + next(2); + if (peek(tkn::Separator::EndOfProgram, -1)) { + return m_foldPoints; + } + + if (currentTokenId = getTokenId(m_curr->location); currentTokenId < 0) { + return m_foldPoints; + } + resetToTokenId(lineIndex, currentTokenId, location); + continue; + } + } + auto start = currentTokenId; + auto end = findMatchingDelimiter(currentTokenId); + if (end.first < 0) { + return m_foldPoints; + } + std::string value = openDelimiter + end.second; + auto lineBased = getDelimiterLineNumbers(start, end.first, value); + + if (lineBased.first.getLine() != lineBased.second.getLine()) + m_foldPoints[lineBased.first] = lineBased.second; + + if (currentTokenId = getTokenId(m_tokens[end.first].location); currentTokenId < 0 || currentTokenId >= (i32) m_tokens.size()) { + return m_foldPoints; + } + advanceTokenId(lineIndex, currentTokenId, location); + } else { + return m_foldPoints; + } + } else { + advanceToNextLine(lineIndex, currentTokenId, location); + if (lineIndex >= bottomLine) { + return m_foldPoints; + } + } + } + return m_foldPoints; + } + + + std::pair TextEditor::Lines::findMatchingDelimiter(i32 from) { + std::string blockDelimiters = "{}[]()<>"; + std::pair result = std::make_pair(-1, '\0'); + auto tokenStart = TokenIter(m_tokens.begin(), m_tokens.end()); + if (from >= (i32) m_tokens.size()) + return result; + m_curr = tokenStart + from; + Location location = m_curr->location; + auto line = operator[](location.line - 1); + std::string openDelimiter; + if (auto columnIndex = line.m_chars.find_first_of(blockDelimiters, location.column - 1); columnIndex != std::string::npos) + openDelimiter = line[columnIndex]; + else + return result; + + i32 currentTokenId = from + 1; + std::string closeDelimiter; + if (size_t idx = blockDelimiters.find(openDelimiter); idx != std::string::npos && currentTokenId < (i32) m_tokens.size()) + closeDelimiter = blockDelimiters[idx + 1]; + else + return result; + m_curr = tokenStart + currentTokenId; + location = m_curr->location; + size_t lineIndex = location.line - 1; + size_t bottomLine = size(); + while (lineIndex < bottomLine) { + line = operator[](lineIndex); + if (line.empty()) { + + if (lineIndex = nextLine(lineIndex); lineIndex >= bottomLine) + return result; + currentTokenId = m_firstTokenIdOfLine[lineIndex]; + m_curr = tokenStart + currentTokenId; + location = m_curr->location; + continue; + } + + if (auto columnIndex = line.m_chars.find_first_of(blockDelimiters, location.column - 1); columnIndex != std::string::npos) { + std::string currentChar = std::string(1, line[columnIndex]); + location.column = columnIndex + 1; + location.length = 1; + + if (currentTokenId = getTokenId(location); currentTokenId < 0) + return result; + m_curr = tokenStart + currentTokenId; + if (m_curr[0].getFormattedType() != "Operator" && m_curr[0].getFormattedType() != "Separator") { + + if (currentTokenId >= (i32) m_tokens.size()) + return result; + currentTokenId++; + m_curr = tokenStart + currentTokenId; + location = m_curr->location; + lineIndex = location.line - 1; + continue; + } + + if (auto idx = blockDelimiters.find(currentChar); idx != std::string::npos) { + if (currentChar == closeDelimiter) { + + if (currentTokenId = getTokenId(location); currentTokenId < 0) + return result; + return std::make_pair(currentTokenId, closeDelimiter[0]); + } else { + if (idx == 6 || idx == 7) { + next(-1); + if (const auto *identifier = const_cast(getValue(0)); + ((idx == 6) && (identifier == nullptr || identifier->getType() != Token::Identifier::IdentifierType::UDT)) || (idx == 7)) { + next(2); + if (peek(tkn::Separator::EndOfProgram, -1)) { + return result; + } + + if (currentTokenId = getTokenId(m_curr->location); currentTokenId < 0) + return result; + m_curr = tokenStart + currentTokenId; + location = m_curr->location; + lineIndex = location.line - 1; + continue; + } + } + if (idx % 2 == 0) { + auto start = currentTokenId; + auto end = findMatchingDelimiter(currentTokenId); + if (end.first < 0) + return result; + std::string value = currentChar + end.second; + auto lineBased = getDelimiterLineNumbers(start, end.first, value); + if (lineBased.first.getLine() != lineBased.second.getLine()) + m_foldPoints[lineBased.first] = lineBased.second; + + if (currentTokenId = getTokenId(m_tokens[end.first].location); currentTokenId < 0 || currentTokenId >= (i32) m_tokens.size()) + return result; + + currentTokenId++; + m_curr = tokenStart + currentTokenId; + location = m_curr->location; + lineIndex = location.line - 1; + } else + return result; + } + } else { + return result; + } + } else { + + if (lineIndex = nextLine(lineIndex); lineIndex >= bottomLine) + return result; + currentTokenId = m_firstTokenIdOfLine[lineIndex]; + m_curr = tokenStart + currentTokenId; + location = m_curr->location; + } + } + return result; + } + void TextEditor::saveCodeFoldStates() { + m_lines.saveCodeFoldStates(); + } + + void TextEditor::Lines::saveCodeFoldStates() { + i32 codeFoldIndex = 0; + std::vector closedFoldIncrements; + for (auto key: m_codeFoldKeys) { + if (m_codeFoldState.contains(key) && !m_codeFoldState[key]) { + closedFoldIncrements.push_back(codeFoldIndex); + codeFoldIndex = 1; + } else + codeFoldIndex++; + } + std::string result = "//+-#:"; + for (u32 i = 0; i < closedFoldIncrements.size(); ++i) { + result += std::to_string(closedFoldIncrements[i]); + if (i < closedFoldIncrements.size() - 1) + result += ","; + } + auto lineIndex = 0; + HiddenLine hiddenLine(lineIndex, result); + if (!m_hiddenLines.empty()) { + m_hiddenLines[0] = hiddenLine; + return; + } + m_hiddenLines.push_back(hiddenLine); + } + + void TextEditor::applyCodeFoldStates() { + m_lines.applyCodeFoldStates(); + } + + void TextEditor::Lines::applyCodeFoldStates() { + + std::string commentLine; + for (auto line: m_hiddenLines) { + if (line.m_line.starts_with("//+-#:")) { + commentLine = line.m_line; + break; + } + } + if (commentLine.size() < 6 || !commentLine.starts_with("//+-#:")) + return; + auto states = commentLine.substr(6); + auto stringVector = wolv::util::splitString(states, ",", true); + auto count = stringVector.size(); + if (count == 1 && stringVector[0].empty()) + return; + std::vector closedFoldIncrements(count); + i32 value = 0; + for (u32 i = 0; i < count; ++i) { + auto stateStr = stringVector[i]; + std::from_chars(stateStr.data(), stateStr.data() + stateStr.size(), value); + closedFoldIncrements[i] = value; + } + m_codeFoldState.clear(); + auto codeFoldKeyIter = m_codeFoldKeys.begin(); + i32 closeFold = 0; + for (auto closedFoldIncrement: closedFoldIncrements) { + closeFold += closedFoldIncrement; + if (closeFold < 0 || closeFold >= (i32) m_codeFoldKeys.size()) + continue; + std::advance(codeFoldKeyIter, closedFoldIncrement); + m_codeFoldState[*codeFoldKeyIter] = false; + } + } + + void TextEditor::Lines::closeCodeFold(const Range &key, bool userTriggered) { + float topRow; + + if (userTriggered) + topRow = m_topRow; + LineIndexToScreen lineIndexToScreen; + bool needsDelimiter = lineNeedsDelimiter(key.m_start.m_line); + auto lineIter = m_lineIndexToScreen.begin(); + + while (lineIter != m_lineIndexToScreen.end() && lineIter->first <= key.m_start.m_line) { + lineIndexToScreen[lineIter->first] = lineIter->second; + lineIter++; + } + auto startingXScreenCoordinate = foldedCoordsToScreen(lineCoordinates(0, 0)).x; + float currentYScreenCoordinate = 0; + + if (needsDelimiter) { + auto screenCoordinates = lineIter->second; + auto &line = m_unfoldedLines[key.m_start.m_line]; + screenCoordinates.y = m_lineIndexToScreen[key.m_start.m_line].y; + TrimMode trimMode = TrimMode::TrimBoth; + auto row = lineIndexToRow(key.m_start.m_line); + + if (m_foldedLines.contains(row) && m_foldedLines[row].m_full.m_start.m_line == key.m_start.m_line) + trimMode = TrimMode::TrimEnd; + screenCoordinates.x = m_lineIndexToScreen[key.m_start.m_line].x + line.trim(trimMode).lineTextSize(); + lineIndexToScreen[lineIter->first] = screenCoordinates; + lineIter++; + } + while (lineIter != m_lineIndexToScreen.end()) { + + if (lineIter->first >= key.m_end.m_line) { + auto screenCoordinates = lineIter->second; + if (lineIter->first == key.m_end.m_line && m_codeFoldDelimiters.contains(key) && !s_delimiters.contains(m_codeFoldDelimiters[key].first)) { + lineIndexToScreen[lineIter->first] = ImVec2(-1, -1); + lineIter++; + continue; + } else if (lineIter->first == key.m_end.m_line) { + auto &line = m_unfoldedLines[key.m_start.m_line]; + screenCoordinates.y = m_lineIndexToScreen[key.m_start.m_line].y; + currentYScreenCoordinate = screenCoordinates.y; + TrimMode trim = TrimMode::TrimBoth; + auto row = lineIndexToRow(key.m_start.m_line); + + if (m_foldedLines.contains(row) && m_foldedLines[row].m_full.m_start.m_line == key.m_start.m_line) + trim = TrimMode::TrimEnd; + + screenCoordinates.x = m_lineIndexToScreen[key.m_start.m_line].x + line.trim(trim).lineTextSize() + Ellipsis.lineTextSize(); + + if (needsDelimiter) { + Line bracketLine("{"); + screenCoordinates.x += bracketLine.lineTextSize(); + } + } else if (screenCoordinates != ImVec2(-1, -1)) { + screenCoordinates.y = currentYScreenCoordinate; + if (screenCoordinates.x == startingXScreenCoordinate) + screenCoordinates.y += m_charAdvance.y; + currentYScreenCoordinate = screenCoordinates.y; + } + lineIndexToScreen[lineIter->first] = screenCoordinates; + } else { + lineIndexToScreen[lineIter->first] = ImVec2(-1, -1); + } + lineIter++; + } + m_lineIndexToScreen = std::move(lineIndexToScreen); + + if (m_codeFoldState.contains(key) && m_codeFoldState[key]) + m_codeFoldState[key] = false; + bool found = false; + FoldedLine currentFoldedLine; + for (auto [row, foldedLine]: m_foldedLines) { + + if (!foldedLine.m_keys.empty() && (key.m_start.m_line == foldedLine.m_keys.back().m_end.m_line || key.m_end.m_line == foldedLine.m_keys.front().m_start.m_line)) { + foldedLine.insertKey(key); + foldedLine.m_row = row; + m_foldedLines.erase(row); + m_foldedLines[row] = foldedLine; + found = true; + currentFoldedLine = m_foldedLines[row]; + break; + } else if (key.contains(foldedLine.m_full)) { + FoldedLine newFoldedLine(this); + newFoldedLine.insertKey(key); + m_foldedLines.erase(row); + m_foldedLines[newFoldedLine.m_row] = newFoldedLine; + found = true; + currentFoldedLine = m_foldedLines[newFoldedLine.m_row]; + } + } + + if (!found) { + FoldedLine newFoldedLine(this); + newFoldedLine.insertKey(key); + + if (m_foldedLines.contains(newFoldedLine.m_row)) { + const auto &foldedLine = m_foldedLines[newFoldedLine.m_row]; + + if (foldedLine.m_built) { + newFoldedLine.m_foldedLine = foldedLine.m_foldedLine; + newFoldedLine.m_ellipsisIndices = foldedLine.m_ellipsisIndices; + newFoldedLine.m_cursorPosition = foldedLine.m_cursorPosition; + newFoldedLine.m_built = true; + } + } + m_foldedLines[newFoldedLine.m_row] = newFoldedLine; + currentFoldedLine = m_foldedLines[newFoldedLine.m_row]; + } + std::map updatedFoldedLines; + for (auto &[row, foldedLine] : m_foldedLines) { + if (row > currentFoldedLine.m_row) { + foldedLine.m_row -= (key.m_end.m_line - key.m_start.m_line); + updatedFoldedLines[foldedLine.m_row] = foldedLine; + } else { + updatedFoldedLines[row] = foldedLine; + } + } + m_foldedLines = std::move(updatedFoldedLines); + if (userTriggered) { + auto topLine = lineCoordinates( rowToLineIndex(topRow), 0); + if (key.contains(topLine)) + m_topRow = lineIndexToRow(key.m_start.m_line); + else + m_topRow = topRow; + m_setTopRow = true; + m_saveCodeFoldStateRequested = true; + } + m_foldedLines[currentFoldedLine.m_row].loadSegments(); + } + + void TextEditor::Lines::openCodeFold(const Range &key) { + for (auto foldedLine : m_foldedLines) { + for (auto foldKey : foldedLine.second.m_keys) { + if (foldKey.contains(key) && foldKey != key) { + m_codeFoldState[key] = true; + return; + } + } + } + + LineIndexToScreen indicesToScreen; + auto lineIter = m_lineIndexToScreen.begin(); + while (lineIter != m_lineIndexToScreen.end() && lineIter->first < key.m_start.m_line) { + indicesToScreen[lineIter->first] = lineIter->second; + lineIter++; + } + + while (lineIter != m_lineIndexToScreen.end()) { + if (lineIter->first + 1 > key.m_end.m_line) { + auto sc = lineIter->second; + if (sc != ImVec2(-1, -1)) + sc.y += m_charAdvance.y * (key.m_end.m_line - key.m_start.m_line); + indicesToScreen[lineIter->first] = sc; + } else { + auto sc = ImVec2(m_cursorScreenPosition.x + m_leftMargin, m_lineIndexToScreen[key.m_start.m_line-1].y + m_charAdvance.y*(lineIter->first + 1 - key.m_start.m_line)); + indicesToScreen[lineIter->first] = sc; + } + lineIter++; + } + m_lineIndexToScreen = std::move(indicesToScreen); + if (m_codeFoldState.contains(key) && !m_codeFoldState[key]) + m_codeFoldState[key]=true; + i32 erasedRow = -1; + for (auto &[row, foldedLine] : m_foldedLines) { + if (std::any_of(foldedLine.m_keys.begin(), foldedLine.m_keys.end(), [key](const Range& currentKey) { return currentKey == key; })) { + foldedLine.removeKey(key); + if (foldedLine.m_keys.empty()) { + m_foldedLines.erase(row); + erasedRow = row; + } + break; + } + } + if (erasedRow != -1) { + std::map updatedFoldedLines; + for (auto &[row, foldedLine] : m_foldedLines) { + if (row > erasedRow) { + foldedLine.m_row += (key.m_end.m_line - key.m_start.m_line); + updatedFoldedLines[foldedLine.m_row] = foldedLine; + } else { + updatedFoldedLines[row] = foldedLine; + } + } + m_foldedLines = std::move(updatedFoldedLines); + } + m_saveCodeFoldStateRequested = true; + } + + void TextEditor::openCodeFoldAt(Coordinates line) { + for (auto fold : m_lines.m_codeFoldKeys) { + if (fold.contains(line) && m_lines.m_codeFoldState.contains(fold) && !m_lines.m_codeFoldState[fold]) { + m_lines.openCodeFold(fold); + if (m_lines.m_lineIndexToScreen[line.m_line] != ImVec2(-1.0f, -1.0f)) + return; + } + } + } + + template + T *TextEditor::Lines::getValue(const i32 index) { + return const_cast(std::get_if(&m_curr[index].value)); + } + + void TextEditor::Lines::next(i32 count) { + if (m_interrupt) { + m_interrupt = false; + throw std::out_of_range("Highlights were deliberately interrupted"); + } + if (count == 0) + return; + i32 id = getTokenId(m_curr->location); + + if (count > 0) + m_curr += std::min(count,static_cast(m_tokens.size() - id)); + else + m_curr += -std::min(-count,id); + + } + constexpr static u32 Normal = 0; + constexpr static u32 Not = 1; + + bool TextEditor::Lines::begin() { + m_originalPosition = m_curr; + + return true; + } + + void TextEditor::Lines::partBegin() { + m_partOriginalPosition = m_curr; + } + + void TextEditor::Lines::reset() { + m_curr = m_originalPosition; + } + + void TextEditor::Lines::partReset() { + m_curr = m_partOriginalPosition; + } + + bool TextEditor::Lines::resetIfFailed(const bool value) { + if (!value) reset(); + + return value; + } + + template + bool TextEditor::Lines::sequenceImpl() { + if constexpr (S == Normal) + return true; + else if constexpr (S == Not) + return false; + else + std::unreachable(); + } + + template + bool TextEditor::Lines::matchOne(const Token &token) { + if constexpr (S == Normal) { + if (!peek(token)) { + partReset(); + return false; + } + + next(); + return true; + } else if constexpr (S == Not) { + if (!peek(token)) + return true; + + next(); + partReset(); + return false; + } else + std::unreachable(); + } + + template + bool TextEditor::Lines::sequenceImpl(const auto &... args) { + return (matchOne(args) && ...); + } + + + template + bool TextEditor::Lines::sequence(const Token &token, const auto &... args) { + partBegin(); + return sequenceImpl(token, args...); + } + + + bool TextEditor::Lines::isValid() { + Token token; + try { + token = m_curr[0]; + } + catch (const std::out_of_range &e) { + auto t = e.what(); + if (t == nullptr) + return false; + return false; + } + if (!isLocationValid(token.location)) + return false; + return true; + } + + bool TextEditor::Lines::peek(const Token &token, const i32 index) { + if (!isValid()) + return false; + i32 id = getTokenId(m_curr->location); + if (id+index < 0 || id+index >= (i32)m_tokens.size()) + return false; + return m_curr[index].type == token.type && m_curr[index] == token.value; + } +} \ No newline at end of file diff --git a/plugins/ui/source/ui/text_editor/editor.cpp b/plugins/ui/source/ui/text_editor/editor.cpp index 4cbe5c02f..c7c4f1adc 100644 --- a/plugins/ui/source/ui/text_editor/editor.cpp +++ b/plugins/ui/source/ui/text_editor/editor.cpp @@ -1,63 +1,68 @@ -#include #include +#include #include #include #include -#include #include #include +#include #define IMGUI_DEFINE_MATH_OPERATORS #include "imgui.h" namespace hex::ui { - const i32 TextEditor::s_cursorBlinkInterval = 1200; - const i32 TextEditor::s_cursorBlinkOnTime = 800; - ImVec2 TextEditor::s_cursorScreenPosition; TextEditor::FindReplaceHandler::FindReplaceHandler() : m_matchCase(false), m_wholeWord(false), m_findRegEx(false) {} - const std::string TextEditor::MatchedBracket::s_separators = "()[]{}"; - const std::string TextEditor::MatchedBracket::s_operators = "<>"; TextEditor::TextEditor() { - m_startTime = ImGui::GetTime() * 1000; - setLanguageDefinition(LanguageDefinition::HLSL()); - m_lines.emplace_back(); + m_lines.m_startTime = ImGui::GetTime() * 1000; + m_lines.setLanguageDefinition(LanguageDefinition::HLSL()); + m_lines.m_unfoldedLines.push_back(Line()); + m_lineSpacing = 1.0f; + m_tabSize = 4; + + m_lines.m_state.m_cursorPosition = lineCoordinates( 0, 0); + m_lines.m_state.m_selection.m_start = m_lines.m_state.m_selection.m_end = lineCoordinates( 0, 0); } - TextEditor::~TextEditor() = default; + TextEditor::~TextEditor() { + } - std::string TextEditor::getText(const Range &from) { + std::string TextEditor::Lines::getRange(const Range &rangeToGet) { std::string result; - auto selection = setCoordinates(from); + auto selection = lineCoordinates(const_cast(rangeToGet)); + selection.m_end = rangeToGet.m_end; auto columns = selection.getSelectedColumns(); - if (selection.isSingleLine()) - result = m_lines[selection.m_start.m_line].substr(columns.m_line, columns.m_column, Line::LinePart::Utf8); - else { + + if (selection.isSingleLine()) { + result = m_unfoldedLines[selection.m_start.m_line].substr(columns.m_line, columns.m_column, Line::LinePart::Utf8); + } else { auto lines = selection.getSelectedLines(); - result = m_lines[lines.m_line].substr(columns.m_line, (u64) -1, Line::LinePart::Utf8) + '\n'; + std::vector lineContents; + lineContents.push_back(m_unfoldedLines[lines.m_line].substr(columns.m_line, (u64) -1, Line::LinePart::Utf8)); for (i32 i = lines.m_line + 1; i < lines.m_column; i++) { - result += m_lines[i].m_chars + '\n'; + lineContents.push_back(m_unfoldedLines[i].m_chars); } - result += m_lines[lines.m_column].substr(0, columns.m_column, Line::LinePart::Utf8); + lineContents.push_back(m_unfoldedLines[lines.m_column].substr(0, columns.m_column, Line::LinePart::Utf8)); + result = wolv::util::combineStrings(lineContents, "\n"); } return result; } - void TextEditor::deleteRange(const Range &rangeToDelete) { + void TextEditor::Lines::deleteRange(const Range &rangeToDelete) { if (m_readOnly) return; - Range selection = setCoordinates(rangeToDelete); + Range selection = lineCoordinates(const_cast(rangeToDelete)); auto columns = selection.getSelectedColumns(); if (selection.isSingleLine()) { - auto &line = m_lines[selection.m_start.m_line]; + auto &line = m_unfoldedLines[selection.m_start.m_line]; line.erase(columns.m_line, columns.m_column); } else { auto lines = selection.getSelectedLines(); - auto &firstLine = m_lines[lines.m_line]; - auto &lastLine = m_lines[lines.m_column]; + auto &firstLine = m_unfoldedLines[lines.m_line]; + auto &lastLine = m_unfoldedLines[lines.m_column]; firstLine.erase(columns.m_line,(u64) -1); lastLine.erase(0, columns.m_column); @@ -65,43 +70,72 @@ namespace hex::ui { firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end()); firstLine.m_colorized = false; } - if (lines.m_line < lines.m_column) - removeLine(lines.m_line + 1, lines.m_column); + if (lines.m_line+1 < lines.m_column) + removeLines(lines.m_line+1,lines.m_column); + else + removeLine(lines.m_column); } m_textChanged = true; } - void TextEditor::appendLine(const std::string &value) { + void TextEditor::Lines::appendLine(const std::string &value) { auto text = wolv::util::replaceStrings(wolv::util::preprocessText(value), "\000", "."); if (text.empty()) return; if (isEmpty()) { - m_lines[0].m_chars = text; - m_lines[0].m_colors = std::string(text.size(), 0); - m_lines[0].m_flags = std::string(text.size(), 0); - m_lines[0].m_lineMaxColumn = -1; - m_lines[0].m_lineMaxColumn = m_lines[0].maxColumn(); + m_unfoldedLines[0].m_chars = text; + m_unfoldedLines[0].m_colors = std::string(text.size(), 0); + m_unfoldedLines[0].m_flags = std::string(text.size(), 0); } else { - m_lines.emplace_back(text); - auto &line = m_lines.back(); - line.m_lineMaxColumn = -1; + m_unfoldedLines.push_back(Line(text)); + auto line = m_unfoldedLines.back(); line.m_lineMaxColumn = line.maxColumn(); } - setCursorPosition(setCoordinates((i32) m_lines.size() - 1, 0)); - m_lines.back().m_colorized = false; - ensureCursorVisible(); - m_textChanged = true; + m_unfoldedLines.back().m_colorized = false; } + void TextEditor::appendLine(const std::string &value) { + m_lines.appendLine(value); + m_lines.setCursorPosition(m_lines.lineCoordinates((i32) m_lines.size() - 1, 0), false); + m_lines.m_unfoldedLines.back().m_colorized = false; + m_lines.ensureCursorVisible(); + m_lines.m_textChanged = true; + } - i32 TextEditor::insertTextAt(Coordinates /* inout */ &where, const std::string &value) { + i32 TextEditor::Lines::insertTextAtCursor(const std::string &value) { if (value.empty()) return 0; - auto start = setCoordinates(where); + auto start = lineCoordinates(m_state.m_cursorPosition); if (start == Invalid) return 0; - auto &line = m_lines[start.m_line]; + auto &line = m_unfoldedLines[start.m_line]; + auto stringVector = wolv::util::splitString(value, "\n", false); + auto lineCount = (i32) stringVector.size(); + auto state = m_state; + state.m_cursorPosition.m_line += lineCount - 1; + state.m_cursorPosition.m_column += stringCharacterCount(stringVector[lineCount - 1]); + m_state = state; + stringVector[lineCount - 1].append(line.substr(start.m_column,(u64) -1, Line::LinePart::Utf8)); + line.erase(start.m_column, (u64) -1); + + line.append(stringVector[0]); + line.m_colorized = false; + for (i32 i = 1; i < lineCount; i++) { + insertLine(start.m_line + i, stringVector[i]); + m_unfoldedLines[start.m_line + i].m_colorized =false; + } + m_textChanged = true; + return lineCount; + } + + i32 TextEditor::Lines::insertTextAt(Coordinates /* inout */ &where, const std::string &value) { + if (value.empty()) + return 0; + auto start = lineCoordinates(where); + if (start == Invalid) + return 0; + auto &line = m_unfoldedLines[start.m_line]; auto stringVector = wolv::util::splitString(value, "\n", false); auto lineCount = (i32) stringVector.size(); where.m_line += lineCount - 1; @@ -113,7 +147,7 @@ namespace hex::ui { line.m_colorized = false; for (i32 i = 1; i < lineCount; i++) { insertLine(start.m_line + i, stringVector[i]); - m_lines[start.m_line + i].m_colorized = false; + m_unfoldedLines[start.m_line + i].m_colorized =false; } m_textChanged = true; return lineCount; @@ -121,25 +155,24 @@ namespace hex::ui { void TextEditor::deleteWordLeft() { const auto wordEnd = getCursorPosition(); - const auto wordStart = findPreviousWord(getCursorPosition()); - setSelection(Range(wordStart, wordEnd)); + setSelection(Range(m_lines.findPreviousWord(wordEnd), wordEnd)); backspace(); } void TextEditor::deleteWordRight() { const auto wordStart = getCursorPosition(); - const auto wordEnd = findNextWord(getCursorPosition()); - setSelection(Range(wordStart, wordEnd)); + setSelection(Range(wordStart, m_lines.findNextWord(wordStart))); backspace(); } - void TextEditor::removeLine(i32 lineStart, i32 lineEnd) { + void TextEditor::Lines::removeLines(i32 lineStart, i32 lineEnd) { + ErrorMarkers errorMarkers; for (auto &errorMarker : m_errorMarkers) { if (errorMarker.first.m_line <= lineStart || errorMarker.first.m_line > lineEnd + 1) { if (errorMarker.first.m_line >= lineEnd + 1) { auto newRow = errorMarker.first.m_line - (lineEnd - lineStart + 1); - auto newCoord = setCoordinates(newRow, errorMarker.first.m_column); + auto newCoord = lineCoordinates(newRow, errorMarker.first.m_column); errorMarkers.insert(ErrorMarkers::value_type(newCoord, errorMarker.second)); } else errorMarkers.insert(errorMarker); @@ -159,25 +192,54 @@ namespace hex::ui { breakpoints.insert(breakpoint); } } - m_breakpoints = std::move(breakpoints); + + CodeFoldKeys folds; + bool foldsChanged = false; + CodeFoldState codeFoldState; + Range fold; + for (auto interval : m_codeFoldKeys) { + fold = interval; + if (interval.m_end.m_line < lineStart) { + folds.insert(fold); + codeFoldState[fold] = m_codeFoldState[interval]; + } else if (interval.m_start.m_line > lineEnd) { + fold.m_start.m_line -= (lineEnd - lineStart + 1); + fold.m_end.m_line -= (lineEnd - lineStart + 1); + codeFoldState[fold] = m_codeFoldState[interval]; + foldsChanged = true; + folds.insert(fold); + } else if (interval.m_start.m_line < lineStart && interval.m_end.m_line > lineEnd) { + fold.m_end.m_line -= (lineEnd - lineStart + 1); + codeFoldState[fold] = m_codeFoldState[interval]; + foldsChanged = true; + folds.insert(fold); + } else + foldsChanged = true; + } + if (foldsChanged) { + m_codeFoldState = std::move(codeFoldState); + m_codeFoldKeys = std::move(folds); + m_saveCodeFoldStateRequested = true; + } + // use clamp to ensure valid results instead of assert. - auto start = std::clamp(lineStart, 0, (i32) m_lines.size() - 1); - auto end = std::clamp(lineEnd, 0, (i32) m_lines.size()); + auto start = std::clamp(lineStart, 0, (i32) size() - 1); + auto end = std::clamp(lineEnd, 0, (i32) size()); if (start > end) std::swap(start, end); - m_lines.erase(m_lines.begin() + lineStart, m_lines.begin() + lineEnd + 1); + m_unfoldedLines.erase(m_unfoldedLines.begin() + lineStart, m_unfoldedLines.begin() + lineEnd + 1); m_textChanged = true; } - void TextEditor::removeLine(i32 index) { - removeLine(index, index); + void TextEditor::Lines::removeLine(i32 index) { + removeLines(index, index); } - void TextEditor::insertLine(i32 index, const std::string &text) { - if (index < 0 || index > (i32) m_lines.size()) + void TextEditor::Lines::insertLine(i32 index, const std::string &text) { + if (index < 0 || index > (i32) size()) return; auto &line = insertLine(index); line.append(text); @@ -185,24 +247,22 @@ namespace hex::ui { m_textChanged = true; } - TextEditor::Line &TextEditor::insertLine(i32 index) { + TextEditor::Line &TextEditor::Lines::insertLine(i32 index) { if (isEmpty()) - return *m_lines.insert(m_lines.begin(), Line()); + return *m_unfoldedLines.insert(m_unfoldedLines.begin(), Line()); - if (index == (i32)m_lines.size()) - return *m_lines.insert(m_lines.end(), Line()); + if (index == (i32)size()) + return *m_unfoldedLines.insert(m_unfoldedLines.end(), Line()); - auto newLine = Line(); - - TextEditor::Line &result = *m_lines.insert(m_lines.begin() + index, newLine); + TextEditor::Line &result = *m_unfoldedLines.insert(m_unfoldedLines.begin() + index, Line()); result.m_colorized = false; ErrorMarkers errorMarkers; bool errorMarkerChanged = false; for (auto &errorMarker : m_errorMarkers) { if (errorMarker.first.m_line > index) { - auto newCoord = setCoordinates(errorMarker.first.m_line + 1, errorMarker.first.m_column); + auto newCoord = lineCoordinates(errorMarker.first.m_line + 1, errorMarker.first.m_column); errorMarkers.insert(ErrorMarkers::value_type(newCoord, errorMarker.second)); errorMarkerChanged = true; } else @@ -212,8 +272,8 @@ namespace hex::ui { m_errorMarkers = std::move(errorMarkers); Breakpoints breakpoints; - for (auto breakpoint: m_breakpoints) { - if (breakpoint >= (u32)index) { + for (auto breakpoint : m_breakpoints) { + if (breakpoint > (u32) index) { breakpoints.insert(breakpoint + 1); m_breakPointsChanged = true; } else @@ -222,86 +282,137 @@ namespace hex::ui { if (m_breakPointsChanged) m_breakpoints = std::move(breakpoints); + CodeFoldKeys folds; + Range fold; + CodeFoldState codeFoldState; + bool foldsChanged = false; + for (auto key : m_codeFoldKeys) { + fold = key; + if (index <= key.m_start.m_line) { + fold.m_start.m_line++; + fold.m_end.m_line++; + foldsChanged = true; + } else if (index <= key.m_end.m_line) { + fold.m_end.m_line++; + foldsChanged = true; + } + codeFoldState[fold] = m_codeFoldState[key]; + folds.insert(fold); + } + if (foldsChanged) { + m_codeFoldState = std::move(codeFoldState); + m_codeFoldKeys = std::move(folds); + m_saveCodeFoldStateRequested = true; + } + return result; } void TextEditor::setText(const std::string &text, bool undo) { + UndoRecord u; - if (!m_readOnly && undo) { - u.m_before = m_state; - u.m_removed = getText(); - u.m_removedRange.m_start = setCoordinates(0, 0); - u.m_removedRange.m_end = setCoordinates(-1, -1); + if (!m_lines.m_readOnly && undo) { + u.m_before = m_lines.m_state; + u.m_removed = m_lines.getText(false); + u.m_removedRange.m_start = m_lines.lineCoordinates(0, 0); + u.m_removedRange.m_end = m_lines.lineCoordinates(-1, -1); if (u.m_removedRange.m_start == Invalid || u.m_removedRange.m_end == Invalid) return; } auto vectorString = wolv::util::splitString(text, "\n", false); auto lineCount = vectorString.size(); if (lineCount == 0) { - m_lines.resize(1); - m_lines[0].clear(); + m_lines.m_unfoldedLines.resize(1); + m_lines.m_unfoldedLines[0].clear(); } else { - m_lines.clear(); - m_lines.resize(lineCount); + m_lines.m_hiddenLines.clear(); u64 i = 0; - for (const auto& line: vectorString) { - m_lines[i].setLine(line); - m_lines[i].m_colorized = false; - m_lines[i].m_lineMaxColumn = -1; - m_lines[i].m_lineMaxColumn = m_lines[i].maxColumn(); + while (vectorString[i].starts_with("//+-")) { + m_lines.m_hiddenLines.push_back(HiddenLine(i, vectorString[i])), i++; + } + vectorString.erase(vectorString.begin(), vectorString.begin() + i); + lineCount = vectorString.size(); + i = 0; + m_lines.m_unfoldedLines.clear(); + m_lines.m_unfoldedLines.resize(lineCount); + + for (auto line: vectorString) { + auto &unfoldedLine = m_lines.m_unfoldedLines[i]; + + unfoldedLine.setLine(line); + unfoldedLine.m_colorized = false; + unfoldedLine.m_lineMaxColumn = unfoldedLine.maxColumn(); i++; } } - if (!m_readOnly && undo) { + if (!m_lines.m_readOnly && undo) { u.m_added = text; - u.m_addedRange.m_start = setCoordinates(0, 0); - u.m_addedRange.m_end = setCoordinates(-1, -1); + u.m_addedRange.m_start = m_lines.lineCoordinates(0, 0); + u.m_addedRange.m_end = m_lines.lineCoordinates(-1, -1); if (u.m_addedRange.m_start == Invalid || u.m_addedRange.m_end == Invalid) return; } - m_scrollToTop = true; - if (!m_readOnly && undo) { - u.m_after = m_state; - UndoRecords v; + m_lines.m_textChanged = true; + if (!m_lines.m_readOnly && undo) { + u.m_after = m_lines.m_state; + std::vector v; v.push_back(u); - addUndo(v); + m_lines.addUndo(v); } - - colorize(); + m_lines.setAllCodeFolds(); + applyCodeFoldStates(); + m_lines.m_useSavedFoldStatesRequested = true; + m_lines.colorize(); } void TextEditor::enterCharacter(ImWchar character, bool shift) { - if (m_readOnly) + + if (m_lines.m_readOnly) return; + auto row = lineIndexToRow(m_lines.m_state.m_cursorPosition.m_line); + if (m_lines.m_foldedLines.contains(row)) { + auto foldedLine = m_lines.m_foldedLines[row]; + auto foldedCoords = m_lines.unfoldedToFoldedCoords(m_lines.m_state.m_cursorPosition); + if (foldedLine.m_foldedLine.m_chars[foldedCoords.m_column] == '.') { + auto keyCount = foldedLine.m_keys.size(); + for (u32 i = 0; i < keyCount; i++) { + if (foldedCoords.m_column >= foldedLine.m_ellipsisIndices[i] && + foldedCoords.m_column <= foldedLine.m_ellipsisIndices[i] + 3) { + m_lines.openCodeFold(m_lines.m_foldedLines[row].m_keys[i]); + return; + } + } + } + } UndoRecord u; - u.m_before = m_state; + u.m_before =m_lines. m_state; - resetCursorBlinkTime(); + m_lines.resetCursorBlinkTime(); - if (hasSelection()) { + if (m_lines.hasSelection()) { if (character == '\t') { - auto start = m_state.m_selection.m_start; - auto end = m_state.m_selection.m_end; + auto start = m_lines.m_state.m_selection.m_start; + auto end = m_lines.m_state.m_selection.m_end; auto originalEnd = end; start.m_column = 0; if (end.m_column == 0 && end.m_line > 0) --end.m_line; - if (end.m_line >= (i32) m_lines.size()) - end.m_line = isEmpty() ? 0 : (i32) m_lines.size() - 1; - end.m_column = lineMaxColumn(end.m_line); + if (end.m_line >= (i32) m_lines.getGlobalRowMax()) + end.m_line = m_lines.isEmpty() ? 0 : (i32) m_lines.getGlobalRowMax(); + end.m_column = m_lines.lineMaxColumn(end.m_line); u.m_removedRange = Range(start, end); - u.m_removed = getText(u.m_removedRange); + u.m_removed = m_lines.getRange(u.m_removedRange); bool modified = false; for (i32 i = start.m_line; i <= end.m_line; i++) { - auto &line = m_lines[i]; + auto &line = m_lines.m_unfoldedLines[i]; if (shift) { if (!line.empty()) { auto index = line.m_chars.find_first_not_of(' ', 0); @@ -326,61 +437,63 @@ namespace hex::ui { if (modified) { Coordinates rangeEnd; if (originalEnd.m_column != 0) { - end = setCoordinates(end.m_line, -1); + end = m_lines.lineCoordinates(end.m_line, -1); if (end == Invalid) return; rangeEnd = end; - u.m_added = getText(Range(start, end)); + u.m_added = m_lines.getRange(Range(start, end)); } else { - end = setCoordinates(originalEnd.m_line, 0); - rangeEnd = setCoordinates(end.m_line - 1, -1); + end = m_lines.lineCoordinates(originalEnd.m_line, 0); + rangeEnd = m_lines.lineCoordinates(end.m_line - 1, -1); if (end == Invalid || rangeEnd == Invalid) return; - u.m_added = getText(Range(start, rangeEnd)); + u.m_added = m_lines.getRange(Range(start, rangeEnd)); } u.m_addedRange = Range(start, rangeEnd); - u.m_after = m_state; + u.m_after = m_lines.m_state; - m_state.m_selection = Range(start, end); + m_lines.m_state.m_selection = Range(start, end); std::vector v; v.push_back(u); - addUndo(v); + m_lines.addUndo(v); - m_textChanged = true; + m_lines.m_textChanged = true; - ensureCursorVisible(); + m_lines.ensureCursorVisible(); } return; } // c == '\t' else { - u.m_removed = getSelectedText(); - u.m_removedRange = Range(m_state.m_selection); - deleteSelection(); + u.m_removed = m_lines.getSelectedText(); + u.m_removedRange = Range(m_lines.m_state.m_selection); + m_lines.deleteSelection(); } } // HasSelection - auto coord = setCoordinates(m_state.m_cursorPosition); + auto coord = m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition); u.m_addedRange.m_start = coord; - if (m_lines.empty()) - m_lines.emplace_back(); + if (m_lines.m_unfoldedLines.empty()) + m_lines.m_unfoldedLines.push_back(Line()); if (character == '\n') { - insertLine(coord.m_line + 1); - auto &line = m_lines[coord.m_line]; - auto &newLine = m_lines[coord.m_line + 1]; + m_lines.insertLine(coord.m_line + 1); + auto &line = m_lines.m_unfoldedLines[coord.m_line]; + auto &newLine = m_lines.m_unfoldedLines[coord.m_line + 1]; + if (m_lines.m_languageDefinition.m_autoIndentation) + newLine.append(std::string(m_lines.m_leadingLineSpaces[coord.m_line], ' ')); - if (m_languageDefinition.m_autoIndentation) - for (u64 it = 0; it < line.size() && isascii(line[it]) && isblank(line[it]); ++it) - newLine.push_back(line[it]); + //if (m_languageDefinition.m_autoIndentation) + // for (u64 it = 0; it < line.size() && isascii(line[it]) && isblank(line[it]); ++it) + // newLine.push_back(line[it]); const u64 whitespaceSize = newLine.size(); i32 charStart; i32 charPosition; - auto charIndex = lineCoordinatesToIndex(coord); - if (charIndex < (i32) whitespaceSize && m_languageDefinition.m_autoIndentation) { + auto charIndex = m_lines.lineCoordsIndex(coord); + if (charIndex < (i32) whitespaceSize && m_lines.m_languageDefinition.m_autoIndentation) { charStart = (i32) whitespaceSize; charPosition = charIndex; } else { @@ -390,21 +503,21 @@ namespace hex::ui { newLine.insert(newLine.end(), line.begin() + charStart, line.end()); line.erase(line.begin() + charStart,(u64) -1); line.m_colorized = false; - setCursorPosition(getCharacterCoordinates(coord.m_line + 1, charPosition)); - u.m_added = (char) character; - u.m_addedRange.m_end = setCoordinates(m_state.m_cursorPosition); + m_lines.setCursorPosition(m_lines.lineIndexCoords(coord.m_line + 2, charPosition), false); + u.m_added = (char) character + std::string(charPosition, ' '); + u.m_addedRange.m_end = m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition); } else if (character == '\t') { - auto &line = m_lines[coord.m_line]; - auto charIndex = lineCoordinatesToIndex(coord); + auto &line = m_lines.m_unfoldedLines[coord.m_line]; + auto charIndex = m_lines.lineCoordsIndex(coord); if (!shift) { auto spacesToInsert = m_tabSize - (charIndex % m_tabSize); std::string spaces(spacesToInsert, ' '); line.insert(line.begin() + charIndex, spaces.begin(), spaces.end()); line.m_colorized = false; - setCursorPosition(getCharacterCoordinates(coord.m_line, charIndex + spacesToInsert)); + m_lines.setCursorPosition(m_lines.lineIndexCoords(coord.m_line + 1, charIndex + spacesToInsert), false); u.m_added = spaces; - u.m_addedRange.m_end = setCoordinates(m_state.m_cursorPosition); + u.m_addedRange.m_end = m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition); } else { auto spacesToRemove = (charIndex % m_tabSize); if (spacesToRemove == 0) spacesToRemove = m_tabSize; @@ -419,17 +532,17 @@ namespace hex::ui { } std::string spaces(spacesRemoved, ' '); u.m_removed = spaces; - u.m_removedRange = Range(Coordinates(coord.m_line, charIndex), Coordinates(coord.m_line, charIndex + spacesRemoved)); + u.m_removedRange = Range(lineCoordinates( coord.m_line, charIndex), lineCoordinates( coord.m_line, charIndex + spacesRemoved)); line.m_colorized = false; - setCursorPosition(getCharacterCoordinates(coord.m_line, std::max(0, charIndex))); + m_lines.setCursorPosition(m_lines.lineIndexCoords(coord.m_line + 1, std::max(0, charIndex)), false); } - u.m_addedRange.m_end = setCoordinates(m_state.m_cursorPosition); + u.m_addedRange.m_end = m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition); } else { - std::string buf; + std::string buf = ""; imTextCharToUtf8(buf, character); - if (!buf.empty()) { - auto &line = m_lines[coord.m_line]; - auto charIndex = lineCoordinatesToIndex(coord); + if (buf.size() > 0) { + auto &line = m_lines.m_unfoldedLines[coord.m_line]; + auto charIndex = m_lines.lineCoordsIndex(coord); if (m_overwrite && charIndex < (i32) line.size()) { i64 column = coord.m_column; @@ -437,7 +550,7 @@ namespace hex::ui { auto charCount = stringCharacterCount(c); auto d = c.size(); - u.m_removedRange = Range(m_state.m_cursorPosition, getCharacterCoordinates(coord.m_line, coord.m_column + charCount)); + u.m_removedRange = Range(m_lines.m_state.m_cursorPosition, m_lines.lineIndexCoords(coord.m_line + 1, 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; @@ -475,24 +588,24 @@ namespace hex::ui { line.insert(line.begin() + charIndex, buf.begin(), buf.end()); line.m_colorized = false; u.m_added = buf; - u.m_addedRange.m_end = getCharacterCoordinates(coord.m_line, charIndex + buf.size()); - setCursorPosition(getCharacterCoordinates(coord.m_line, charIndex + charCount)); + u.m_addedRange.m_end = m_lines.lineIndexCoords(coord.m_line + 1, charIndex + buf.size()); + m_lines.setCursorPosition(m_lines.lineIndexCoords(coord.m_line + 1, charIndex + charCount), false); } else return; } - u.m_after = m_state; - m_textChanged = true; + u.m_after = m_lines.m_state; + m_lines.m_textChanged = true; - UndoRecords v; + std::vector v; v.push_back(u); - addUndo(v); - colorize(); - refreshSearchResults(); - ensureCursorVisible(); + m_lines.addUndo(v); + m_lines.colorize(); + m_lines.refreshSearchResults(); + m_lines.ensureCursorVisible(); } - void TextEditor::refreshSearchResults() { + void TextEditor::Lines::refreshSearchResults() { std::string findWord = m_findReplaceHandler.getFindWord(); if (!findWord.empty()) { m_findReplaceHandler.resetMatches(); @@ -500,26 +613,21 @@ namespace hex::ui { } } - void TextEditor::insertText(const std::string &value) { + void TextEditor::Lines::insertText(const std::string &value) { insertText(value.c_str()); } - void TextEditor::insertText(const char *value) { + void TextEditor::Lines::insertText(const char *value) { if (value == nullptr) return; - auto pos = setCoordinates(m_state.m_cursorPosition); + insertTextAtCursor(value); - insertTextAt(pos, value); - m_lines[pos.m_line].m_colorized = false; - - setSelection(Range(pos, pos)); - setCursorPosition(pos); refreshSearchResults(); colorize(); } - void TextEditor::deleteSelection() { + void TextEditor::Lines::deleteSelection() { if (m_state.m_selection.m_end == m_state.m_selection.m_start) return; @@ -527,91 +635,91 @@ namespace hex::ui { deleteRange(m_state.m_selection); setSelection(Range(m_state.m_selection.m_start, m_state.m_selection.m_start)); - setCursorPosition(m_state.m_selection.m_start); + setCursorPosition(m_state.m_selection.m_start, false); refreshSearchResults(); colorize(); } void TextEditor::deleteChar() { - resetCursorBlinkTime(); + m_lines.resetCursorBlinkTime(); - if (isEmpty() || m_readOnly) + if (m_lines.isEmpty() || m_lines.m_readOnly) return; UndoRecord u; - u.m_before = m_state; + u.m_before = m_lines.m_state; - if (hasSelection()) { - u.m_removed = getSelectedText(); - u.m_removedRange = m_state.m_selection; - deleteSelection(); + if (m_lines.hasSelection()) { + u.m_removed = m_lines.getSelectedText(); + u.m_removedRange = m_lines.m_state.m_selection; + m_lines.deleteSelection(); } else { - auto pos = setCoordinates(m_state.m_cursorPosition); - setCursorPosition(pos); - auto &line = m_lines[pos.m_line]; + auto pos = m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition); + m_lines.setCursorPosition(pos, false); + auto &line = m_lines.m_unfoldedLines[pos.m_line]; - if (pos.m_column == lineMaxColumn(pos.m_line)) { + if (pos.m_column == line.maxColumn()) { if (pos.m_line == (i32) m_lines.size() - 1) return; u.m_removed = '\n'; - u.m_removedRange.m_start = u.m_removedRange.m_end = setCoordinates(m_state.m_cursorPosition); + u.m_removedRange.m_start = u.m_removedRange.m_end = m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition); advance(u.m_removedRange.m_end); - auto &nextLine = m_lines[pos.m_line + 1]; + auto &nextLine = m_lines.m_unfoldedLines[pos.m_line + 1]; line.insert(line.end(), nextLine.begin(), nextLine.end()); line.m_colorized = false; - removeLine(pos.m_line + 1); + m_lines.removeLine(pos.m_line + 1); } else { - i64 charIndex = lineCoordinatesToIndex(pos); - u.m_removedRange.m_start = u.m_removedRange.m_end = setCoordinates(m_state.m_cursorPosition); + i64 charIndex = m_lines.lineCoordsIndex(pos); + u.m_removedRange.m_start = u.m_removedRange.m_end = m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition); u.m_removedRange.m_end.m_column++; - u.m_removed = getText(u.m_removedRange); + u.m_removed = m_lines.getRange(u.m_removedRange); auto d = utf8CharLength(line[charIndex][0]); line.erase(line.begin() + charIndex, d); line.m_colorized = false; } - m_textChanged = true; + m_lines.m_textChanged = true; - colorize(); + m_lines.colorize(); } - u.m_after = m_state; - UndoRecords v; + u.m_after = m_lines.m_state; + std::vector v; v.push_back(u); - addUndo(v); - refreshSearchResults(); + m_lines.addUndo(v); + m_lines.refreshSearchResults(); } void TextEditor::backspace() { - resetCursorBlinkTime(); - if (isEmpty() || m_readOnly) + m_lines.resetCursorBlinkTime(); + if (m_lines.isEmpty() || m_lines.m_readOnly) return; UndoRecord u; - u.m_before = m_state; + u.m_before = m_lines.m_state; - if (hasSelection()) { - u.m_removed = getSelectedText(); - u.m_removedRange = m_state.m_selection; - deleteSelection(); + if (m_lines.hasSelection()) { + u.m_removed = m_lines.getSelectedText(); + u.m_removedRange = m_lines.m_state.m_selection; + m_lines.deleteSelection(); } else { - auto pos = setCoordinates(m_state.m_cursorPosition); - auto &line = m_lines[pos.m_line]; + auto pos = m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition); + auto &line = m_lines.m_unfoldedLines[pos.m_line]; if (pos.m_column == 0) { if (pos.m_line == 0) return; u.m_removed = '\n'; - u.m_removedRange.m_start = u.m_removedRange.m_end = setCoordinates(pos.m_line - 1, -1); + u.m_removedRange.m_start = u.m_removedRange.m_end = m_lines.lineCoordinates(pos.m_line - 1, -1); advance(u.m_removedRange.m_end); - auto &prevLine = m_lines[pos.m_line - 1]; - auto prevSize = lineMaxColumn(pos.m_line - 1); + auto &prevLine = m_lines.m_unfoldedLines[pos.m_line - 1]; + auto prevSize = prevLine.maxColumn(); if (prevSize == 0) prevLine = line; else @@ -620,12 +728,12 @@ namespace hex::ui { ErrorMarkers errorMarker; - for (auto &i: m_errorMarkers) - errorMarker.insert(ErrorMarkers::value_type(i.first.m_line - 1 == m_state.m_cursorPosition.m_line ? setCoordinates(i.first.m_line - 1, i.first.m_column) : i.first, i.second)); - m_errorMarkers = std::move(errorMarker); - removeLine(m_state.m_cursorPosition.m_line); - --m_state.m_cursorPosition.m_line; - m_state.m_cursorPosition.m_column = prevSize; + for (auto &i: m_lines.m_errorMarkers) + errorMarker.insert(ErrorMarkers::value_type(i.first.m_line - 1 == m_lines.m_state.m_cursorPosition.m_line ? m_lines.lineCoordinates(i.first.m_line - 1, i.first.m_column) : i.first, i.second)); + m_lines.m_errorMarkers = std::move(errorMarker); + m_lines.removeLine(m_lines.m_state.m_cursorPosition.m_line); + m_lines.m_state.m_cursorPosition.m_line--; + m_lines.m_state.m_cursorPosition.m_column = prevSize; } else { pos.m_column -= 1; i64 column = pos.m_column; @@ -634,80 +742,80 @@ namespace hex::ui { std::string charToRemoveNext = line[column + 1]; if (charToRemove == "{" && charToRemoveNext == "}") { charToRemove += "}"; - m_state.m_cursorPosition.m_column += 1; + m_lines.m_state.m_cursorPosition.m_column += 1; } else if (charToRemove == "[" && charToRemoveNext == "]") { charToRemove += "]"; - m_state.m_cursorPosition.m_column += 1; + m_lines.m_state.m_cursorPosition.m_column += 1; } else if (charToRemove == "(" && charToRemoveNext == ")") { charToRemove += ")"; - m_state.m_cursorPosition.m_column += 1; + m_lines.m_state.m_cursorPosition.m_column += 1; } else if (charToRemove == "\"" && charToRemoveNext == "\"") { charToRemove += "\""; - m_state.m_cursorPosition.m_column += 1; + m_lines.m_state.m_cursorPosition.m_column += 1; } else if (charToRemove == "'" && charToRemoveNext == "'") { charToRemove += "'"; - m_state.m_cursorPosition.m_column += 1; + m_lines.m_state.m_cursorPosition.m_column += 1; } } - u.m_removedRange = Range(pos, m_state.m_cursorPosition); + u.m_removedRange = Range(pos, m_lines.m_state.m_cursorPosition); u.m_removed = charToRemove; - auto charStart = lineCoordinatesToIndex(pos); - auto charEnd = lineCoordinatesToIndex(m_state.m_cursorPosition); + auto charStart = m_lines.lineCoordsIndex(pos); + auto charEnd = m_lines.lineCoordsIndex(m_lines.m_state.m_cursorPosition); line.erase(line.begin() + charStart, charEnd - charStart); - m_state.m_cursorPosition = pos; + m_lines.m_state.m_cursorPosition = pos; line.m_colorized = false; } - m_textChanged = true; + m_lines.m_textChanged = true; - ensureCursorVisible(); - colorize(); + m_lines.ensureCursorVisible(); + m_lines.colorize(); } - u.m_after = m_state; - UndoRecords v; + u.m_after = m_lines.m_state; + std::vector v; v.push_back(u); - addUndo(v); - refreshSearchResults(); + m_lines.addUndo(v); + m_lines.refreshSearchResults(); } void TextEditor::copy() { - if (hasSelection()) { - ImGui::SetClipboardText(getSelectedText().c_str()); + if (m_lines.hasSelection()) { + ImGui::SetClipboardText(m_lines.getSelectedText().c_str()); } else { - if (!isEmpty()) { + if (!m_lines.isEmpty()) { std::string str; - const auto &line = m_lines[setCoordinates(m_state.m_cursorPosition).m_line]; - std::ranges::copy(line.m_chars, std::back_inserter(str)); + const auto &line = m_lines.m_unfoldedLines[m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition).m_line]; + std::copy(line.m_chars.begin(), line.m_chars.end(), std::back_inserter(str)); ImGui::SetClipboardText(str.c_str()); } } } void TextEditor::cut() { - if (m_readOnly) { + if (m_lines.m_readOnly) { copy(); } else { - if (!hasSelection()) { - auto lineIndex = setCoordinates(m_state.m_cursorPosition).m_line; + if (!m_lines.hasSelection()) { + auto lineIndex = m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition).m_line; if (lineIndex < 0 || lineIndex >= (i32) m_lines.size()) return; - setSelection(Range(setCoordinates(lineIndex, 0), setCoordinates(lineIndex + 1, 0))); + setSelection(Range(m_lines.lineCoordinates(lineIndex, 0), m_lines.lineCoordinates(lineIndex + 1, 0))); } UndoRecord u; - u.m_before = m_state; - u.m_removed = getSelectedText(); - u.m_removedRange = m_state.m_selection; + u.m_before = m_lines.m_state; + u.m_removed = m_lines.getSelectedText(); + u.m_removedRange = m_lines.m_state.m_selection; copy(); - deleteSelection(); + m_lines.deleteSelection(); - u.m_after = m_state; + u.m_after = m_lines.m_state; std::vector v; v.push_back(u); - addUndo(v); + m_lines.addUndo(v); } - refreshSearchResults(); + m_lines.refreshSearchResults(); } void TextEditor::doPaste(const char *clipText) { @@ -715,35 +823,39 @@ namespace hex::ui { if (clipText != nullptr) { auto clipTextStr = wolv::util::preprocessText(clipText); - u.m_before = m_state; + u.m_before = m_lines.m_state; - if (hasSelection()) { - u.m_removed = getSelectedText(); - u.m_removedRange = m_state.m_selection; - deleteSelection(); + if (m_lines.hasSelection()) { + u.m_removed = m_lines.getSelectedText(); + u.m_removedRange = m_lines.m_state.m_selection; + m_lines.deleteSelection(); } u.m_added = clipTextStr; - u.m_addedRange.m_start = setCoordinates(m_state.m_cursorPosition); - insertText(clipTextStr); + u.m_addedRange.m_start = m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition); + m_lines.insertText(clipTextStr); - u.m_addedRange.m_end = setCoordinates(m_state.m_cursorPosition); - u.m_after = m_state; - UndoRecords v; + u.m_addedRange.m_end = m_lines.lineCoordinates(m_lines.m_state.m_cursorPosition); + + + m_lines.setCursorPosition(u.m_addedRange.m_end, false); + setSelection(Range(u.m_addedRange.m_end, u.m_addedRange.m_end)); + u.m_after = m_lines.m_state; + std::vector v; v.push_back(u); - addUndo(v); + m_lines.addUndo(v); } - refreshSearchResults(); + m_lines.refreshSearchResults(); } void TextEditor::paste() { - if (m_readOnly) + if (m_lines.m_readOnly) return; const char *clipText = ImGui::GetClipboardText(); if (clipText != nullptr) { auto stringVector = wolv::util::splitString(clipText, "\n", false); - if (std::ranges::any_of(stringVector, [](const std::string &s) { return s.size() > 1024; })) { + if (std::any_of(stringVector.begin(), stringVector.end(), [](const std::string &s) { return s.size() > 1024; })) { ui::PopupQuestion::open("hex.builtin.view.pattern_editor.warning_paste_large"_lang, [this, clipText]() { this->doPaste(clipText); }, [] {}); @@ -754,36 +866,44 @@ namespace hex::ui { } bool TextEditor::canUndo() { - return !m_readOnly && m_undoIndex > 0; + return !m_lines.m_readOnly && m_lines.m_undoIndex > 0; } bool TextEditor::canRedo() const { - return !m_readOnly && m_undoIndex < (i32) m_undoBuffer.size(); + return !m_lines.m_readOnly && m_lines.m_undoIndex < (i32) m_lines.m_undoBuffer.size(); } void TextEditor::undo() { if (canUndo()) { - m_undoIndex--; - m_undoBuffer[m_undoIndex].undo(this); + m_lines.m_undoIndex--; + m_lines.m_undoBuffer[m_lines.m_undoIndex].undo(this); } - refreshSearchResults(); + m_lines.refreshSearchResults(); } void TextEditor::redo() { if (canRedo()) { - m_undoBuffer[m_undoIndex].redo(this); - m_undoIndex++; + m_lines.m_undoBuffer[m_lines.m_undoIndex].redo(this); + m_lines.m_undoIndex++; } - refreshSearchResults(); + m_lines.refreshSearchResults(); } - std::string TextEditor::getText() { - auto start = setCoordinates(0, 0); - auto size = m_lines.size(); - auto end = setCoordinates(-1, m_lines[size - 1].m_lineMaxColumn); + std::string TextEditor::Lines::getText(bool includeHiddenLines) { + auto start = lineCoordinates(0, 0); + auto size = m_unfoldedLines.size(); + auto line = m_unfoldedLines[size - 1]; + auto end = Coordinates( size - 1, line.m_lineMaxColumn); if (start == Invalid || end == Invalid) return ""; - return getText(Range(start, end)); + std::string result; + if (includeHiddenLines) { + for (const auto &hiddenLine: m_hiddenLines) { + result += hiddenLine.m_line + "\n"; + } + } + result += getRange(Range(start, end)); + return result; } std::vector TextEditor::getTextLines() const { @@ -791,7 +911,7 @@ namespace hex::ui { result.reserve(m_lines.size()); - for (const auto &line: m_lines) { + for (const auto &line: m_lines.m_unfoldedLines) { std::string text = line.m_chars; result.emplace_back(std::move(text)); } @@ -799,56 +919,56 @@ namespace hex::ui { return result; } - std::string TextEditor::getSelectedText() { - return getText(m_state.m_selection); + std::string TextEditor::Lines::getSelectedText() { + return getRange(m_state.m_selection); } std::string TextEditor::getLineText(i32 line) { - auto sanitizedLine = setCoordinates(line, 0); - auto endLine = setCoordinates(line, -1); - if (sanitizedLine == Invalid || endLine == Invalid) + auto sanitizedLine = m_lines.lineCoordinates(line, 0); + auto endLine = m_lines.lineCoordinates(line, -1); + if (!sanitizedLine.isValid(m_lines) || !endLine.isValid(m_lines)) return ""; - return getText(Range(sanitizedLine, endLine)); + return m_lines.getRange(Range(sanitizedLine, endLine)); } TextEditor::UndoRecord::UndoRecord( std::string added, - const TextEditor::Range addedRange, + TextEditor::Range addedRange, std::string removed, - const TextEditor::Range removedRange, + TextEditor::Range removedRange, TextEditor::EditorState &before, - TextEditor::EditorState &after) : m_added(std::move(added)), m_addedRange(addedRange), m_removed(std::move(removed)), m_removedRange(removedRange), m_before(before), m_after(after) {} + TextEditor::EditorState &after) : m_added(added), m_addedRange(addedRange), m_removed(removed), m_removedRange(removedRange), m_before(before), m_after(after) {} void TextEditor::UndoRecord::undo(TextEditor *editor) { if (!m_added.empty()) { - editor->deleteRange(m_addedRange); - editor->colorize(); + editor->m_lines.deleteRange(m_addedRange); + editor->m_lines.colorize(); } if (!m_removed.empty()) { auto start = m_removedRange.m_start; - editor->insertTextAt(start, m_removed.c_str()); - editor->colorize(); + editor->m_lines.insertTextAt(start, m_removed.c_str()); + editor->m_lines.colorize(); } - editor->m_state = m_before; - editor->ensureCursorVisible(); + editor->m_lines.m_state = m_before; + editor->m_lines.ensureCursorVisible(); } void TextEditor::UndoRecord::redo(TextEditor *editor) { if (!m_removed.empty()) { - editor->deleteRange(m_removedRange); - editor->colorize(); + editor->m_lines.deleteRange(m_removedRange); + editor->m_lines.colorize(); } if (!m_added.empty()) { auto start = m_addedRange.m_start; - editor->insertTextAt(start, m_added.c_str()); - editor->colorize(); + editor->m_lines.insertTextAt(start, m_added.c_str()); + editor->m_lines.colorize(); } - editor->m_state = m_after; - editor->ensureCursorVisible(); + editor->m_lines.m_state = m_after; + editor->m_lines.ensureCursorVisible(); } void TextEditor::UndoAction::undo(TextEditor *editor) { @@ -857,8 +977,8 @@ namespace hex::ui { } void TextEditor::UndoAction::redo(TextEditor *editor) { - for (auto & m_record : m_records) - m_record.redo(editor); + for (i32 i = 0; i < (i32) m_records.size(); i++) + m_records.at(i).redo(editor); } } \ No newline at end of file diff --git a/plugins/ui/source/ui/text_editor/highlighter.cpp b/plugins/ui/source/ui/text_editor/highlighter.cpp index a2ead5aa2..82f598dbb 100644 --- a/plugins/ui/source/ui/text_editor/highlighter.cpp +++ b/plugins/ui/source/ui/text_editor/highlighter.cpp @@ -6,6 +6,7 @@ namespace hex::ui { extern TextEditor::Palette s_paletteBase; + template bool equals(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, BinaryPredicate p) { for (; first1 != last1 && first2 != last2; ++first1, ++first2) { @@ -16,13 +17,13 @@ namespace hex::ui { } void TextEditor::setNeedsUpdate(i32 line, bool needsUpdate) { - if (line < (i32) m_lines.size()) - m_lines[line].setNeedsUpdate(needsUpdate); + if (line < m_lines.size()) + m_lines.m_unfoldedLines[line].setNeedsUpdate(needsUpdate); } void TextEditor::setColorizedLine(i64 line, const std::string &tokens) { - if (line < (i64)m_lines.size()) { - auto &lineTokens = m_lines[line].m_colors; + if (line < m_lines.size()) { + auto &lineTokens = m_lines.m_unfoldedLines[line].m_colors; if (lineTokens.size() != tokens.size()) { lineTokens.resize(tokens.size()); std::ranges::fill(lineTokens, 0x00); @@ -40,11 +41,46 @@ namespace hex::ui { } } - void TextEditor::colorize() { + std::vector TextEditor::Lines::getDeactivatedBlocks() { + colorizeInternal(); + std::vector deactivatedBlocks; + if (isEmpty()) + return deactivatedBlocks; + for (i32 i = 0; i < size(); ++i) { + std::string flags = m_unfoldedLines[i].m_flags; + if (flags.empty()) + continue; + auto index = flags.find_first_not_of((char)8); + Range currentBlock; + bool foundStart = false; + while (index == std::string::npos) { + if (!foundStart) { + currentBlock.m_start.m_line = i; + currentBlock.m_start.m_column = 0; + foundStart = true; + } else { + currentBlock.m_end.m_line = i; + currentBlock.m_end.m_column = flags.size() - 1; + } + i++; + if (i >= size()) + break; + flags = m_unfoldedLines[i].m_flags; + index = flags.find_first_not_of((char) 8); + } + if (currentBlock.m_start.m_line < currentBlock.m_end.m_line) { + deactivatedBlocks.push_back(currentBlock); + } + } + return deactivatedBlocks; + + } + + void TextEditor::Lines::colorize() { m_updateFlags = true; } - void TextEditor::colorizeRange() { + void TextEditor::Lines::colorizeRange() { if (isEmpty()) return; @@ -56,9 +92,9 @@ namespace hex::ui { log::warn("Syntax highlighting tokenize callback is nullptr"); return; } - i32 linesSize = m_lines.size(); + i32 linesSize = size(); for (i32 i = 0; i < linesSize; ++i) { - auto &line = m_lines[i]; + auto &line = m_unfoldedLines[i]; auto size = line.size(); if (line.m_colors.size() != size) { @@ -72,6 +108,7 @@ namespace hex::ui { auto last = line.end(); auto first = line.begin(); + line.m_colorized = true; for (auto current = first; (current - first) < (i64)size;) { strConstIter token_begin; strConstIter token_end; @@ -164,12 +201,12 @@ namespace hex::ui { } } - void TextEditor::colorizeInternal() { + void TextEditor::Lines::colorizeInternal() { if (isEmpty() || !m_colorizerEnabled) return; if (m_updateFlags) { - auto endLine = m_lines.size(); + auto endLine = size(); auto commentStartLine = endLine; auto commentStartIndex = 0; auto withinGlobalDocComment = false; @@ -185,7 +222,7 @@ namespace hex::ui { ifDefs.push_back(true); m_defines.emplace_back("__IMHEX__"); for (currentLine = 0; currentLine < endLine; currentLine++) { - auto &line = m_lines[currentLine]; + auto &line = m_unfoldedLines[currentLine]; auto lineLength = line.size(); if (line.m_flags.size() != lineLength) { @@ -213,9 +250,9 @@ namespace hex::ui { flags.m_value = (i32) Line::Comments::BlockDoc; flags.m_bits.deactivated = withinNotDef; 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; + if (m_unfoldedLines[currentLine].m_flags[index] != flags.m_value) { + m_unfoldedLines[currentLine].m_colorized = false; + m_unfoldedLines[currentLine].m_flags[index] = flags.m_value; } }; @@ -227,34 +264,34 @@ namespace hex::ui { char c = line[currentIndex]; matchedBracket = false; - if (MatchedBracket::s_separators.contains(c) && m_matchedBracket.isActive()) { - if (m_matchedBracket.m_nearCursor == getCharacterCoordinates(currentLine, currentIndex) || m_matchedBracket.m_matched == getCharacterCoordinates(currentLine, currentIndex)) + if (s_separators.contains(c) && m_matchedDelimiter.isActive()) { + if (m_matchedDelimiter.m_nearCursor == lineIndexCoords(currentLine + 1, currentIndex) || m_matchedDelimiter.m_matched == lineIndexCoords(currentLine + 1, currentIndex)) matchedBracket = true; - } else if (MatchedBracket::s_operators.contains(c) && m_matchedBracket.isActive()) { - Coordinates current = setCoordinates(currentLine,currentIndex); + } else if (s_operators.contains(c) && m_matchedDelimiter.isActive()) { + Coordinates current = lineCoordinates(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 ((c == '<' && m_matchedDelimiter.m_nearCursor == current) || (c == '>' && m_matchedDelimiter.m_matched == current)) + cursor = m_matchedDelimiter.m_nearCursor; + else if ((c == '>' && m_matchedDelimiter.m_nearCursor == current) || (c == '<' && m_matchedDelimiter.m_matched == current)) + cursor = m_matchedDelimiter.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; + cursor.m_column = m_unfoldedLines[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)) { + while (std::isblank(m_unfoldedLines[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; + cursor.m_column = m_unfoldedLines[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)) + if (m_unfoldedLines[cursor.m_line].m_colors[cursor.m_column] == udt && (cursor.m_line != 0 || cursor.m_column != 0)) matchedBracket = true; } } @@ -379,17 +416,17 @@ namespace hex::ui { } if (currentIndex < line.size()) { Line::Flags flags(0); - flags.m_value = m_lines[currentLine].m_flags[currentIndex]; + flags.m_value = m_unfoldedLines[currentLine].m_flags[currentIndex]; flags.m_bits.preprocessor = withinPreproc; - m_lines[currentLine].m_flags[currentIndex] = flags.m_value; + m_unfoldedLines[currentLine].m_flags[currentIndex] = flags.m_value; } auto utf8CharLen = utf8CharLength(c); if (utf8CharLen > 1) { Line::Flags flags(0); - flags.m_value = m_lines[currentLine].m_flags[currentIndex]; + flags.m_value = m_unfoldedLines[currentLine].m_flags[currentIndex]; for (i32 j = 1; j < utf8CharLen; j++) { currentIndex++; - m_lines[currentLine].m_flags[currentIndex] = flags.m_value; + m_unfoldedLines[currentLine].m_flags[currentIndex] = flags.m_value; } } currentIndex++; @@ -401,7 +438,7 @@ namespace hex::ui { colorizeRange(); } - void TextEditor::setLanguageDefinition(const LanguageDefinition &languageDef) { + void TextEditor::Lines::setLanguageDefinition(const LanguageDefinition &languageDef) { m_languageDefinition = languageDef; m_regexList.clear(); diff --git a/plugins/ui/source/ui/text_editor/navigate.cpp b/plugins/ui/source/ui/text_editor/navigate.cpp index 6e6441e31..7c334a47f 100644 --- a/plugins/ui/source/ui/text_editor/navigate.cpp +++ b/plugins/ui/source/ui/text_editor/navigate.cpp @@ -9,56 +9,114 @@ namespace hex::ui { return std::isalnum(asUChar) || c == '_' || asUChar > 0x7F; } + void TextEditor::jumpToLine(i32 line) { - auto newPos = m_state.m_cursorPosition; + auto newPos = m_lines.m_state.m_cursorPosition; if (line != -1) { - newPos = setCoordinates(line, 0); + newPos = m_lines.lineCoordinates(line, 0); } jumpToCoords(newPos); } void TextEditor::jumpToCoords(const Coordinates &coords) { setSelection(Range(coords, coords)); - setCursorPosition(coords, true); - ensureCursorVisible(); + m_lines.setCursorPosition(coords, true); + m_lines.ensureCursorVisible(); - setFocusAtCoords(coords, true); + m_lines.setFocusAtCoords(coords, true); } - void TextEditor::moveToMatchedBracket(bool select) { - resetCursorBlinkTime(); - if (m_matchedBracket.isNearABracket(this, m_state.m_cursorPosition)) { - m_matchedBracket.findMatchingBracket(this); - auto oldPos = m_matchedBracket.m_nearCursor; - auto newPos = m_matchedBracket.m_matched; - if (newPos != setCoordinates(-1, -1)) { - if (select) { - if (oldPos == m_interactiveSelection.m_start) - m_interactiveSelection.m_start = newPos; - else if (oldPos == m_interactiveSelection.m_end) - m_interactiveSelection.m_end = newPos; - else { - m_interactiveSelection = Range(newPos, oldPos); - } - } else - m_interactiveSelection.m_start = m_interactiveSelection.m_end = newPos; + void TextEditor::moveToMatchedDelimiter(bool select) { + m_lines.moveToMatchedDelimiter(select); + } - setSelection(m_interactiveSelection); - setCursorPosition(newPos); - ensureCursorVisible(); + TextEditor::Coordinates TextEditor::Lines::rfind( const std::string &text, const Coordinates &from) { + Coordinates result = Invalid; + if (text.empty() || isEmpty() || from.m_line >= (i32) size() || from.m_line < 0) + return result; + for (i32 i = from.m_line; i >= 0; --i) { + auto &line = m_unfoldedLines[i]; + auto index = line.m_chars.rfind(text, (i == from.m_line) ? from.m_column : std::string::npos); + if (index != std::string::npos) { + result = Coordinates( i, line.indexColumn(index)); + break; + } + } + return result; + } + + + TextEditor::Coordinates TextEditor::Lines::find(const std::string &text, const Coordinates &from) { + Coordinates result = Invalid; + if (text.empty() || isEmpty() || from.m_line >= (i32) size() || from.m_line < 0) + return result; + for (i32 i = from.m_line; i < (i32) size(); ++i) { + auto &line = m_unfoldedLines[i]; + auto index = line.m_chars.find(text, (i == from.m_line) ? from.m_column : 0); + if (index != std::string::npos) { + result = Coordinates( i, line.indexColumn(index)); + break; + } + } + + return result; + } + + void TextEditor::Lines::moveToMatchedDelimiter(bool select) { + resetCursorBlinkTime(); + if (m_matchedDelimiter.coordinatesNearDelimiter(this, m_state.m_cursorPosition)) { + auto oldPos = m_matchedDelimiter.m_nearCursor; + if (operator[](oldPos.m_line).m_colors[oldPos.m_column] == (char) PaletteIndex::WarningText) { + m_matchedDelimiter.findMatchingDelimiter(this); + auto newPos = m_matchedDelimiter.m_matched; + if (newPos != Invalid) { + if (select) { + if (oldPos == m_interactiveSelection.m_start) + m_interactiveSelection.m_start = newPos; + else if (oldPos == m_interactiveSelection.m_end) + m_interactiveSelection.m_end = newPos; + else { + m_interactiveSelection = Range(newPos, oldPos); + } + } else + m_interactiveSelection.m_start = m_interactiveSelection.m_end = newPos; + + setSelection(m_interactiveSelection); + setCursorPosition(newPos); + ensureCursorVisible(); + } } } } void TextEditor::moveUp(i32 amount, bool select) { - resetCursorBlinkTime(); + m_lines.resetCursorBlinkTime(); + m_lines.moveUp(amount, select); + setSelection(m_lines.m_interactiveSelection); + m_lines.ensureCursorVisible(); + } + + void TextEditor::Lines::moveUp(i32 amount, bool select) { auto oldPos = m_state.m_cursorPosition; if (amount < 0) { m_scrollYIncrement = -1.0; setScrollY(); return; } - m_state.m_cursorPosition.m_line = std::max(0, m_state.m_cursorPosition.m_line - amount); + auto row = lineIndexToRow(m_state.m_cursorPosition.m_line); + if (isMultiLineRow(row)) { + auto position = unfoldedToFoldedCoords(m_state.m_cursorPosition); + m_state.m_cursorPosition.m_column = position.m_column; + } + row -= amount; + if (isMultiLineRow(row)) { + auto position = foldedToUnfoldedCoords(Coordinates( rowToLineIndex(row), m_state.m_cursorPosition.m_column)); + m_state.m_cursorPosition = position; + } else { + row = std::clamp(row, 0.0f, getGlobalRowMax()); + m_state.m_cursorPosition.m_line = rowToLineIndex(row); + } + if (oldPos != m_state.m_cursorPosition) { if (select) { if (oldPos == m_interactiveSelection.m_start) @@ -71,15 +129,18 @@ namespace hex::ui { } } else m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition; - setSelection(m_interactiveSelection); - - ensureCursorVisible(); } } void TextEditor::moveDown(i32 amount, bool select) { - IM_ASSERT(m_state.m_cursorPosition.m_column >= 0); - resetCursorBlinkTime(); + m_lines.resetCursorBlinkTime(); + m_lines.moveDown(amount, select); + setSelection(m_lines.m_interactiveSelection); + + m_lines.ensureCursorVisible(); + } + + void TextEditor::Lines::moveDown(i32 amount, bool select) { auto oldPos = m_state.m_cursorPosition; if (amount < 0) { m_scrollYIncrement = 1.0; @@ -87,15 +148,29 @@ namespace hex::ui { return; } - m_state.m_cursorPosition.m_line = std::clamp(m_state.m_cursorPosition.m_line + amount, 0, (i32) m_lines.size() - 1); - if (oldPos.m_line == (i64) (m_lines.size() - 1)) { - m_topLine += amount; - m_topLine = std::clamp(m_topLine, 0.0F, m_lines.size() - 1.0F); - setTopLine(); + if (isLastLine(oldPos.m_line)) { + m_topRow += amount; + m_topRow = std::clamp(m_topRow, 0.0F, getGlobalRowMax()); + setFirstRow(); ensureCursorVisible(); return; } + auto row = lineIndexToRow(m_state.m_cursorPosition.m_line); + if (isMultiLineRow(row)) { + auto position = unfoldedToFoldedCoords(m_state.m_cursorPosition); + m_state.m_cursorPosition.m_column = position.m_column; + } + row += amount; + if (isMultiLineRow(row)) { + auto position = foldedToUnfoldedCoords(Coordinates( rowToLineIndex(row), m_state.m_cursorPosition.m_column)); + m_state.m_cursorPosition = position; + } else { + row = std::clamp(row, 0.0f, getGlobalRowMax()); + m_state.m_cursorPosition.m_line = rowToLineIndex(row); + } + + if (m_state.m_cursorPosition != oldPos) { if (select) { if (oldPos == m_interactiveSelection.m_end) @@ -108,37 +183,40 @@ namespace hex::ui { } } else m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition; - setSelection(m_interactiveSelection); - - ensureCursorVisible(); } } void TextEditor::moveLeft(i32 amount, bool select, bool wordMode) { - resetCursorBlinkTime(); + m_lines.resetCursorBlinkTime(); + m_lines.moveLeft(amount, select, wordMode); + setSelection(m_lines.m_interactiveSelection); + m_lines.ensureCursorVisible(); + } + void TextEditor::Lines::moveLeft(i32 amount, bool select, bool wordMode) { auto oldPos = m_state.m_cursorPosition; + auto foldedPos = unfoldedToFoldedCoords(oldPos); - - if (isEmpty() || oldPos < Coordinates(0, 0)) + if (isEmpty() || foldedPos < Coordinates( 0, 0) || foldedPos == Invalid) return; - auto lindex = m_state.m_cursorPosition.m_line; - auto lineMaxColumn = this->lineMaxColumn(lindex); - auto column = std::min(m_state.m_cursorPosition.m_column, lineMaxColumn); + auto lindex = foldedPos.m_line; + auto line = operator[](lindex); + auto lineMaxColumn = line.maxColumn(); + auto column = std::min(foldedPos.m_column, lineMaxColumn); + auto row = lineIndexToRow(lindex); while (amount-- > 0) { if (column == 0) { if (lindex == 0) m_state.m_cursorPosition = Coordinates(0, 0); - else { - lindex--; - m_state.m_cursorPosition = setCoordinates(lindex, -1); - } + else + m_state.m_cursorPosition = foldedToUnfoldedCoords(lineCoordinates(rowToLineIndex(row - 1), -1)); + } else if (wordMode) m_state.m_cursorPosition = findPreviousWord(m_state.m_cursorPosition); else - m_state.m_cursorPosition = Coordinates(lindex, column - 1); + m_state.m_cursorPosition = foldedToUnfoldedCoords(Coordinates( lindex, column - 1)); } if (select) { @@ -152,35 +230,39 @@ namespace hex::ui { } } else m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition; - - setSelection(m_interactiveSelection); - - ensureCursorVisible(); } void TextEditor::moveRight(i32 amount, bool select, bool wordMode) { - resetCursorBlinkTime(); + m_lines.resetCursorBlinkTime(); + m_lines.moveRight(amount, select, wordMode); + setSelection(m_lines.m_interactiveSelection); + m_lines.ensureCursorVisible(); + } + void TextEditor::Lines::moveRight(i32 amount, bool select, bool wordMode) { auto oldPos = m_state.m_cursorPosition; + auto foldedPos = unfoldedToFoldedCoords(oldPos); - if (isEmpty() || oldPos > setCoordinates(-1, -1)) + if (isEmpty() || foldedPos > lineCoordinates(-1, -1) || foldedPos == Invalid) return; - auto lindex = m_state.m_cursorPosition.m_line; - auto lineMaxColumn = this->lineMaxColumn(lindex); - auto column = std::min(m_state.m_cursorPosition.m_column, lineMaxColumn); + auto lindex = foldedPos.m_line; + auto line = operator[](lindex); + auto lineMaxColumn = line.maxColumn(); + auto column = std::min(foldedPos.m_column, lineMaxColumn); + auto row = lineIndexToRow(lindex); while (amount-- > 0) { - if (isEndOfLine(oldPos)) { - if (!isEndOfFile(oldPos)) { - lindex++; - m_state.m_cursorPosition = Coordinates(lindex, 0); - } else - m_state.m_cursorPosition = setCoordinates(-1, -1); + if (line.isEndOfLine(foldedPos.m_column)) { + if (isEndOfFile(foldedPos)) + m_state.m_cursorPosition = lineCoordinates(-1, -1); + else + m_state.m_cursorPosition = foldedToUnfoldedCoords(Coordinates(rowToLineIndex(row + 1), 0)); + } else if (wordMode) m_state.m_cursorPosition = findNextWord(m_state.m_cursorPosition); else - m_state.m_cursorPosition = Coordinates(lindex, column + 1); + m_state.m_cursorPosition = foldedToUnfoldedCoords(Coordinates(lindex, column + 1)); } if (select) { @@ -194,45 +276,54 @@ namespace hex::ui { } } else m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition; - - setSelection(m_interactiveSelection); - - ensureCursorVisible(); } void TextEditor::moveTop(bool select) { - resetCursorBlinkTime(); - auto oldPos = m_state.m_cursorPosition; - setCursorPosition(setCoordinates(0, 0), false); + m_lines.resetCursorBlinkTime(); + auto oldPos = m_lines.m_state.m_cursorPosition; + m_lines.setCursorPosition(m_lines.lineCoordinates(0, 0), false); - if (m_state.m_cursorPosition != oldPos) { + if (m_lines.m_state.m_cursorPosition != oldPos) { if (select) { - m_interactiveSelection = Range(m_state.m_cursorPosition, oldPos); + m_lines.m_interactiveSelection = Range(m_lines.m_state.m_cursorPosition, oldPos); } else - m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition; - setSelection(m_interactiveSelection); + m_lines.m_interactiveSelection.m_start = m_lines.m_interactiveSelection.m_end = m_lines.m_state.m_cursorPosition; + setSelection(m_lines.m_interactiveSelection); } } - void TextEditor::TextEditor::moveBottom(bool select) { - resetCursorBlinkTime(); + void TextEditor::moveBottom(bool select) { + m_lines.resetCursorBlinkTime(); auto oldPos = getCursorPosition(); - auto newPos = setCoordinates(-1, -1); - setCursorPosition(newPos, false); + auto newPos = m_lines.lineCoordinates(-1, -1); + m_lines.setCursorPosition(newPos, false); if (select) { - m_interactiveSelection = Range(oldPos, newPos); + m_lines.m_interactiveSelection = Range(oldPos, newPos); } else - m_interactiveSelection.m_start = m_interactiveSelection.m_end = newPos; - setSelection(m_interactiveSelection); + m_lines.m_interactiveSelection.m_start = m_lines.m_interactiveSelection.m_end = newPos; + setSelection(m_lines.m_interactiveSelection); } void TextEditor::moveHome(bool select) { - resetCursorBlinkTime(); - auto oldPos = m_state.m_cursorPosition; + m_lines.resetCursorBlinkTime(); + m_lines.moveHome(select); + setSelection(m_lines.m_interactiveSelection); + } - auto &line = m_lines[oldPos.m_line]; - auto prefix = line.substr(0, oldPos.m_column); - auto postfix = line.substr(oldPos.m_column); + void TextEditor::Lines::moveHome(bool select) { + auto oldPos = m_state.m_cursorPosition; + Coordinates foldedPos = oldPos; + auto row = lineIndexToRow(oldPos.m_line); + Line line; + + if (isMultiLineRow(row)) { + line = m_foldedLines[row].m_foldedLine; + foldedPos = unfoldedToFoldedCoords(oldPos); + } else + line = m_unfoldedLines[oldPos.m_line]; + + auto prefix = line.substr(0, foldedPos.m_column); + auto postfix = line.substr(foldedPos.m_column); if (prefix.empty() && postfix.empty()) return; i32 home; @@ -245,7 +336,7 @@ namespace hex::ui { else { postIdx = postfix.find_first_not_of(' '); if (postIdx == std::string::npos) - home = this->lineMaxColumn(oldPos.m_line); + home = lineMaxColumn(oldPos.m_line); else if (postIdx == 0) home = 0; else @@ -260,13 +351,15 @@ namespace hex::ui { else { postIdx = postfix.find_first_not_of(' '); if (postIdx == std::string::npos) - home = lineMaxColumn(oldPos.m_line); + home = lineMaxColumn(foldedPos.m_line); else - home = oldPos.m_column + postIdx; + home = foldedPos.m_column + postIdx; } } - - setCursorPosition(Coordinates(m_state.m_cursorPosition.m_line, home)); + if (isMultiLineRow(row)) { + setCursorPosition(foldedToUnfoldedCoords(Coordinates(foldedPos.m_line, home))); + } else + setCursorPosition(Coordinates(m_state.m_cursorPosition.m_line, home)); if (m_state.m_cursorPosition != oldPos) { if (select) { if (oldPos == m_interactiveSelection.m_start) @@ -279,14 +372,27 @@ namespace hex::ui { } } else m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition; - setSelection(m_interactiveSelection); } } void TextEditor::moveEnd(bool select) { - resetCursorBlinkTime(); + m_lines.resetCursorBlinkTime(); + m_lines.moveEnd(select); + setSelection(m_lines.m_interactiveSelection); + } + + void TextEditor::Lines::moveEnd(bool select) { auto oldPos = m_state.m_cursorPosition; - setCursorPosition(setCoordinates(m_state.m_cursorPosition.m_line, lineMaxColumn(oldPos.m_line))); + auto row = lineIndexToRow(oldPos.m_line); + if (isMultiLineRow(row)) { + auto position = unfoldedToFoldedCoords(m_state.m_cursorPosition); + m_state.m_cursorPosition.m_column = position.m_column; + setCursorPosition(foldedToUnfoldedCoords(Coordinates( position.m_line, lineMaxColumn(position.m_line)))); + } else { + row = std::clamp(row, 0.0f, getGlobalRowMax()); + m_state.m_cursorPosition.m_line = rowToLineIndex(row); + setCursorPosition(lineCoordinates(m_state.m_cursorPosition.m_line, lineMaxColumn(oldPos.m_line))); + } if (m_state.m_cursorPosition != oldPos) { if (select) { @@ -300,11 +406,10 @@ namespace hex::ui { } } else m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition; - setSelection(m_interactiveSelection); } } - void TextEditor::setScrollY() { + void TextEditor::Lines::setScrollY() { if (!m_withinRender) { m_setScrollY = true; return; @@ -316,7 +421,7 @@ namespace hex::ui { } void TextEditor::setScroll(ImVec2 scroll) { - if (!m_withinRender) { + if (!m_lines.m_withinRender) { m_scroll = scroll; m_setScroll = true; return; @@ -328,44 +433,42 @@ namespace hex::ui { } } - void TextEditor::setFocusAtCoords(const Coordinates &coords, bool scrollToCursor) { + void TextEditor::Lines::setFocusAtCoords(const Coordinates &coords, bool scrollToCursor) { m_focusAtCoords = coords; m_state.m_cursorPosition = coords; m_updateFocus = true; m_scrollToCursor = scrollToCursor; } + void TextEditor::Lines::setCursorPosition(const Coordinates &position, bool unfoldIfNeeded, bool scrollToCursor) { + if (m_state.m_cursorPosition != position) + m_state.m_cursorPosition = lineCoordinates(position); - void TextEditor::setCursorPosition(const Coordinates &position, bool scrollToCursor) { - if (m_state.m_cursorPosition != position) { - m_state.m_cursorPosition = position; - m_scrollToCursor = scrollToCursor; - if (scrollToCursor) - ensureCursorVisible(); - } + m_unfoldIfNeeded = unfoldIfNeeded; + m_focusAtCoords = m_state.m_cursorPosition; + m_scrollToCursor = scrollToCursor; + if (scrollToCursor) + ensureCursorVisible(); } - void TextEditor::setCursorPosition() { + void TextEditor::Lines::setCursorPosition() { 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(Lines &lines) { - bool TextEditor::Coordinates::isValid(TextEditor *editor) const { - - auto maxLine = editor->m_lines.size(); - if (std::abs(m_line) > (i32) maxLine) + auto maxLine = lines.size(); + if (std::abs(m_line) > maxLine) return false; - auto maxColumn = editor->lineMaxColumn(m_line); - return std::abs(m_column) <= maxColumn; + auto maxColumn = lines.lineMaxColumn(m_line); + if (std::abs(m_column) > maxColumn) + return false; + return true; } - TextEditor::Coordinates TextEditor::Coordinates::sanitize(TextEditor *editor) { + TextEditor::Coordinates TextEditor::Coordinates::sanitize(Lines &lines) { - i32 lineCount = editor->m_lines.size(); + i32 lineCount = lines.size(); if (m_line < 0) { m_line = std::clamp(m_line, -lineCount, -1); m_line = lineCount + m_line; @@ -373,7 +476,7 @@ namespace hex::ui { m_line = std::clamp(m_line, 0, lineCount - 1); - auto maxColumn = editor->lineMaxColumn(m_line) + 1; + auto maxColumn = lines.lineMaxColumn(m_line) + 1; if (m_column < 0) { m_column = std::clamp(m_column, -maxColumn, -1); m_column = maxColumn + m_column; @@ -383,37 +486,54 @@ namespace hex::ui { return *this; } - TextEditor::Coordinates TextEditor::setCoordinates(i32 line, i32 column) { + TextEditor::Range::Coordinates TextEditor::lineCoordinates(i32 lineIndex, i32 column) { + return m_lines.lineCoordinates(lineIndex, column); + } + + TextEditor::Range::Coordinates TextEditor::Lines::lineCoordinates(i32 lineIndex, i32 column) { if (isEmpty()) - return {0, 0}; - return {this, line, column}; + return Coordinates( 0, 0); + Coordinates result(lineIndex, column); + + return result.sanitize(*this); } - TextEditor::Coordinates TextEditor::setCoordinates(const Coordinates &value) { - auto sanitized_value = setCoordinates(value.m_line, value.m_column); - return sanitized_value; + TextEditor::Coordinates TextEditor::lineCoordinates(const Coordinates &value) { + return m_lines.lineCoordinates(value); } - TextEditor::Range TextEditor::setCoordinates(const Range &value) { - auto start = setCoordinates(value.m_start); - auto end = setCoordinates(value.m_end); + TextEditor::Coordinates TextEditor::Lines::lineCoordinates(const Coordinates &value) { + auto result = value; + + return result.sanitize(*this); + } + + TextEditor::Range TextEditor::lineCoordinates(const Range &value) { + return m_lines.lineCoordinates(value); + } + + TextEditor::Range TextEditor::Lines::lineCoordinates(const Range &value) { + auto start = lineCoordinates(value.m_start); + auto end = lineCoordinates(value.m_end); if (start == Invalid || end == Invalid) - return {Invalid, Invalid}; + return Range(Invalid, Invalid); if (start > end) { std::swap(start, end); } - return {start, end}; + return Range(start, end); } - void TextEditor::advance(Coordinates &coordinates) const { - if (isEndOfFile(coordinates)) + void TextEditor::advance(Coordinates &coordinates) { + if (m_lines.isEndOfFile(coordinates)) return; - if (isEndOfLine(coordinates)) { - ++coordinates.m_line; + if (m_lines.isEndOfLine(coordinates)) { + auto row = m_lines.lineIndexToRow(coordinates.m_line); + coordinates.m_line = m_lines.rowToLineIndex(row + 1); + coordinates.m_column = 0; return; } - auto &line = m_lines[coordinates.m_line]; + auto &line = m_lines.m_unfoldedLines[coordinates.m_line]; i64 column = coordinates.m_column; std::string lineChar = line[column]; auto incr = stringCharacterCount(lineChar); @@ -421,12 +541,12 @@ namespace hex::ui { } TextEditor::Coordinates TextEditor::findWordStart(const Coordinates &from) { - Coordinates at = setCoordinates(from); + Coordinates at = m_lines.lineCoordinates(from); if (at.m_line >= (i32) m_lines.size()) return at; - auto &line = m_lines[at.m_line]; - auto charIndex = lineCoordinatesToIndex(at); + auto &line = m_lines.m_unfoldedLines[at.m_line]; + auto charIndex = m_lines.lineCoordsIndex(at); bool found = false; while (charIndex > 0 && isWordChar(line.m_chars[charIndex - 1])) { @@ -439,16 +559,16 @@ namespace hex::ui { } while (!found && charIndex > 0 && isspace(line.m_chars[charIndex - 1])) --charIndex; - return getCharacterCoordinates(at.m_line, charIndex); + return m_lines.lineIndexCoords(at.m_line + 1, charIndex); } TextEditor::Coordinates TextEditor::findWordEnd(const Coordinates &from) { - Coordinates at = from; + Coordinates at = m_lines.lineCoordinates(from); if (at.m_line >= (i32) m_lines.size()) return at; - auto &line = m_lines[at.m_line]; - auto charIndex = lineCoordinatesToIndex(at); + auto &line = m_lines.m_unfoldedLines[at.m_line]; + auto charIndex = m_lines.lineCoordsIndex(at); bool found = false; while (charIndex < (i32) line.m_chars.size() && isWordChar(line.m_chars[charIndex])) { @@ -462,16 +582,16 @@ namespace hex::ui { while (!found && charIndex < (i32) line.m_chars.size() && isspace(line.m_chars[charIndex])) ++charIndex; - return getCharacterCoordinates(at.m_line, charIndex); + return m_lines.lineIndexCoords(at.m_line + 1, charIndex); } - TextEditor::Coordinates TextEditor::findNextWord(const Coordinates &from) { - Coordinates at = from; - if (at.m_line >= (i32) m_lines.size()) - return at; + TextEditor::Coordinates TextEditor::Lines::findNextWord(const Coordinates &from) { + Coordinates at = unfoldedToFoldedCoords(from); + if (at.m_line >= (i32) size()) + return from; - auto &line = m_lines[at.m_line]; - auto charIndex = lineCoordinatesToIndex(at); + auto &line = operator[](at.m_line); + auto charIndex = line.columnIndex(at.m_column); while (charIndex < (i32) line.m_chars.size() && isspace(line.m_chars[charIndex])) ++charIndex; @@ -483,16 +603,16 @@ namespace hex::ui { while (!found && charIndex < (i32) line.m_chars.size() && (ispunct(line.m_chars[charIndex]))) ++charIndex; - return getCharacterCoordinates(at.m_line, charIndex); + return foldedToUnfoldedCoords(lineIndexCoords(at.m_line + 1, charIndex)); } - TextEditor::Coordinates TextEditor::findPreviousWord(const Coordinates &from) { - Coordinates at = from; - if (at.m_line >= (i32) m_lines.size()) - return at; + TextEditor::Coordinates TextEditor::Lines::findPreviousWord(const Coordinates &from) { + Coordinates at = unfoldedToFoldedCoords(from); + if (at.m_line >= (i32) size()) + return from; - auto &line = m_lines[at.m_line]; - auto charIndex = lineCoordinatesToIndex(at); + auto &line = operator[](at.m_line); + auto charIndex = line.columnIndex(at.m_column); bool found = false; while (charIndex > 0 && isspace(line.m_chars[charIndex - 1])) @@ -505,39 +625,51 @@ namespace hex::ui { while (!found && charIndex > 0 && ispunct(line.m_chars[charIndex - 1])) --charIndex; - return getCharacterCoordinates(at.m_line, charIndex); + return foldedToUnfoldedCoords(lineIndexCoords(at.m_line + 1, charIndex)); } - u32 TextEditor::skipSpaces(const Coordinates &from) { - auto line = from.m_line; - if (line >= (i64) m_lines.size()) - return 0; - auto &lines = m_lines[line].m_chars; - auto &colors = m_lines[line].m_colors; - auto charIndex = lineCoordinatesToIndex(from); + u32 TextEditor::Line::skipSpaces(i32 index) { + auto charIndex = index; u32 s = 0; - while (charIndex < (i32) lines.size() && lines[charIndex] == ' ' && colors[charIndex] == 0x00) { + while (charIndex < (i32) m_chars.size() && m_chars[charIndex] == ' ' && m_colors[charIndex] == 0x00) { ++s; ++charIndex; } - if (m_updateFocus) - setFocus(); return s; } - bool TextEditor::MatchedBracket::checkPosition(TextEditor *editor, const Coordinates &from) { + u32 TextEditor::Lines::skipSpaces(const Coordinates &from) { auto lineIndex = from.m_line; - auto line = editor->m_lines[lineIndex].m_chars; - auto colors = editor->m_lines[lineIndex].m_colors; + if (lineIndex >= (i64) size()) + return 0; + auto line = m_unfoldedLines[lineIndex]; + auto charIndex = lineCoordsIndex(lineCoordinates(lineIndex, from.m_column)); + return line.skipSpaces(charIndex); + } + + bool TextEditor::MatchedDelimiter::setNearCursor(Lines *lines,const Coordinates &from) { + Coordinates fromCopy = from; + if (coordinatesNearDelimiter(lines, fromCopy)) { + m_nearCursor = fromCopy; + return true; + } + return false; + } + + bool TextEditor::MatchedDelimiter::checkPosition(Lines *lines, Coordinates &from) { + auto start = lines->lineCoordinates(from); + auto lineIndex = start.m_line; + auto line = lines->m_unfoldedLines[lineIndex].m_chars; + auto colors = lines->m_unfoldedLines[lineIndex].m_colors; if (!line.empty() && colors.empty()) return false; - auto result = editor->lineCoordinatesToIndex(from); + auto result = lines->lineCoordsIndex(start); auto character = line[result]; auto color = colors[result]; if ((s_separators.find(character) != std::string::npos && (static_cast(color) == PaletteIndex::Separator)) || (static_cast(color) == PaletteIndex::WarningText) || (s_operators.find(character) != std::string::npos && (static_cast(color) == PaletteIndex::Operator)) || (static_cast(color) == PaletteIndex::WarningText)) { - if (m_nearCursor != editor->getCharacterCoordinates(lineIndex, result)) { - m_nearCursor = editor->getCharacterCoordinates(lineIndex, result); + if (from != lines->lineIndexCoords(lineIndex + 1, result)) { + from = lines->lineIndexCoords(lineIndex + 1, result); m_changed = true; } m_active = true; @@ -546,127 +678,150 @@ namespace hex::ui { return false; } - i32 TextEditor::MatchedBracket::detectDirection(TextEditor *editor, const Coordinates &from) { - std::string brackets = "()[]{}<>"; + i32 TextEditor::MatchedDelimiter::detectDirection(Lines *lines, const Coordinates &from) { + auto start = lines->lineCoordinates(from); + std::string delimiters = "()[]{}<>"; i32 result = -2; // dont check either - auto start = editor->setCoordinates(from); - if (start == TextEditor::Invalid) + + if (start == Invalid) return result; auto lineIndex = start.m_line; - auto line = editor->m_lines[lineIndex].m_chars; - auto charIndex = editor->lineCoordinatesToIndex(start); + auto line = lines->m_unfoldedLines[lineIndex].m_chars; + auto charIndex = lines->lineCoordsIndex(start); auto ch2 = line[charIndex]; - auto idx2 = brackets.find(ch2); + auto idx2 = delimiters.find(ch2); if (charIndex == 0) {// no previous character - if (idx2 == std::string::npos) // no brackets + if (idx2 == std::string::npos) // no delimiters return -2;// dont check either else return 1; // check only current }// charIndex > 0 auto ch1 = line[charIndex - 1]; - auto idx1 = brackets.find(ch1); - if (idx1 == std::string::npos && idx2 == std::string::npos) // no brackets + auto idx1 = delimiters.find(ch1); + if (idx1 == std::string::npos && idx2 == std::string::npos) // no delimiters return -2; if (idx1 != std::string::npos && idx2 != std::string::npos) { - if (idx1 % 2) // closing bracket + any bracket + if (idx1 % 2) // closing delimiter + any delimiter return -1; // check both and previous first - else if (!(idx1 % 2) && !(idx2 % 2)) // opening bracket + opening bracket + else if (!(idx1 % 2) && !(idx2 % 2)) // opening delimiter + opening delimiter return 0; // check both and current first - } else if (idx1 != std::string::npos) // only first bracket + } else if (idx1 != std::string::npos) // only first delimiter return 1; // check only previous - else // only second bracket + else // only second delimiter return 2; // check only current return result; } - bool TextEditor::MatchedBracket::isNearABracket(TextEditor *editor, const Coordinates &from) { - if (editor->isEmpty()) + bool TextEditor::MatchedDelimiter::coordinatesNearDelimiter(Lines *lines, Coordinates &from) { + auto start = lines->lineCoordinates(from); + if (lines->isEmpty()) return false; - auto start = editor->setCoordinates(from); - if (start == TextEditor::Invalid) + + if (start == Invalid) return false; auto lineIndex = start.m_line; - auto charIndex = editor->lineCoordinatesToIndex(start); - auto direction1 = detectDirection(editor, start); - auto charCoords = editor->getCharacterCoordinates(lineIndex, charIndex); + auto charIndex = lines->lineCoordsIndex(start); + auto direction1 = detectDirection(lines, start); + auto charCoords = lines->lineIndexCoords(lineIndex + 1, charIndex); i32 direction2 = 1; if (direction1 == -1 || direction1 == 1) { - if (checkPosition(editor, editor->setCoordinates(charCoords.m_line, charCoords.m_column - 1))) + Coordinates position = lines->lineCoordinates(charCoords.m_line, charCoords.m_column - 1); + if (checkPosition(lines, position)) { + from = position; return true; + } if (direction1 == -1) direction2 = 0; } else if (direction1 == 2 || direction1 == 0) { - if (checkPosition(editor, charCoords)) + if (checkPosition(lines, charCoords)) return true; if (direction1 == 0) direction2 = -1; } if (direction2 != 1) { - if (checkPosition(editor, editor->setCoordinates(charCoords.m_line, charCoords.m_column + direction2))) + auto position = lines->lineCoordinates(charCoords.m_line, charCoords.m_column + direction2); + if (checkPosition(lines, position)) { + from = position; return true; + } } u64 result; - auto strLine = editor->m_lines[lineIndex].m_chars; + auto strLine = lines->m_unfoldedLines[lineIndex].m_chars; if (charIndex == 0) { - if (strLine[0] == ' ') { + if (strLine[0] == ' ') result = std::string::npos; - } else { + else result = 0; - } - } else { + } else result = strLine.find_last_not_of(' ', charIndex - 1); - } if (result != std::string::npos) { - auto resultCoords = editor->getCharacterCoordinates(lineIndex, result); - if (checkPosition(editor, resultCoords)) + auto resultCoords = lines->lineIndexCoords(lineIndex + 1, result); + if (checkPosition(lines, resultCoords)) { + from = resultCoords; return true; + } } result = strLine.find_first_not_of(' ', charIndex); if (result != std::string::npos) { - auto resultCoords = editor->getCharacterCoordinates(lineIndex, result); - if (checkPosition(editor, resultCoords)) + auto resultCoords = lines->lineIndexCoords(lineIndex + 1, result); + if (checkPosition(lines, resultCoords)) { + from = resultCoords; return true; + } } if (isActive()) { - editor->m_lines[m_nearCursor.m_line].m_colorized = false; - editor->m_lines[m_matched.m_line].m_colorized = false; + lines->m_unfoldedLines[m_nearCursor.m_line].m_colorized = false; + lines->m_unfoldedLines[m_matched.m_line].m_colorized = false; m_active = false; - editor->colorize(); + lines->colorize(); } return false; } - void TextEditor::MatchedBracket::findMatchingBracket(TextEditor *editor) { - auto from = editor->setCoordinates(m_nearCursor); - if (from == TextEditor::Invalid) { + TextEditor::Coordinates TextEditor::MatchedDelimiter::findMatchingDelimiter(Lines *lines, Coordinates &from, bool folded) { + Coordinates start; + Coordinates result = Invalid; + if (!folded) + start = from; + else + start = lines->unfoldedToFoldedCoords(lines->lineCoordinates(from)); + if (start == Invalid) { m_active = false; - return; + return result; + } + result = start; + auto lineIndex = start.m_line; + auto row = lines->lineIndexToRow(lineIndex); + auto maxLineIndex = (i32) lines->size() - 1; + auto charIndex = lines->lineCoordsIndex(start); + std::string line; + std::string colors; + if (!folded) { + line = lines->m_unfoldedLines[lineIndex].m_chars; + colors = lines->m_unfoldedLines[lineIndex].m_colors; + } else { + line = lines->operator[](lineIndex).m_chars; + colors = lines->operator[](lineIndex).m_colors; } - m_matched = from; - auto lineIndex = from.m_line; - auto maxLineIndex = editor->m_lines.size() - 1; - auto charIndex = editor->lineCoordinatesToIndex(from); - std::string line = editor->m_lines[lineIndex].m_chars; - std::string colors = editor->m_lines[lineIndex].m_colors; if (!line.empty() && colors.empty()) { m_active = false; - return; + return Invalid; } - std::string brackets = "()[]{}<>"; - char bracketChar = line[charIndex]; + std::string delimiters = "()[]{}<>"; + char delimiterChar = line[charIndex]; char color1; - auto idx = brackets.find_first_of(bracketChar); + auto idx = delimiters.find_first_of(delimiterChar); if (idx == std::string::npos) { if (m_active) { m_active = false; - editor->colorize(); + lines->colorize(); } - return; + return Invalid; } - auto bracketChar2 = brackets[idx ^ 1]; - brackets = bracketChar; - brackets += bracketChar2; + auto delimiterChar2 = delimiters[idx ^ 1]; + delimiters = delimiterChar; + delimiters += delimiterChar2; i32 direction = 1 - 2 * (idx % 2); if (idx > 5) color1 = static_cast(PaletteIndex::Operator); @@ -675,35 +830,51 @@ namespace hex::ui { char color = static_cast(PaletteIndex::WarningText); i32 depth = 1; if (charIndex == (i64) (line.size() - 1) * (1 + direction) / 2) { - if (lineIndex == (i64) maxLineIndex * (1 + direction) / 2) { + if (row + direction < 0 || lines->rowToLineIndex(row + direction) > (i64) maxLineIndex) { m_active = false; - return; + return Invalid; + } + if (!folded) { + if (lineIndex += direction; lineIndex < 0 || lineIndex > (i64) maxLineIndex) { + m_active = false; + return Invalid; + } + } else { + row += direction; + if(lineIndex = lines->rowToLineIndex(row); lineIndex <0 || lineIndex > (i64) maxLineIndex) { + m_active = false; + return Invalid; + } + } + if (!folded) { + line = lines->m_unfoldedLines[lineIndex].m_chars; + colors = lines->m_unfoldedLines[lineIndex].m_colors; + } else { + line = lines->operator[](lineIndex).m_chars; + colors = lines->operator[](lineIndex).m_colors; } - lineIndex += direction; - line = editor->m_lines[lineIndex].m_chars; - colors = editor->m_lines[lineIndex].m_colors; if (!line.empty() && colors.empty()) { m_active = false; - return; + return Invalid; } charIndex = (line.size() - 1) * (1 - direction) / 2 - direction; } for (i32 i = charIndex + direction;; i += direction) { if (direction == 1) - idx = line.find_first_of(brackets, i); + idx = line.find_first_of(delimiters, i); else - idx = line.find_last_of(brackets, i); + idx = line.find_last_of(delimiters, i); if (idx != std::string::npos) { - if (line[idx] == bracketChar && (colors[idx] == color || colors[idx] == color1)) { + if (line[idx] == delimiterChar && (colors[idx] == color || colors[idx] == color1)) { ++depth; i = idx; - } else if ((line[idx] == bracketChar2 && (colors[idx] == color)) || colors[idx] == color1) { + } else if ((line[idx] == delimiterChar2 && (colors[idx] == color)) || colors[idx] == color1) { --depth; if (depth == 0) { - if (m_matched != editor->getCharacterCoordinates(lineIndex, idx)) { - m_matched = editor->getCharacterCoordinates(lineIndex, idx); - m_changed = true; - } + if (!folded) + result = lines->lineIndexCoords(lineIndex + 1, idx); + else + result = lines->foldedToUnfoldedCoords( lines->lineIndexCoords(lineIndex + 1, idx)); m_active = true; break; } @@ -717,32 +888,73 @@ namespace hex::ui { else i = 0; } - if ((direction * i) >= (i32) ((line.size() - 1) * (1 + direction) / 2)) { - if (lineIndex == (i64) maxLineIndex * (1 + direction) / 2) { + if ((i32) (direction * i) >= (i32) ((line.size() - 1) * (1 + direction) / 2)) { + if (lines->rowToLineIndex(row) == (i64) maxLineIndex * (1 + direction) / 2) { if (m_active) { m_active = false; m_changed = true; } break; } else { - lineIndex += direction; - line = editor->m_lines[lineIndex].m_chars; - colors = editor->m_lines[lineIndex].m_colors; + if (!folded) { + lineIndex += direction; + } else { + row += direction; + lineIndex = lines->rowToLineIndex(row); + } + if (lineIndex < 0 || lineIndex >= (i64) lines->size()) { + if (m_active) { + m_active = false; + m_changed = true; + } + return Invalid; + } + if (!folded) { + line = lines->m_unfoldedLines[lineIndex].m_chars; + colors = lines->m_unfoldedLines[lineIndex].m_colors; + } else { + line = lines->operator[](lineIndex).m_chars; + colors = lines->operator[](lineIndex).m_colors; + } if (!line.empty() && colors.empty()) { m_active = false; - return; + return Invalid; } i = (line.size() - 1) * (1 - direction) / 2 - direction; } } } - if (hasChanged()) { - editor->m_lines[m_nearCursor.m_line].m_colorized = false; - editor->m_lines[m_matched.m_line].m_colorized = false; + return result; + } - editor->colorize(); - m_changed = false; + void TextEditor::MatchedDelimiter::findMatchingDelimiter(Lines *lines, bool folded) { + Coordinates from = m_nearCursor; + Coordinates result = findMatchingDelimiter(lines, from, folded); + if (result != Invalid) { + if (result != m_matched || hasChanged()) { + m_matched = result; + auto mls = (i32) lines->m_unfoldedLines.size(); + auto mline = m_matched.m_line; + auto nline = m_nearCursor.m_line; + if (mline < 0 || mline >= mls || nline < 0 || nline >= mls) { + m_active = false; + return; + } + i32 mcs = lines->m_unfoldedLines[mline].size(); + i32 ncs = lines->m_unfoldedLines[nline].size(); + auto mcol = m_matched.m_column; + auto ncol = m_nearCursor.m_column; + if (mcol < 0 || mcol >= mcs || ncol < 0 || ncol >= ncs) { + m_active = false; + return; + } + lines->m_unfoldedLines[m_nearCursor.m_line].m_colorized = false; + lines->m_unfoldedLines[m_matched.m_line].m_colorized = false; + + lines->colorize(); + m_changed = false; + } } } } diff --git a/plugins/ui/source/ui/text_editor/render.cpp b/plugins/ui/source/ui/text_editor/render.cpp index 3489f4a80..8fe6b99b6 100644 --- a/plugins/ui/source/ui/text_editor/render.cpp +++ b/plugins/ui/source/ui/text_editor/render.cpp @@ -1,62 +1,154 @@ #include "imgui.h" #include "fonts/fonts.hpp" #include +#include +#include +#include #include namespace hex::ui { TextEditor::Palette s_paletteBase = TextEditor::getDarkPalette(); - inline void TextUnformattedColoredAt(const ImVec2 &pos, const ImU32 &color, const char *text) { - ImGui::SetCursorScreenPos(pos); + + inline void TextUnformattedColored(const ImU32 &color, const char *text) { ImGui::PushStyleColor(ImGuiCol_Text, color); ImGui::TextUnformatted(text); ImGui::PopStyleColor(); } + inline void TextUnformattedColoredAt(const ImVec2 &pos, const ImU32 &color, const char *text) { + ImGui::SetCursorScreenPos(pos); + TextUnformattedColored(color, text); + } + + void TextEditor::Line::print(i32 lineIndex, i32 maxLineIndex, std::optional position) { + u32 idx = 0; + std::string lineNumberStr = std::to_string(lineIndex + 1); + auto padding = std::string(std::to_string(maxLineIndex + 1).size() - lineNumberStr.size(),' '); + lineNumberStr = " " + lineNumberStr + padding + " "; + auto drawList = ImGui::GetCurrentWindow()->DrawList; + drawList->AddRectFilled(ImGui::GetCursorScreenPos(),ImVec2(ImGui::GetCursorScreenPos().x + ImGui::CalcTextSize(lineNumberStr.c_str()).x,ImGui::GetCursorScreenPos().y + ImGui::GetTextLineHeightWithSpacing()),ImGui::ColorConvertFloat4ToU32(ImGui::GetStyle().Colors[ImGuiCol_MenuBarBg])); + drawList->AddLine(ImVec2(ImGui::GetCursorScreenPos().x + ImGui::CalcTextSize(lineNumberStr.c_str()).x,ImGui::GetCursorScreenPos().y),ImVec2(ImGui::GetCursorScreenPos().x + ImGui::CalcTextSize(lineNumberStr.c_str()).x,ImGui::GetCursorScreenPos().y + ImGui::GetTextLineHeightWithSpacing()),ImGui::ColorConvertFloat4ToU32(ImGui::GetStyle().Colors[ImGuiCol_Border])); + if (position.has_value()) { + TextUnformattedColoredAt(*position, TextEditor::m_palette[(i32) PaletteIndex::LineNumber], (lineNumberStr+" ").c_str()); + position->x += ImGui::CalcTextSize(lineNumberStr.c_str()).x; + } else { + TextUnformattedColored(TextEditor::m_palette[(i32) PaletteIndex::LineNumber], (lineNumberStr+ " ").c_str()); + ImGui::SameLine(); + } + while (idx < m_chars.size()) { + u8 color = idx < m_colors.size() ? (u8) m_colors[idx] : 0; + auto colorIdx = m_colors.find_first_not_of((char) color, idx); + u32 wordSize; + if (colorIdx == std::string::npos) + wordSize = m_colors.size() - idx; + else + wordSize = colorIdx - idx; + if (position.has_value()) { + TextUnformattedColoredAt(*position, TextEditor::m_palette[color], substr(idx, wordSize).c_str()); + position->x += ImGui::CalcTextSize(substr(idx, wordSize).c_str()).x; + } else { + TextUnformattedColored(TextEditor::m_palette[color], substr(idx, wordSize).c_str()); + ImGui::SameLine(); + } + idx += wordSize; + if (wordSize == 0 && !m_colorized) + break; + else if (wordSize == 0) + idx++; + } + } + void TextEditor::setTopMarginChanged(i32 newMargin) { m_newTopMargin = newMargin; m_topMarginChanged = true; } void TextEditor::clearErrorMarkers() { + m_lines.clearErrorMarkers(); + } + + void TextEditor::Lines::clearErrorMarkers() { m_errorMarkers.clear(); m_errorHoverBoxes.clear(); } - void TextEditor::clearActionables() { + void TextEditor::Lines::clearCodeFolds() { + m_codeFolds.clear(); + m_codeFoldKeys.clear(); + } + + void TextEditor::Lines::clearActionables() { clearErrorMarkers(); clearGotoBoxes(); clearCursorBoxes(); } - ImVec2 TextEditor::underwaves(ImVec2 pos, u32 nChars, ImColor color, const ImVec2 &size_arg) { + bool TextEditor::Lines::lineNeedsDelimiter(i32 lineIndex) { + auto row = lineIndexToRow(lineIndex); + if (row == -1.0 || !m_foldedLines.contains(row)) { + if (lineIndex >= (i64)m_unfoldedLines.size() || lineIndex < 0) + return false; + auto line = m_unfoldedLines[lineIndex].m_chars; + if (line.empty()) + return false; + + for (auto keys : m_codeFoldKeys) { + if (keys.m_start.m_line == lineIndex && m_codeFoldDelimiters.contains(keys)) { + auto delimiter = m_codeFoldDelimiters[keys].first; + if (!delimiter || (delimiter != '(' && delimiter != '[' && delimiter != '{' && delimiter != '<')) + return false; + return !line.contains(delimiter); + } + } + return !line.ends_with('{'); + } + return m_foldedLines[row].firstLineNeedsDelimiter(); + } + + bool TextEditor::FoldedLine::firstLineNeedsDelimiter() { + return ((u8)m_type & (u8)FoldedLine::FoldType::FirstLineNeedsDelimiter); + } + + bool TextEditor::FoldedLine::addsLastLineToFold() { + return (u8)m_type & (u8)FoldedLine::FoldType::AddsLastLine; + } + + bool TextEditor::FoldedLine::addsFullFirstLineToFold() { + return (u8)m_type & (u8)FoldedLine::FoldType::AddsFirstLine; + } + + ImVec2 TextEditor::underWavesAt(ImVec2 pos, i32 nChars, ImColor color, const ImVec2 &size_arg) { ImGui::GetStyle().AntiAliasedLines = false; ImGuiWindow *window = ImGui::GetCurrentWindow(); window->DC.CursorPos = pos; const ImVec2 label_size = ImGui::CalcTextSize("W", nullptr, true); ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x, label_size.y); float lineWidth = size.x / 3.0f + 0.5f; - float halfLineW = lineWidth / 2.0f; + constexpr i32 segmentEndCount = 4; + constexpr i32 segmentCount = segmentEndCount - 1; + constexpr float signMultiplier = -1.0f; + ImVec2 segment[segmentEndCount]; - for (u32 i = 0; i < nChars; i++) { + for (i32 i = 0; i < nChars; i++) { pos = window->DC.CursorPos; float lineY = pos.y + size.y; + auto sign = signMultiplier; + for (i32 j = 0; j < segmentEndCount; j++) { + sign *= signMultiplier; + segment[j] = ImVec2(pos.x + j * lineWidth, lineY + sign * lineWidth / 2.0f); + } - ImVec2 pos1_1 = ImVec2(pos.x + 0 * lineWidth, lineY + halfLineW); - ImVec2 pos1_2 = ImVec2(pos.x + 1 * lineWidth, lineY - halfLineW); - ImVec2 pos2_1 = ImVec2(pos.x + 2 * lineWidth, lineY + halfLineW); - ImVec2 pos2_2 = ImVec2(pos.x + 3 * lineWidth, lineY - halfLineW); - - ImGui::GetWindowDrawList()->AddLine(pos1_1, pos1_2, ImU32(color), 0.4f); - ImGui::GetWindowDrawList()->AddLine(pos1_2, pos2_1, ImU32(color), 0.4f); - ImGui::GetWindowDrawList()->AddLine(pos2_1, pos2_2, ImU32(color), 0.4f); + for (i32 j = 0; j < segmentCount; j++) { + ImGui::GetWindowDrawList()->AddLine(segment[j], segment[j + 1], ImU32(color), 0.4f); + } window->DC.CursorPos = ImVec2(pos.x + size.x, pos.y); } - auto ret = window->DC.CursorPos; - ret.y += size.y; - return ret; + auto result = window->DC.CursorPos; + result.y += size.y; + return result; } void TextEditor::setTabSize(i32 value) { @@ -64,80 +156,162 @@ namespace hex::ui { } float TextEditor::getPageSize() const { - return ImGui::GetCurrentWindow()->InnerClipRect.GetHeight() / m_charAdvance.y; + return ImGui::GetCurrentWindow()->InnerClipRect.GetHeight() / m_lines.m_charAdvance.y; } - bool TextEditor::isEndOfLine() const { + bool TextEditor::Lines::isEndOfLine() { return isEndOfLine(m_state.m_cursorPosition); } - bool TextEditor::isStartOfLine() const { + bool TextEditor::Lines::isStartOfLine() const { return m_state.m_cursorPosition.m_column == 0; } - bool TextEditor::isEndOfLine(const Coordinates &coordinates) const { - if (coordinates.m_line < (i32) m_lines.size()) - return coordinates.m_column >= stringCharacterCount(m_lines[coordinates.m_line].m_chars); + bool TextEditor::Line::isEndOfLine(i32 column) { + return column >= maxColumn(); + } + + bool TextEditor::Lines::isEndOfLine(const Coordinates &coordinates) { + if (coordinates.m_line < (i32) size()) + return m_unfoldedLines[coordinates.m_line].isEndOfLine(coordinates.m_column); + return true; } - bool TextEditor::isEndOfFile(const Coordinates &coordinates) const { - if (coordinates.m_line < (i32) m_lines.size()) - return coordinates.m_line >= (i32) m_lines.size() - 1 && isEndOfLine(coordinates); + bool TextEditor::Lines::isEndOfFile(const Coordinates &coordinates) { + if (coordinates.m_line < (i32) size()) + return isLastLine(coordinates.m_line) && isEndOfLine(coordinates); + return true; } - void TextEditor::setTopLine() { + bool TextEditor::Lines::isLastLine() { + return isLastLine(m_state.m_cursorPosition.m_line); + } + + bool TextEditor::Lines::isLastLine(i32 lineIndex) { + auto row = lineIndexToRow(lineIndex); + return row == getMaxDisplayedRow(); + } + + void TextEditor::Lines::setFirstRow() { if (!m_withinRender) { - m_setTopLine = true; + m_setTopRow = true; return; } else { - m_setTopLine = false; - ImGui::SetScrollY(m_topLine * m_charAdvance.y); - ensureCursorVisible(); + m_setTopRow = false; + ImGui::SetScrollY(m_topRow * m_charAdvance.y); } } - void TextEditor::render(const char *title, const ImVec2 &size, bool border) { - m_withinRender = true; + float TextEditor::Lines::getMaxDisplayedRow() { + auto maxRow = getGlobalRowMax(); + if (maxRow - m_topRow < m_numberOfLinesDisplayed) + return maxRow; + return m_topRow + m_numberOfLinesDisplayed; + } - if (m_lines.capacity() < 2 * m_lines.size()) - m_lines.reserve(2 * m_lines.size()); + float TextEditor::Lines::getGlobalRowMax() { + auto maxRow = size() - 1.0f; + std::vector spanningIntervals; + if (m_codeFoldKeys.empty()) + return std::floor(maxRow); + for (auto key1 = m_codeFoldKeys.begin(); key1 != m_codeFoldKeys.end(); ++key1) { + auto key2 = std::next(key1); + while (key2 != m_codeFoldKeys.end() && (!key2->contains(*key1) || (m_codeFoldState.contains(*key2) && m_codeFoldState[*key2]))) { + ++key2; + } + if (key2 == m_codeFoldKeys.end()) + spanningIntervals.push_back(*key1); + } + + for (auto key: spanningIntervals) { + if (m_codeFoldState.contains(key) && !m_codeFoldState[key]) { + if ((key.m_end.m_line - key.m_start.m_line - 1) < maxRow) { + maxRow -= ((key.m_end.m_line - key.m_start.m_line)); + } else + break; + } else + m_codeFoldState[key] = true; + } + return std::floor(maxRow); + } + + float TextEditor::getMaxLineNumber() { + float maxLineNumber = std::min(m_lines.size() - 2.0f, m_lines.m_topRow + m_lines.m_numberOfLinesDisplayed); + + if (maxLineNumber == m_lines.size() - 2.0f || m_lines.m_codeFoldKeys.empty()) + return std::floor(maxLineNumber + 1.0f); + float currentLineNumber = m_topLineNumber; + for (auto range: m_lines.m_codeFoldKeys) { + + if (m_lines.m_codeFoldState.contains(range) && m_lines.m_codeFoldState[range] == false) { + + if ((range.m_start.m_line - currentLineNumber) < maxLineNumber) + maxLineNumber += ((range.m_end.m_line - range.m_start.m_line)); + else + break; + } else + m_lines.m_codeFoldState[range] = true; + currentLineNumber = range.m_end.m_line + 1; + } + return maxLineNumber + 1.0f; + } + + float TextEditor::getTopLineNumber() { + float result = m_lines.m_topRow; + for (auto interval: m_lines.m_codeFoldKeys) { + + if (interval.m_start.m_line > result) + break; + if (m_lines.m_codeFoldState.contains(interval) && m_lines.m_codeFoldState[interval] == false) + result += (interval.m_end.m_line- interval.m_start.m_line); + else + m_lines.m_codeFoldState[interval] = true; + } + return result; + } + + void TextEditor::render(const char *title, const ImVec2 &size, bool border) { + m_lines.m_title = title; + if (m_lines.m_unfoldedLines.capacity() < 2u * m_lines.size()) + m_lines.m_unfoldedLines.reserve(2 * m_lines.size()); auto scrollBg = ImGui::GetStyleColorVec4(ImGuiCol_ScrollbarBg); scrollBg.w = 0.0f; auto scrollBarSize = ImGui::GetStyle().ScrollbarSize; - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(m_palette[(i32) PaletteIndex::Background])); + ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImGui::ColorConvertFloat4ToU32(scrollBg)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarRounding, 0); ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, scrollBarSize); - auto position = ImGui::GetCursorScreenPos(); + m_lines.m_lineNumbersStartPos = ImGui::GetCursorScreenPos(); if (m_showLineNumbers) { - std::string lineNumber = " " + std::to_string(m_lines.size()) + " "; - m_lineNumberFieldWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, lineNumber.c_str(), nullptr, nullptr).x + m_leftMargin; - ImGui::SetNextWindowPos(position); - ImGui::SetCursorScreenPos(position); - auto lineNoSize = ImVec2(m_lineNumberFieldWidth, size.y); - if (!m_ignoreImGuiChild) { - ImGui::BeginChild("##lineNumbers", lineNoSize, false, ImGuiWindowFlags_NoScrollbar); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyle().Colors[ImGuiCol_MenuBarBg]); + std::string lineNumberStr = std::to_string(m_lines.size()) + " "; + m_lines.m_lineNumberFieldWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, lineNumberStr.c_str(),nullptr, nullptr).x + 2 * m_lines.m_charAdvance.x; + ImGui::SetNextWindowPos(m_lines.m_lineNumbersStartPos); + ImGui::SetCursorScreenPos(m_lines.m_lineNumbersStartPos); + auto lineNumberSize = ImVec2(m_lines.m_lineNumberFieldWidth, size.y); + if (!m_lines.m_ignoreImGuiChild) { + ImGui::BeginChild("##lineNumbers", lineNumberSize, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); ImGui::EndChild(); } + ImGui::PopStyleColor(); } else { - m_lineNumberFieldWidth = 0; + m_lines.m_lineNumberFieldWidth = 0; } ImVec2 textEditorSize = size; - textEditorSize.x -= m_lineNumberFieldWidth; - - bool scroll_x = m_longestLineLength * m_charAdvance.x >= textEditorSize.x; - + textEditorSize.x -= m_lines.m_lineNumberFieldWidth; + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(m_palette[(i32) PaletteIndex::Background])); + bool scroll_x = m_longestDrawnLineLength * m_lines.m_charAdvance.x >= textEditorSize.x; bool scroll_y = m_lines.size() > 1; - ImGui::SetCursorScreenPos(ImVec2(position.x + m_lineNumberFieldWidth, position.y)); + ImGui::SetCursorScreenPos(ImVec2(m_lines.m_lineNumbersStartPos.x + m_lines.m_lineNumberFieldWidth, m_lines.m_lineNumbersStartPos.y)); ImGuiChildFlags childFlags = border ? ImGuiChildFlags_Borders : ImGuiChildFlags_None; ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove; - if (!m_ignoreImGuiChild) + if (!m_lines.m_ignoreImGuiChild) ImGui::BeginChild(title, textEditorSize, childFlags, windowFlags); auto window = ImGui::GetCurrentWindow(); window->ScrollbarSizes = ImVec2(scrollBarSize * scroll_x, scrollBarSize * scroll_y); @@ -152,7 +326,7 @@ namespace hex::ui { ImGui::Scrollbar(ImGuiAxis_X); ImGui::GetCurrentWindow()->ScrollbarX = false; } - + ImGui::PopStyleColor(); if (m_handleKeyboardInputs) { handleKeyboardInputs(); } @@ -160,22 +334,57 @@ namespace hex::ui { if (m_handleMouseInputs) handleMouseInputs(); + m_lines.colorizeInternal(); + renderText(textEditorSize); - colorizeInternal(); - renderText(title, position, textEditorSize); - - if (!m_ignoreImGuiChild) + if (!m_lines.m_ignoreImGuiChild) ImGui::EndChild(); ImGui::PopStyleVar(3); - ImGui::PopStyleColor(2); + ImGui::PopStyleColor(); - m_withinRender = false; - ImGui::SetCursorScreenPos(ImVec2(position.x, position.y + size.y - 1)); + ImGui::SetCursorScreenPos(ImVec2(m_lines.m_lineNumbersStartPos.x, m_lines.m_lineNumbersStartPos.y + size.y - 1)); ImGui::Dummy({}); } - void TextEditor::ensureCursorVisible() { + void TextEditor::Lines::ensureSelectionNotFolded() { + auto selectionStart = m_state.m_selection.m_start; + auto selectionEnd = m_state.m_selection.m_end; + auto foldedSelectionStart = unfoldedToFoldedCoords(selectionStart); + auto foldedSelectionEnd = unfoldedToFoldedCoords(selectionEnd); + for (auto &foldedLine: m_foldedLines) { + auto keyCount = foldedLine.second.m_keys.size(); + for (u32 i = 0; i < keyCount; i++) { + auto ellipsisIndex = foldedLine.second.m_ellipsisIndices[i]; + Range ellipsisRange = Range( + Coordinates(rowToLineIndex(foldedLine.first), ellipsisIndex), + Coordinates(rowToLineIndex(foldedLine.first), ellipsisIndex + 3) + ); + if (Range(foldedSelectionStart,foldedSelectionEnd).overlaps(ellipsisRange)) { + openCodeFold(foldedLine.second.m_keys[i]); + } + } + } + } + + void TextEditor::Lines::ensureCursorVisible() { + auto pos = lineCoordinates(m_state.m_cursorPosition); + auto row = lineIndexToRow(pos.m_line); + + if (m_unfoldIfNeeded && m_foldedLines.contains(row) && !m_codeFoldsDisabled) { + auto foldedLine = m_foldedLines[row]; + auto foldedCoords = unfoldedToFoldedCoords(pos); + auto keyCount = foldedLine.m_keys.size(); + for (u32 i = 0; i < keyCount; i++) { + if (foldedCoords.m_column >= foldedLine.m_ellipsisIndices[i] && foldedCoords.m_column <= foldedLine.m_ellipsisIndices[i] + 3) { + openCodeFold(m_foldedLines[row].m_keys[i]); + break; + } + } + } + m_unfoldIfNeeded = false; + + if (!m_withinRender) { m_scrollToCursor = true; return; @@ -197,10 +406,10 @@ namespace hex::ui { 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); + pos = lineCoordinates(unfoldedToFoldedCoords(m_state.m_cursorPosition)); + auto posColumnIndex = (i32) rint(textDistanceToLineStart(pos) / m_charAdvance.x); - auto posRow = pos.m_line; + auto posRow = lineIndexToRow(pos.m_line); bool scrollToCursorX = true; bool scrollToCursorY = true; @@ -244,17 +453,686 @@ namespace hex::ui { m_oldTopMargin = m_topMargin; } - void TextEditor::resetCursorBlinkTime() { + float TextEditor::screenPosToRow(const ImVec2 &position) const { + if (position.y < m_lines.m_cursorScreenPosition.y + m_lines.m_topMargin) + return -1.0f; + return (position.y - m_lines.m_cursorScreenPosition.y - m_lines.m_topMargin) / m_lines.m_charAdvance.y; + } + + float TextEditor::rowToLineIndex(i32 row) { + return m_lines.rowToLineIndex(row); + } + + float TextEditor::lineIndexToRow(i32 lineIndex) { + return m_lines.lineIndexToRow(lineIndex); + } + + float TextEditor::Lines::rowToLineIndex(i32 row) { + if (m_codeFoldsDisabled) + return row; + if (m_rowToLineIndex.contains(row)) + return m_rowToLineIndex[row]; + return -1.0f; + } + + float TextEditor::Lines::lineIndexToRow(i32 lineIndex) { + if (m_codeFoldsDisabled) + return lineIndex; + if (m_lineIndexToRow.contains(lineIndex)) + return m_lineIndexToRow[lineIndex]; + return -1.0f; + } + + void TextEditor::Lines::resetCursorBlinkTime() { m_startTime = ImGui::GetTime() * 1000 - s_cursorBlinkOnTime; } - void TextEditor::renderText(const char *title, const ImVec2 &lineNumbersStartPos, const ImVec2 &textEditorSize) { + bool TextEditor::CodeFold::trigger() { + lines->m_codeFoldHighlighted = NoCodeFoldSelected; + if (!isOpen() && startHovered()) { + lines->m_codeFoldHighlighted = key; + codeFoldStartCursorBox.callback(); + } else { + auto &rowFoldSymbols = lines->m_rowToFoldSymbol; + auto row = lines->lineIndexToRow(key.m_end.m_line); + if (isOpen() && endHovered() && rowFoldSymbols.contains(row) && rowFoldSymbols[row] != Lines::FoldSymbol::Square) { + lines->m_codeFoldHighlighted = key; + codeFoldEndCursorBox.callback(); + } + row = lines->lineIndexToRow(key.m_start.m_line); + if (startHovered() && rowFoldSymbols.contains(row) && rowFoldSymbols[row] != Lines::FoldSymbol::Square) { + lines->m_codeFoldHighlighted = key; + codeFoldStartCursorBox.callback(); + } + } + bool result = TextEditor::ActionableBox::trigger(); + if (isOpen()) + result = result || codeFoldEndActionBox.trigger(); + bool clicked = ImGui::IsMouseClicked(0); + result = result && clicked; + return result; + } + + ImVec2 TextEditor::coordsToScreen(Coordinates coordinates) { + return m_lines.foldedCoordsToScreen(coordinates); + } + + ImVec2 TextEditor::Lines::foldedCoordsToScreen(Coordinates coordinates) { + ImVec2 lineStartScreenPos = getLineStartScreenPos(0, lineIndexToRow(coordinates.m_line)); + auto line = operator[](coordinates.m_line); + auto text = line.m_chars.substr(0, line.indexColumn(coordinates.m_column)); + return lineStartScreenPos + ImVec2(line.stringTextSize(text), 0); + } + + void TextEditor::Lines::initializeCodeFolds() { + + m_codeFoldKeyMap.clear(); + m_codeFoldKeyLineMap.clear(); + m_codeFoldValueMap.clear(); + m_codeFoldValueLineMap.clear(); + m_codeFolds.clear(); + m_rowToFoldSymbol.clear(); + + //m_foldsAreInstalled = !m_codeFoldKeys.empty(); + + + for (auto [key, isOpen]: m_codeFoldState){ + auto index = m_codeFoldKeys.find(key); + if (index->m_start != key.m_start || index->m_end != key.m_end) + m_codeFoldState.erase(key); + } + + for (auto key : m_codeFoldKeys) { + if (key.m_start >= key.m_end) { + m_codeFoldKeys.erase(key); + continue; + } + auto rowStart = lineIndexToRow(key.m_start.m_line); + auto rowEnd = lineIndexToRow(key.m_end.m_line); + + if (!m_rowToFoldSymbol.contains(rowStart)) + m_rowToFoldSymbol[rowStart] = FoldSymbol::Down; + m_rowToFoldSymbol[rowEnd] = FoldSymbol::Up; + const ImRect &rect1 = getBoxForRow(rowStart); + const ImRect &rect2 = getBoxForRow(rowEnd); + + CodeFold fold = CodeFold(this, key, rect1, rect2); + m_codeFolds.insert({key,fold}); + + auto index = m_codeFoldKeys.find(key); + if (index->m_start != key.m_start || index->m_end != key.m_end) + m_codeFoldState[key] = true; + + if (!m_codeFoldKeyMap.contains(key.m_start)) + m_codeFoldKeyMap[key.m_start] = key.m_end; + + if (!m_codeFoldValueMap.contains(key.m_end)) + m_codeFoldValueMap[key.m_end] = key.m_start; + m_codeFoldKeyLineMap.insert(std::make_pair(key.m_start.m_line, key.m_start)); + m_codeFoldValueLineMap.insert(std::make_pair(key.m_end.m_line, key.m_end)); + } + + for (auto &[row, foldedLine]: m_foldedLines) { + for (auto key = foldedLine.m_keys.begin(); key != foldedLine.m_keys.end(); key++) { + auto index = m_codeFoldKeys.find(*key); + if (index->m_start != key->m_start || index->m_end != key->m_end) + foldedLine.m_keysToRemove.push_back(*key); + } + } + + m_lineIndexToScreen.clear(); + m_leadingLineSpaces.clear(); + m_leadingLineSpaces.resize(size(), 0); + for (i32 i = 0; i < size(); i++) { + m_lineIndexToScreen[i] = foldedCoordsToScreen(lineCoordinates(i, 0)); + m_leadingLineSpaces[i] = skipSpaces(lineCoordinates( i, 0)); + } + + + if (m_useSavedFoldStatesRequested) { + applyCodeFoldStates(); + m_useSavedFoldStatesRequested = false; + } else + saveCodeFoldStates(); + + m_foldedLines.clear(); + std::vector closedFolds = removeEmbeddedFolds(); + for (auto closedFold : closedFolds) { + closeCodeFold(closedFold, false); + auto row = lineIndexToRow(closedFold.m_start.m_line); + m_rowToFoldSymbol[row] = FoldSymbol::Square; + } + + removeKeys(); + m_initializedCodeFolds = true; + } + + void TextEditor::Lines::setRowToLineIndexMap() { + m_rowToLineIndex.clear(); + m_lineIndexToRow.clear(); + + i32 lineIndex = 0; + auto maxRow = getGlobalRowMax(); + if (maxRow < 0) + return; + + for (u32 i = 0; i <= maxRow; i++) { + + if (m_codeFoldKeyLineMap.contains(lineIndex)) { + auto values = m_codeFoldKeyLineMap.equal_range(lineIndex); + for (auto value = values.first; value != values.second; value++) { + Range key(value->second, m_codeFoldKeyMap[value->second]); + + auto newKey = key; + + while (m_codeFolds.contains(newKey) && !m_codeFolds[newKey].isOpen() && m_codeFoldKeyLineMap.contains(newKey.m_end.m_line)) { + auto endLine = newKey.m_end.m_line; + + auto range = m_codeFoldKeyLineMap.equal_range(endLine); + bool found = false; + for (auto it = range.first; it != range.second; ++it) { + Range testKey(it->second, m_codeFoldKeyMap[it->second]); + if (m_codeFolds.contains(testKey) && !m_codeFolds[testKey].isOpen()) { + newKey = testKey; + found = true; + break; + } + } + if (!found) + break; + } + if (!m_rowToLineIndex.contains(i)) { + m_rowToLineIndex[i] = key.m_start.m_line; + m_lineIndexToRow[key.m_start.m_line] = i; + } + if (!m_codeFolds[key].isOpen()) + lineIndex = newKey.m_end.m_line; + } + } else { + if (!m_rowToLineIndex.contains(i)) { + m_rowToLineIndex[i] = lineIndex; + m_lineIndexToRow[lineIndex] = i; + } + } + lineIndex += 1; + } + for (lineIndex = 1; lineIndex < size(); lineIndex++) { + if (!m_lineIndexToRow.contains(lineIndex)) + m_lineIndexToRow[lineIndex] = m_lineIndexToRow[lineIndex - 1]; + } + } + + bool TextEditor::Lines::updateCodeFolds() { + + bool triggered = false; + std::map detectedFolds; + for (auto key = m_codeFoldKeys.rbegin(); key != m_codeFoldKeys.rend(); key++) { + if (m_codeFolds[*key].trigger()) + triggered = true; + if (m_codeFolds[*key].isDetected()) + detectedFolds.insert({*key, m_codeFolds[*key]}); + } + if (detectedFolds.empty()) { + m_codeFoldHighlighted = NoCodeFoldSelected; + return false; + } + auto detectedFoldRIter = detectedFolds.rbegin(); + while (detectedFoldRIter != detectedFolds.rend()) { + if (!detectedFoldRIter->second.isOpen()) { + m_codeFoldHighlighted = detectedFoldRIter->first; + if (triggered) { + detectedFoldRIter->second.callback(); + return true; + } + } + detectedFoldRIter++; + } + auto detectedFoldIter = detectedFolds.begin(); + m_codeFoldHighlighted = detectedFoldIter->first; + if (triggered) { + detectedFoldIter->second.callback(); + return true; + } + return false; + } + + std::vector TextEditor::Lines::removeEmbeddedFolds() { + std::vector closedFolds; + for (auto key : m_codeFoldKeys) { + if (m_codeFoldState.contains(key) && !m_codeFoldState[key]) { + bool replace = false; + std::vector keysToErase; + for (auto closedFold : closedFolds) { + if (key.contains(closedFold)) { + replace = true; + keysToErase.push_back(closedFold); + } + } + if (replace) { + closedFolds.erase(std::remove_if(closedFolds.begin(), closedFolds.end(),[&keysToErase]( + const Range &interval + ) { + return std::find(keysToErase.begin(), keysToErase.end(), interval) != keysToErase.end(); + }), closedFolds.end()); + } + closedFolds.push_back(key); + keysToErase.clear(); + } + } + return closedFolds; + } + + void TextEditor::Lines::getRowSegments() { + m_rowToSegments.clear(); + m_multiLinesToRow.clear(); + m_rowCodeFoldTooltips.clear(); + std::vector closedFolds = removeEmbeddedFolds(); + + for (auto key : closedFolds) { + auto row = lineIndexToRow(key.m_start.m_line); + if (m_rowToSegments.contains(row)) + continue; + for (i32 i = key.m_start.m_line; i < key.m_end.m_line; i++) + m_multiLinesToRow[i] = row; + auto lineIndex = rowToLineIndex(row); + auto foldedLine = m_foldedLines[row]; + i32 count = foldedLine.m_keys.size(); + if (count == 0) + continue; + for (i32 i = 0; i < count; i++) { + Interval sgm = {foldedLine.m_foldedSegments[2 * i].m_column, foldedLine.m_foldedSegments[2 * i + 1].m_column}; + m_rowToSegments[row].push_back({foldedLine.m_keys[i].m_start, indexScreenPosition(lineIndex, sgm)}); + ImVec2 screenPosEnd = indexCoordsToScreen(lineCoordinates(lineIndex, foldedLine.m_ellipsisIndices[i])); + m_rowCodeFoldTooltips[row].push_back(TextEditor::CodeFoldTooltip(this, foldedLine.m_keys[i],ImRect(screenPosEnd, screenPosEnd + ImVec2(Ellipsis.lineTextSize(), m_charAdvance.y)))); + } + Interval sgm = {foldedLine.m_foldedSegments[2 * count].m_column, foldedLine.m_foldedSegments[2 * count + 1].m_column}; + m_rowToSegments[row].push_back({foldedLine.m_keys[count - 1].m_end, indexScreenPosition(lineIndex, sgm)}); + } + } + + TextEditor::Interval TextEditor::Lines::indexScreenPosition(i32 lineIndex, Interval stringIndices) { + return Interval( lineIndexToScreen(lineIndex, stringIndices)); + } + + ImVec2 TextEditor::Line::intervalToScreen(Interval stringIndices) const { + return ImVec2(textSize(stringIndices.m_start), textSize(stringIndices.m_end)); + } + + ImVec2 TextEditor::Lines::lineIndexToScreen(i32 lineIndex, Interval stringIndices) { + auto &line = operator[](lineIndex); + auto startPos = m_lineIndexToScreen[lineIndex].x; + auto increments = line.intervalToScreen(stringIndices); + return ImVec2(startPos + increments.x, startPos + increments.y); + } + + ImVec2 TextEditor::Lines::indexCoordsToScreen(Coordinates indexCoords) { + return foldedCoordsToScreen(lineIndexCoords(indexCoords.m_line + 1, indexCoords.m_column)); + } + + bool TextEditor::CodeFoldTooltip::trigger() { + bool hovered = ActionableBox::trigger(); + + if (hovered && ImGui::IsMouseClicked(0)) { + auto codeFoldKeys = m_lines->m_codeFoldKeys; + auto codeFoldState = m_lines->m_codeFoldState; + std::vector keysToOpen; + auto key = codeFoldKeys.begin(); + for (; key != codeFoldKeys.end(); key++) { + if (*key == m_key) + break; + } + keysToOpen.push_back(*key); + auto prevKey = key; + key++; + while (key->m_start == prevKey->m_end && codeFoldState.contains(*key) && !codeFoldState[*key]) { + keysToOpen.push_back(*key); + prevKey = key; + key++; + } + while (key != codeFoldKeys.end()) { + if (key->contains(m_key) && codeFoldState.contains(*key) && !codeFoldState[*key]) + keysToOpen.push_back(*key); + key++; + } + for (auto openKey = keysToOpen.rbegin(); openKey != keysToOpen.rend(); openKey++) + m_lines->openCodeFold(*openKey); + + return true; + } + + return hovered; + } + + void TextEditor::CodeFoldTooltip::callback() { + ImGui::BeginChild("##lineNumbers"); + ImGui::BeginTooltip(); + i32 textWindowWidth = (m_lines->operator[](m_key.m_start.m_line)).lineTextSize(); + for (auto lineIndex = m_key.m_start.m_line + 1; lineIndex <= m_key.m_end.m_line; lineIndex++) { + textWindowWidth = std::max(textWindowWidth,(m_lines->operator[](lineIndex)).lineTextSize()); + } + + auto textEditorSize = ImVec2(textWindowWidth + m_lines->m_lineNumberFieldWidth, (m_key.m_end.m_line - m_key.m_start.m_line + 1) * m_lines->m_charAdvance.y); + + if (!m_lines->m_ignoreImGuiChild) { + std::string textTitle = m_lines->m_title + popupText; + ImGui::BeginChild(textTitle.c_str(), textEditorSize, false, ImGuiWindowFlags_NoScrollbar); + } + m_lines->printCodeFold(m_key); + ImGui::EndChild(); + ImGui::EndTooltip(); + ImGui::EndChild(); + } + + void TextEditor::Lines::printCodeFold(const Range &key) { + auto maxLineIndex = key.m_end.m_line; + auto lineIndex = key.m_start.m_line; + m_unfoldedLines[lineIndex].print(lineIndex, maxLineIndex); + ImGui::NewLine(); + lineIndex = std::floor(lineIndex + 1.0F); + while (lineIndex <= maxLineIndex) { + m_unfoldedLines[lineIndex].print(lineIndex, maxLineIndex); + ImGui::NewLine(); + lineIndex = std::floor(lineIndex + 1.0F); + } + } + + TextEditor::FoldedLine::FoldedLine(Lines *lines) : m_lines(lines) { + m_row = -1; + m_full = Range(Invalid,Invalid); + m_cursorPosition = Invalid; + m_selection = Range(Invalid, Invalid); + m_ellipsisIndices.clear(); + m_keys.clear(); + m_built = false; + m_foldedLine = Line(); + } + + template T operator+(const T &lhs, const T &rhs) { + return (T)((i64) lhs + (i64)rhs); + } + + void TextEditor::FoldedLine::insertKey(const Range& key) { + m_type = (FoldType)0; + std::pair delimiters = {' ',' '}; + if (m_lines->m_codeFoldDelimiters.contains(key)) + delimiters = m_lines->m_codeFoldDelimiters[key]; + std::string delimiterOpen(1,delimiters.first); + std::string delimiterClose(1,delimiters.second); + Line lineStart = m_lines->m_unfoldedLines[key.m_start.m_line]; + Line lineEnd = m_lines->m_unfoldedLines[key.m_end.m_line]; + + bool isIfDef; + if (key.m_start.m_line > 0 && key.m_end.m_line < m_lines->size()) { + std::string prevLine = m_lines->m_unfoldedLines[key.m_start.m_line - 1].m_chars; + if (prevLine.starts_with("#ifdef") || prevLine.starts_with("#ifndef")) { + isIfDef = true; + } else + isIfDef = false; + } else + isIfDef = false; + + bool appendClosingLine = true; + Line bracket; + Range delimiterCoordinates = findDelimiterCoordinates(key); + if (delimiterCoordinates.m_start == Invalid || delimiterCoordinates.m_end == Invalid) + return; + + if (m_row == -1) { + m_keys.push_back(key); + m_full = key; + + if (lineStart.m_chars.starts_with("import")) { + appendClosingLine = false; + m_foldedLine = lineStart.subLine(0, 7); + m_type = FoldType::AddsFirstLine; + } else if (lineStart.m_chars.starts_with("#include")) { + appendClosingLine = false; + m_foldedLine = lineStart.subLine(0, 9); + m_type = FoldType::AddsFirstLine; + } else if (lineStart.m_chars.starts_with("//")) { + appendClosingLine = false; + m_foldedLine = lineStart.subLine(0, 1); + m_type = FoldType::AddsFirstLine; + }else if (isIfDef) { + appendClosingLine = false; + m_type = FoldType::NoDelimiters; + } else { + m_type = FoldType::AddsFirstLine + FoldType::HasOpenDelimiter + FoldType::AddsLastLine + FoldType::HasCloseDelimiter; + if (delimiterCoordinates.m_start.m_line == key.m_start.m_line) { + m_foldedLine = lineStart.subLine(0, delimiterCoordinates.m_start.m_column + 1).trim(TrimMode::TrimEnd); + } else { + m_foldedLine = lineStart.trim(TrimMode::TrimEnd); + bracket = m_lines->m_unfoldedLines[delimiterCoordinates.m_start.m_line].subLine(delimiterCoordinates.m_start.m_column, 1); + m_type = m_type + FoldType::FirstLineNeedsDelimiter; + m_foldedLine.append(" "); + m_foldedLine.append(bracket); + } + } + + i32 newIndex = m_foldedLine.size(); + auto const newPos = std::lower_bound(m_ellipsisIndices.begin(), m_ellipsisIndices.end(), newIndex); + m_ellipsisIndices.insert(newPos, newIndex); + m_foldedLine.append(Ellipsis); + if (appendClosingLine) { + if (delimiterCoordinates.m_end.m_line == key.m_end.m_line) { + auto line = lineEnd.subLine(delimiterCoordinates.m_end.m_column, -1).trim(TrimMode::TrimBoth); + m_foldedLine.append(line); + } else { + auto line = lineEnd.trim(TrimMode::TrimBoth); + m_foldedLine.append(line); + } + } + } else if (key.m_end.m_line == m_full.m_start.m_line) { + Line line = lineStart.trim(TrimMode::TrimEnd); + m_type = FoldType::AddsFirstLine + FoldType::HasOpenDelimiter + FoldType::AddsLastLine + FoldType::HasCloseDelimiter; + if (delimiterCoordinates.m_start.m_line != key.m_start.m_line) { + bracket = m_lines->m_unfoldedLines[delimiterCoordinates.m_start.m_line].subLine(delimiterCoordinates.m_start.m_column, 1); + m_type = m_type + FoldType::FirstLineNeedsDelimiter; + line.append(" "); + line.append(bracket); + } + + i32 newIndex = line.size(); + std::transform(m_ellipsisIndices.begin(), m_ellipsisIndices.end(), m_ellipsisIndices.begin(), [newIndex, this] + (i32 index) { + return index + newIndex + 3 - m_lines->m_leadingLineSpaces[m_row]; + } + ); + auto const newPos = std::lower_bound(m_ellipsisIndices.begin(), m_ellipsisIndices.end(), newIndex); + m_ellipsisIndices.insert(newPos, newIndex); + line.append(Ellipsis); + auto trimmedLine = m_foldedLine.trim(TrimMode::TrimBoth); + line.append(trimmedLine); + m_foldedLine = line; + m_keys.insert(m_keys.begin(), key); + m_full.m_start = key.m_start; + } else if (key.m_start.m_line == m_full.m_end.m_line) { + m_type = FoldType::AddsFirstLine + FoldType::HasOpenDelimiter + FoldType::AddsLastLine + FoldType::HasCloseDelimiter; + if (lineStart.size() > (u32) (delimiterCoordinates.m_start.m_column + 1)) { + u32 extra = lineStart.size() - (delimiterCoordinates.m_start.m_column + 1); + m_foldedLine = m_foldedLine.subLine(0, m_foldedLine.size() - extra); + } + i32 newIndex = m_foldedLine.size(); + if (delimiterCoordinates.m_start.m_line != key.m_start.m_line) { + bracket = m_lines->m_unfoldedLines[delimiterCoordinates.m_start.m_line].subLine(delimiterCoordinates.m_start.m_column, 1); + m_type = m_type + FoldType::FirstLineNeedsDelimiter; + m_foldedLine.append(" "); + newIndex += 1; + m_foldedLine.append(bracket); + newIndex += bracket.size(); + } + auto const newPos = std::lower_bound(m_ellipsisIndices.begin(), m_ellipsisIndices.end(), newIndex); + m_ellipsisIndices.insert(newPos, newIndex); + m_foldedLine.append(Ellipsis); + auto line = lineEnd.subLine(delimiterCoordinates.m_end.m_column, -1).trim(TrimMode::TrimBoth); + m_foldedLine.append(line); + m_keys.push_back(key); + m_full.m_end = key.m_end; + } + m_row = m_lines->lineIndexToRow(key.m_start.m_line); + m_built = appendClosingLine; + } + + TextEditor::Range TextEditor::FoldedLine::findDelimiterCoordinates(Range key) { + std::pair delimiterPair = {' ',' '}; + if (m_lines->m_codeFoldDelimiters.contains(key)) + delimiterPair = m_lines->m_codeFoldDelimiters[key]; + std::string delimiters; + delimiters += delimiterPair.first; + delimiters += delimiterPair.second; + if (delimiters == "" || (delimiters != "{}" && delimiters != "[]" && delimiters != "()" && delimiters != "<>")) { + auto lineStart = m_lines->m_unfoldedLines[key.m_start.m_line].m_chars; + if (lineStart.starts_with("import") || lineStart.starts_with("#include")) { + auto lineEnd = m_lines->m_unfoldedLines[key.m_end.m_line]; + auto columnStart = lineStart.find_first_of(" "); + return Range(m_lines->lineCoordinates(key.m_start.m_line, columnStart), m_lines->lineCoordinates(key.m_end.m_line, lineEnd.maxColumn())); + } + return key; + } + if (delimiters.size() < 2) + return key; + + auto lineIndex = key.m_start.m_line; + Coordinates openDelimiterCoordinates = m_lines->find(delimiters.substr(0,1), Coordinates(lineIndex, 0)); + Coordinates closeDelimiterCoordinates; + Line openDelimiterLine = m_lines->m_unfoldedLines[openDelimiterCoordinates.m_line]; + i32 columnIndex = 0; + bool found = false; + + while (true) { + Coordinates nextCoordinates = m_lines->lineCoordinates(openDelimiterCoordinates.m_line, openDelimiterCoordinates.m_column + columnIndex); + if (openDelimiterCoordinates.m_column < openDelimiterLine.maxColumn() && openDelimiterLine[(u64) nextCoordinates.m_column] == delimiters[0]) { + //auto saveNearCursor = m_editor->m_lines.m_matchedDelimiter.m_nearCursor; + if (m_lines->m_matchedDelimiter.coordinatesNearDelimiter(m_lines, nextCoordinates)) { + auto result = m_lines->m_matchedDelimiter.findMatchingDelimiter(m_lines, nextCoordinates, false); + if (result.m_line == key.m_end.m_line) { + found = true; + closeDelimiterCoordinates = result; + break; + } + } else + break; + openDelimiterCoordinates.m_column += 1; + } else + break; + columnIndex++; + } + if (!found) + closeDelimiterCoordinates = m_lines->rfind(delimiters.substr(1,1), m_lines->lineCoordinates(key.m_end.m_line, -1)); + return Range(openDelimiterCoordinates, closeDelimiterCoordinates); + } + + void TextEditor::FoldedLine::loadSegments() { + m_foldedSegments.clear(); + m_unfoldedSegments.clear(); + i32 keyCount = (i32)m_keys.size(); + m_foldedSegments.resize(2 * keyCount + 2); + m_unfoldedSegments.resize(2 * keyCount + 2); + + auto foldedLineIndex = m_lines->rowToLineIndex(m_row); + auto lineIndex = m_keys[0].m_start.m_line; + if (!addsFullFirstLineToFold() && !addsLastLineToFold()) { + Range key = m_keys[0]; + m_foldedSegments[0] = m_lines->lineCoordinates( foldedLineIndex, 0); + m_foldedSegments[1] = m_lines->lineCoordinates( foldedLineIndex, 1); + m_unfoldedSegments[0] = m_lines->lineCoordinates( lineIndex , 0); + m_unfoldedSegments[1] = m_lines->lineCoordinates( lineIndex , 1); + + lineIndex = key.m_end.m_line; + m_foldedSegments[2] = m_lines->lineCoordinates( foldedLineIndex, m_ellipsisIndices[0] + Ellipsis.size() - 1); + m_foldedSegments[3] = m_lines->lineCoordinates( foldedLineIndex, m_ellipsisIndices[0] + Ellipsis.size()); + m_unfoldedSegments[2] = m_lines->lineCoordinates( lineIndex , m_lines->m_unfoldedLines[lineIndex].maxColumn() - 1); + m_unfoldedSegments[3] = m_lines->lineCoordinates( lineIndex , m_lines->m_unfoldedLines[lineIndex].maxColumn()); + return; + } + + Range delimiterCoordinates = findDelimiterCoordinates(m_keys[0]); + + m_foldedSegments[0] = m_lines->lineCoordinates( foldedLineIndex, 0); + m_foldedSegments[1] = m_lines->lineCoordinates( foldedLineIndex, m_ellipsisIndices[0]); + + m_unfoldedSegments[0] = m_lines->lineCoordinates( lineIndex, 0); + m_unfoldedSegments[1] = m_lines->lineCoordinates( delimiterCoordinates.m_start.m_line, delimiterCoordinates.m_start.m_column + 1); + + for (i32 i = 0; i < keyCount - 1; i++) { + auto closeDelimiterCoordinates = delimiterCoordinates.m_end; + delimiterCoordinates = findDelimiterCoordinates(m_keys[i + 1]); + + lineIndex = m_keys[i].m_end.m_line; + m_foldedSegments[2 * i + 2] = m_lines->lineCoordinates( foldedLineIndex, m_ellipsisIndices[i] + 3); + m_foldedSegments[2 * i + 3] = m_lines->lineCoordinates( foldedLineIndex, m_ellipsisIndices[i + 1]); + if (firstLineNeedsDelimiter()) + m_foldedSegments[2 * i + 3].m_column -= 2; + + m_unfoldedSegments[2 * i + 2] = m_lines->lineCoordinates( lineIndex, closeDelimiterCoordinates.m_column); + m_unfoldedSegments[2 * i + 3] = m_lines->lineCoordinates( lineIndex, delimiterCoordinates.m_start.m_column + 1); + } + + lineIndex = m_keys.back().m_end.m_line; + m_foldedSegments[2 * keyCount] = m_lines->lineCoordinates(foldedLineIndex, m_ellipsisIndices.back() + 3); + m_foldedSegments[2 * keyCount + 1] = m_lines->lineCoordinates(foldedLineIndex, m_foldedLine.maxColumn()); + m_unfoldedSegments[2 * keyCount] = m_lines->lineCoordinates( lineIndex, delimiterCoordinates.m_end.m_column); + m_unfoldedSegments[2 * keyCount + 1] = m_lines->lineCoordinates( lineIndex, m_lines->m_unfoldedLines[lineIndex].maxColumn()); + } + + void TextEditor::Lines::removeKeys() { + for (auto &[row,foldedLine] : m_foldedLines) { + for (auto i = foldedLine.m_keysToRemove.rbegin(); i != foldedLine.m_keysToRemove.rend(); i++) { + openCodeFold(*i); + } + foldedLine.m_keysToRemove.clear(); + if (foldedLine.m_keys.empty()) { + m_foldedLines.erase(row); + } + } + } + + void TextEditor::FoldedLine::removeKey(const Range& key) { + + if (m_lines->rowToLineIndex(m_row) == key.m_start.m_line) { + m_foldedLine = m_foldedLine.subLine(m_ellipsisIndices[0], m_foldedLine.size() - m_ellipsisIndices[0]); + m_row = m_lines->lineIndexToRow(key.m_end.m_line); + m_keys.erase(m_keys.begin()); + m_ellipsisIndices.erase(m_ellipsisIndices.begin()); + } else { + i32 index = 0; + for (i32 i = 1; i < (i32)m_keys.size(); i++) { + if (m_keys[i] == key) { + index = i; + break; + } + } + m_foldedLine = m_foldedLine.subLine(0, m_ellipsisIndices[index]); + + for (i32 i = index + 1; i < (i32)m_keys.size() ; i++) { + m_keysToRemove.push_back(m_keys[i]); + } + m_ellipsisIndices.erase(m_ellipsisIndices.begin() + index); + m_keys.erase(m_keys.begin() + index); + } + + if (!m_keys.empty()) { + m_full.m_start = m_keys.front().m_start; + m_full.m_end = m_keys.back().m_end; + m_built = true; + } else { + m_full = Range(Invalid, Invalid); + m_row = -1; + m_cursorPosition = Invalid; + m_foldedLine = Line(); + m_ellipsisIndices.clear(); + m_built = false; + } + } + + void TextEditor::renderText(const ImVec2 &textEditorSize) { + m_lines.m_withinRender = true; preRender(); auto drawList = ImGui::GetWindowDrawList(); - s_cursorScreenPosition = ImGui::GetCursorScreenPos(); - ImVec2 position = lineNumbersStartPos; + m_lines.m_cursorScreenPosition = ImGui::GetCursorScreenPos(); float scrollY; + if (m_setScroll) { setScroll(m_scroll); scrollY = m_scroll.y; @@ -264,244 +1142,389 @@ namespace hex::ui { m_scroll = ImVec2(scrollX, scrollY); } - if (m_setTopLine) - setTopLine(); + if (m_lines.m_setTopRow) + m_lines.setFirstRow(); else - m_topLine = std::max(0.0F, (scrollY - m_topMargin) / m_charAdvance.y); - auto lineNo = m_topLine; + m_lines.m_topRow = std::max(0.0F, (scrollY - m_lines.m_topMargin) / m_lines.m_charAdvance.y); + m_topLineNumber = getTopLineNumber(); + float maxDisplayedRow = m_lines.getMaxDisplayedRow(); + float lineIndex = m_topLineNumber; + float row = m_lines.m_topRow; + m_longestDrawnLineLength = m_longestLineLength; + if (!m_lines.isEmpty()) { + if (!m_lines.m_codeFoldsDisabled) { + m_lines.initializeCodeFolds(); + if (m_lines.updateCodeFolds()) { + m_lines.setFocusAtCoords(m_lines.m_state.m_cursorPosition, false); + } + m_lines.setRowToLineIndexMap(); + m_lines.getRowSegments(); + } + if (m_lines.m_saveCodeFoldStateRequested) { + saveCodeFoldStates(); + m_lines.m_saveCodeFoldStateRequested = false; + } - auto lineMax = std::clamp(lineNo + m_numberOfLinesDisplayed, 0.0F, m_lines.size() - 1.0F); - - if (!m_lines.empty()) { bool focused = ImGui::IsWindowFocused(); + while (std::floor(row) <= maxDisplayedRow) { + if (!focused && m_lines.m_updateFocus) { + m_lines.m_state.m_cursorPosition = m_lines.m_focusAtCoords; + m_lines.resetCursorBlinkTime(); + if (m_lines.m_scrollToCursor) + m_lines.ensureCursorVisible(); - while (lineNo <= lineMax) { + if (!this->m_lines.m_readOnly) + ImGui::SetKeyboardFocusHere(0); + m_lines.m_updateFocus = false; + } - drawSelection(lineNo); + lineIndex = rowToLineIndex((i32) row); + if (lineIndex >= (i32) m_lines.size() || lineIndex < 0) + break; - if (!m_ignoreImGuiChild) + if (m_showLineNumbers) { + if (!m_lines.m_ignoreImGuiChild) + ImGui::EndChild(); + + drawBreakpoints(lineIndex, textEditorSize, drawList, "##lineNumbers"); + drawLineNumbers(lineIndex); + if (!m_lines.m_codeFoldsDisabled) + drawCodeFolds(lineIndex, drawList); + + if (!m_lines.m_ignoreImGuiChild) + ImGui::BeginChild(m_lines.m_title.c_str()); + } + + drawSelection(lineIndex, drawList); + drawButtons(lineIndex); + + if (m_showCursor) + drawCursor(lineIndex, textEditorSize, focused, drawList); + + u64 currentLineLength = drawColoredText(lineIndex, textEditorSize); + if (currentLineLength > m_longestDrawnLineLength) + m_longestDrawnLineLength = currentLineLength; + + row = row + 1.0F; + } + } else { + m_lines.m_rowToLineIndex[0] = 1; + m_topLineNumber = 1; + lineIndex = 0; + if (m_lines.m_unfoldedLines.empty()) + m_lines.m_unfoldedLines.push_back(Line()); + m_lines.m_state.m_cursorPosition = lineCoordinates( 0, 0); + if (m_showLineNumbers) { + if (!m_lines.m_ignoreImGuiChild) ImGui::EndChild(); + drawLineNumbers(0); + if (!m_lines.m_ignoreImGuiChild) + ImGui::BeginChild(m_lines.m_title.c_str()); + } + if (m_showCursor) + drawCursor(0,textEditorSize, true, drawList); + ImGui::Dummy(m_lines.m_charAdvance); - if (m_showLineNumbers) - drawLineNumbers(position, lineNo, textEditorSize, focused, drawList); + //auto lineStart = m_lines.lineCoordinates(lineIndex, 0); + //drawText(lineStart, 0, 0); + //m_lines.m_rowToLineIndex.clear(); + } - if (!m_ignoreImGuiChild) - ImGui::BeginChild(title); + if (m_lines.m_scrollToCursor) + m_lines.ensureCursorVisible(); + m_lines.m_withinRender = false; + postRender(lineIndex, "##lineNumbers"); + } - if (m_state.m_cursorPosition.m_line == lineNo && m_showCursor && focused) - renderCursor(lineNo, drawList); + i64 TextEditor::drawColoredText(i32 lineIndex, const ImVec2 &textEditorSize) { + auto line = m_lines[lineIndex]; + //if (line.m_chars.starts_with("//+-#:") && lineIndex == (i32) m_lines.size() - 1) { + // return 0; + //} - if (!m_showLineNumbers) - renderGotoButtons(lineNo); + if (line.empty()) { + ImGui::Dummy(m_lines.m_charAdvance); - // Render colorized text + auto lineStart = m_lines.lineCoordinates(lineIndex, 0); + drawText(lineStart, 0, 0); + return 0; + } - auto &line = m_lines[lineNo]; - if (line.empty()) { - ImGui::Dummy(m_charAdvance); - lineNo = std::floor(lineNo + 1.0F); - if (m_updateFocus) - setFocus(); - continue; - } - auto colors = m_lines[lineNo].m_colors; - 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.indexColumn(line.size()); - if (textSize < start) { - while (textSize < start && head.m_column < maxColumn) { - head.m_column += 1; - textSize = textDistanceToLineStart(head); - } - } else { - while (textSize > start && head.m_column > 0) { - head.m_column -= 1; - textSize = textDistanceToLineStart(head); - } - } - Coordinates current = Coordinates(lineNo, (start + colorsSize) / m_charAdvance.x); - textSize = textDistanceToLineStart(current); - if (textSize < start + colorsSize) { - while (textSize < start + colorsSize && current.m_column < maxColumn) { - current.m_column += 1; - textSize = textDistanceToLineStart(current); - } - } else { - while (textSize > start + colorsSize && current.m_column > 0) { - current.m_column -= 1; - textSize = textDistanceToLineStart(current); - } - } + auto colors = line.m_colors; + auto lineSize = line.lineTextSize(); - 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); - if (index == std::string::npos) - index = maxI; - else - index -= i; - - u32 tokenLength = std::clamp((u64) index,(u64) 1, maxI - i); - if (m_updateFocus) - setFocus(); - auto lineStart = setCoordinates(lineNo, line.indexColumn(i)); - - drawText(lineStart, i, tokenLength, color); - - i += (tokenLength + skipSpaces(lineStart)); - } - - lineNo = std::floor(lineNo + 1.0F); + i64 visibleSize = std::min((u64) textEditorSize.x, (u64) lineSize); + i64 start = ImGui::GetScrollX(); + Coordinates head = Coordinates(lineIndex, start / m_lines.m_charAdvance.x); + i64 textSize = m_lines.textDistanceToLineStart(head); + auto maxColumn = line.indexColumn(line.size()); + if (textSize < start) { + while (textSize < start && head.m_column < maxColumn) { + head.m_column += 1; + textSize = m_lines.textDistanceToLineStart(head); + } + } else { + while (textSize > start && head.m_column > 0) { + head.m_column -= 1; + textSize = m_lines.textDistanceToLineStart(head); + } + } + Coordinates current = Coordinates(lineIndex, (start + visibleSize) / m_lines.m_charAdvance.x);//line.substr(0, (start + visibleSize) / m_charAdvance.x); + textSize = m_lines.textDistanceToLineStart(current); + if (textSize < start + visibleSize) { + while (textSize < start + visibleSize && current.m_column < maxColumn) { + current.m_column += 1; + textSize = m_lines.textDistanceToLineStart(current); + } + } else { + while (textSize > start + visibleSize && current.m_column > 0) { + current.m_column -= 1; + textSize = m_lines.textDistanceToLineStart(current); } } - if (m_scrollToCursor) - ensureCursorVisible(); + 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); + if (index == std::string::npos) + index = maxI; + else + index -= i; - postRender(title, position, lineNo); + u32 tokenLength = std::clamp((u64) index, (u64) 1, maxI - i); + i32 columnCoordinate = line.indexColumn(i); + auto lineStart = m_lines.lineCoordinates(lineIndex, columnCoordinate); + drawText(lineStart, tokenLength, color); + + i += tokenLength; + } + auto result = line.size(); + return result; } - void TextEditor::setFocus() { - m_state.m_cursorPosition = m_focusAtCoords; - resetCursorBlinkTime(); - if (m_scrollToCursor) - ensureCursorVisible(); - - if (!this->m_readOnly) - ImGui::SetKeyboardFocusHere(0); - m_updateFocus = false; + bool TextEditor::Lines::isMultiLineRow(i32 row) { + return (m_foldedLines.contains(row) && m_foldedLines[row].m_keys.size() > 0); } void TextEditor::preRender() { - m_charAdvance = calculateCharAdvance(); - - /* Update palette with the current alpha from style */ + //static ImVec2 previous = {0,0}; + //static bool update = true; + //if (update) { + m_lines.m_charAdvance = calculateCharAdvance(); + m_lines.m_leftMargin = m_lines.m_charAdvance.x; for (i32 i = 0; i < (i32) PaletteIndex::Max; ++i) { auto color = ImGui::ColorConvertU32ToFloat4(s_paletteBase[i]); color.w *= ImGui::GetStyle().Alpha; m_palette[i] = ImGui::ColorConvertFloat4ToU32(color); } - - m_numberOfLinesDisplayed = getPageSize(); - - if (m_scrollToTop) { - m_scrollToTop = false; - ImGui::SetScrollY(0.f); - } - - if (m_scrollToBottom && ImGui::GetScrollMaxY() >= ImGui::GetScrollY()) { - m_scrollToBottom = false; - ImGui::SetScrollY(ImGui::GetScrollMaxY()); - } - + m_lines.m_numberOfLinesDisplayed = getPageSize(); + //if (previous == m_lines.m_charAdvance) { + // update = false; + // } else { + // update = true; + // previous = m_lines.m_charAdvance; + // } } - void TextEditor::drawSelection(float lineNo) { - ImVec2 lineStartScreenPos = s_cursorScreenPosition + ImVec2(m_leftMargin, m_topMargin + std::floor(lineNo) * m_charAdvance.y); - Range lineCoords = Range(setCoordinates(lineNo, 0), setCoordinates(lineNo, -1)); - auto drawList = ImGui::GetWindowDrawList(); + void TextEditor::drawSelection(float lineIndex, ImDrawList *drawList) { + auto row = m_lines.lineIndexToRow(lineIndex); + auto lineStartScreenPos = m_lines.getLineStartScreenPos(0,row); + Range lineCoords; + if (m_lines.isMultiLineRow(row)) { + lineCoords.m_start = m_lines.lineCoordinates(m_lines.m_foldedLines[row].m_full.m_start.m_line, 0); + lineCoords.m_end = m_lines.lineCoordinates(m_lines.m_foldedLines[row].m_full.m_end.m_line, -1); + } else { + lineCoords = Range(m_lines.lineCoordinates(lineIndex, 0), m_lines.lineCoordinates(lineIndex, -1)); + } - if (m_state.m_selection.m_start <= lineCoords.m_end && m_state.m_selection.m_end > lineCoords.m_start) { - float selectionStart = textDistanceToLineStart(std::max(m_state.m_selection.m_start, lineCoords.m_start)); - float selectionEnd = textDistanceToLineStart(std::min(m_state.m_selection.m_end, lineCoords.m_end)) + m_charAdvance.x * (m_state.m_selection.m_end.m_line > lineNo); + if (m_lines.m_state.m_selection.m_start <= lineCoords.m_end && m_lines.m_state.m_selection.m_end > lineCoords.m_start) { + auto start = m_lines.unfoldedToFoldedCoords(std::max(m_lines.m_state.m_selection.m_start, lineCoords.m_start)); + auto end = m_lines.unfoldedToFoldedCoords(std::min(m_lines.m_state.m_selection.m_end, lineCoords.m_end)); + float selectionStart = m_lines.textDistanceToLineStart(start);//coordsToScreen(start).x; + float selectionEnd = m_lines.textDistanceToLineStart(end);//)coordsToScreen(end).x; if (selectionStart < selectionEnd) { - ImVec2 rectStart(lineStartScreenPos.x + selectionStart, lineStartScreenPos.y); - ImVec2 rectEnd(lineStartScreenPos.x + selectionEnd, lineStartScreenPos.y + m_charAdvance.y); + ImVec2 rectStart = ImVec2(lineStartScreenPos.x + selectionStart, lineStartScreenPos.y); + ImVec2 rectEnd = ImVec2(lineStartScreenPos.x + selectionEnd, lineStartScreenPos.y + m_lines.m_charAdvance.y); drawList->AddRectFilled(rectStart, rectEnd, m_palette[(i32) PaletteIndex::Selection]); } } } - void TextEditor::drawLineNumbers(ImVec2 position, float lineNo, const ImVec2 &contentSize, bool focused, ImDrawList *drawList) { - ImVec2 lineStartScreenPos = s_cursorScreenPosition + ImVec2(m_leftMargin, m_topMargin + std::floor(lineNo) * m_charAdvance.y); - ImVec2 lineNoStartScreenPos = ImVec2(position.x, m_topMargin + s_cursorScreenPosition.y + std::floor(lineNo) * m_charAdvance.y); - auto start = ImVec2(lineNoStartScreenPos.x + m_lineNumberFieldWidth - m_charAdvance.x / 2, lineStartScreenPos.y); - i32 totalDigitCount = std::floor(std::log10(m_lines.size())) + 1; - ImGui::SetCursorScreenPos(position); - if (!m_ignoreImGuiChild) - ImGui::BeginChild("##lineNumbers"); - - i32 padding = totalDigitCount - std::floor(std::log10(lineNo + 1)) - 1; - std::string space = std::string(padding, ' '); - std::string lineNoStr = space + std::to_string((i32) (lineNo + 1)); - ImGui::SetCursorScreenPos(ImVec2(position.x, lineStartScreenPos.y)); - if (ImGui::InvisibleButton(lineNoStr.c_str(), ImVec2(m_lineNumberFieldWidth, m_charAdvance.y))) { - if (m_breakpoints.contains(lineNo + 1)) - m_breakpoints.erase(lineNo + 1); - else - m_breakpoints.insert(lineNo + 1); - m_breakPointsChanged = true; - setFocusAtCoords(m_state.m_cursorPosition, false); - } - auto color = m_palette[(i32) PaletteIndex::LineNumber]; - - if (m_state.m_cursorPosition.m_line == lineNo && m_showCursor) { - color = m_palette[(i32) PaletteIndex::Cursor]; - // Highlight the current line (where the cursor is) - if (!hasSelection()) { - auto end = ImVec2(lineNoStartScreenPos.x + contentSize.x + m_lineNumberFieldWidth, lineStartScreenPos.y + m_charAdvance.y); - drawList->AddRectFilled(ImVec2(position.x, lineStartScreenPos.y), end, m_palette[(i32) (focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]); - drawList->AddRect(ImVec2(position.x, lineStartScreenPos.y), end, m_palette[(i32) PaletteIndex::CurrentLineEdge], 1.0f); - } - } - // Draw breakpoints - if (m_breakpoints.contains(lineNo + 1)) { - auto end = ImVec2(lineNoStartScreenPos.x + contentSize.x + m_lineNumberFieldWidth, lineStartScreenPos.y + m_charAdvance.y); - drawList->AddRectFilled(ImVec2(position.x, lineStartScreenPos.y), end, m_palette[(i32) PaletteIndex::Breakpoint]); - - drawList->AddCircleFilled(start + ImVec2(0, m_charAdvance.y) / 2, m_charAdvance.y / 3, m_palette[(i32) PaletteIndex::Breakpoint]); - drawList->AddCircle(start + ImVec2(0, m_charAdvance.y) / 2, m_charAdvance.y / 3, m_palette[(i32) PaletteIndex::Default]); - drawList->AddText(ImVec2(lineNoStartScreenPos.x + m_leftMargin, lineStartScreenPos.y), color, lineNoStr.c_str()); - } - TextUnformattedColoredAt(ImVec2(m_leftMargin + lineNoStartScreenPos.x, lineStartScreenPos.y), color, lineNoStr.c_str()); - - if (!m_ignoreImGuiChild) - ImGui::EndChild(); + ImVec2 TextEditor::Lines::getLineStartScreenPos(float leftMargin, float row) { + return m_cursorScreenPosition + ImVec2(m_leftMargin + leftMargin, m_topMargin + std::floor(row) * m_charAdvance.y); } - void TextEditor::renderCursor(float lineNo, ImDrawList *drawList) { - ImVec2 lineStartScreenPos = s_cursorScreenPosition + ImVec2(m_leftMargin, m_topMargin + std::floor(lineNo) * m_charAdvance.y); - auto timeEnd = ImGui::GetTime() * 1000; - auto elapsed = timeEnd - m_startTime; - if (elapsed > s_cursorBlinkOnTime) { - float width = 1.0f; - u64 charIndex = lineCoordinatesToIndex(m_state.m_cursorPosition); - float cx = textDistanceToLineStart(m_state.m_cursorPosition); - auto &line = m_lines[std::floor(lineNo)]; - if (m_overwrite && charIndex < line.size()) { - char c = line[charIndex]; - std::string s(1, c); - width = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, s.c_str()).x; + void TextEditor::drawBreakpoints(float lineIndex, const ImVec2 &contentSize, ImDrawList *drawList, std::string title) { + if (!m_lines.m_ignoreImGuiChild) + ImGui::BeginChild(title.c_str()); + auto row = m_lines.lineIndexToRow(lineIndex); + auto lineStartScreenPos = m_lines.getLineStartScreenPos(0, row); + ImVec2 lineNumberStartScreenPos = ImVec2(m_lines.m_lineNumbersStartPos.x, lineStartScreenPos.y); + auto start = lineStartScreenPos; + ImVec2 end = lineStartScreenPos + ImVec2(m_lines.m_lineNumberFieldWidth + contentSize.x, m_lines.m_charAdvance.y); + auto center = lineNumberStartScreenPos + ImVec2(m_lines.m_lineNumberFieldWidth - 2 * m_lines.m_charAdvance.x + 1_scaled, 0); + if (m_lines.m_rowToSegments.contains(row)) { + bool circlesDrawn = false; + for (auto segments : m_lines.m_rowToSegments[row]) { + if (segments.m_foldEnd.m_line != lineIndex && m_lines.m_breakpoints.count(segments.m_foldEnd.m_line + 1) > 0) + start.x = segments.m_segment.m_start; + if (m_lines.m_breakpoints.count(segments.m_foldEnd.m_line + 1) > 0) { + if (segments == m_lines.m_rowToSegments[row].back()) + end.x = lineNumberStartScreenPos.x + contentSize.x + m_lines.m_lineNumberFieldWidth; + else + end.x = segments.m_segment.m_end; + drawList->AddRectFilled(start, end, m_palette[(i32) PaletteIndex::Breakpoint]); + if (!circlesDrawn) { + circlesDrawn = true; + drawList->AddCircleFilled(center + ImVec2(0, m_lines.m_charAdvance.y) / 2, m_lines.m_charAdvance.y / 3, m_palette[(i32) PaletteIndex::Breakpoint]); + drawList->AddCircle(center + ImVec2(0, m_lines.m_charAdvance.y) / 2, m_lines.m_charAdvance.y / 3, m_palette[(i32) PaletteIndex::Default]); + } + } + Coordinates segmentStart = segments.m_foldEnd; + if (m_lines.m_codeFoldKeyMap.contains(segments.m_foldEnd)) { + auto keyValue = m_lines.m_codeFoldKeyMap[segmentStart]; + auto key = Range(segmentStart, keyValue); + if (m_lines.m_codeFoldState.contains(key) && !m_lines.m_codeFoldState[key]) { + for (i32 i = key.m_start.m_line + 1; i < key.m_end.m_line; i++) { + if (m_lines.m_breakpoints.count(i + 1) > 0) { + start.x = segments.m_segment.m_end; + end.x = start.x + Ellipsis.lineTextSize(); + drawList->AddRectFilled(start, end, m_palette[(i32) PaletteIndex::Breakpoint]); + if (!circlesDrawn) { + circlesDrawn = true; + drawList->AddCircleFilled(center + ImVec2(0, m_lines.m_charAdvance.y) / 2, m_lines.m_charAdvance.y / 3, m_palette[(i32) PaletteIndex::Breakpoint]); + drawList->AddCircle(center + ImVec2(0, m_lines.m_charAdvance.y) / 2, m_lines.m_charAdvance.y / 3, m_palette[(i32) PaletteIndex::Default]); + } + } + } + } + } } - ImVec2 rectStart(lineStartScreenPos.x + cx, lineStartScreenPos.y); - ImVec2 rectEnd(lineStartScreenPos.x + cx + width, lineStartScreenPos.y + m_charAdvance.y); - drawList->AddRectFilled(rectStart, rectEnd, m_palette[(i32) PaletteIndex::Cursor]); - if (elapsed > s_cursorBlinkInterval) - m_startTime = timeEnd; - if (m_matchedBracket.isNearABracket(this, m_state.m_cursorPosition)) - m_matchedBracket.findMatchingBracket(this); + } else if (m_lines.m_breakpoints.count(lineIndex + 1) > 0) { + end = ImVec2(lineNumberStartScreenPos.x + contentSize.x + m_lines.m_lineNumberFieldWidth, lineStartScreenPos.y + m_lines.m_charAdvance.y); + drawList->AddRectFilled(start, end, m_palette[(i32) PaletteIndex::Breakpoint]); + drawList->AddCircleFilled(center + ImVec2(0, m_lines.m_charAdvance.y) / 2, m_lines.m_charAdvance.y / 3, m_palette[(i32) PaletteIndex::Breakpoint]); + drawList->AddCircle(center + ImVec2(0, m_lines.m_charAdvance.y) / 2, m_lines.m_charAdvance.y / 3, m_palette[(i32) PaletteIndex::Default]); } + + ImGui::SetCursorScreenPos(lineNumberStartScreenPos); + ImGui::PushID((i32) (lineIndex + lineNumberStartScreenPos.y)); + float buttonWidth = m_lines.m_lineNumberFieldWidth; + auto boxSize = m_lines.m_charAdvance.x + (((u32) m_lines.m_charAdvance.x % 2) ? 2.0f : 1.0f); + if (m_lines.m_codeFoldKeyLineMap.contains(lineIndex) || m_lines.m_codeFoldValueLineMap.contains(lineIndex)) { + buttonWidth -= (boxSize - 1) / 2; + } + + if (buttonWidth > 0 && m_lines.m_charAdvance.y > 0) { + if (ImGui::InvisibleButton("##breakpoints", ImVec2(buttonWidth, m_lines.m_charAdvance.y))) { + if (m_lines.m_breakpoints.contains(lineIndex + 1)) + m_lines.m_breakpoints.erase(lineIndex + 1); + else + m_lines.m_breakpoints.insert(lineIndex + 1); + m_lines.m_breakPointsChanged = true; + m_lines.setFocusAtCoords(m_lines.m_state.m_cursorPosition, false); + } + } + + if (ImGui::IsItemHovered() && (ImGui::IsKeyDown(ImGuiKey_RightShift) || ImGui::IsKeyDown(ImGuiKey_LeftShift)) && m_lines.m_state.m_cursorPosition.isValid(m_lines)) { + if (ImGui::BeginTooltip()) { + auto lineCursor = m_lines.m_state.m_cursorPosition.m_line + 1; + auto columnCursor = m_lines.m_state.m_cursorPosition.m_column + 1; + ImGui::Text("(%d/%d)", lineCursor, columnCursor); + } + ImGui::EndTooltip(); + } + ImGui::PopID(); + } - void TextEditor::renderGotoButtons(float lineNo) { - if (isEmpty()) + void TextEditor::drawLineNumbers(float lineIndex) { + auto row = m_lines.lineIndexToRow(lineIndex); + auto lineStartScreenPos = m_lines.getLineStartScreenPos(0,row); + ImVec2 lineNumberStartScreenPos = ImVec2(m_lines.m_lineNumbersStartPos.x, lineStartScreenPos.y); + auto lineNumber = lineIndex + 1; + if (lineNumber <= 0) 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); + auto color = m_palette[(i32) PaletteIndex::LineNumber]; + auto cursorRow = m_lines.lineIndexToRow(m_lines.m_state.m_cursorPosition.m_line); + i32 lineNumberToDraw = (i32) lineNumber; + if (cursorRow == row && m_showCursor) { + color = m_palette[(i32) PaletteIndex::Default]; + if (m_lines.isMultiLineRow(row)) { + lineNumberToDraw = m_lines.m_state.m_cursorPosition.m_line + 1; + } + } + + i32 padding = std::floor(std::log10(m_lines.size())) - std::floor(std::log10((float)lineNumberToDraw)); + std::string lineNumberStr = std::string(padding, ' ') + std::to_string(lineNumberToDraw); + + TextUnformattedColoredAt(ImVec2(lineNumberStartScreenPos.x, lineStartScreenPos.y), color, lineNumberStr.c_str()); + } + + void TextEditor::drawCursor(float lineIndex, const ImVec2 &contentSize, bool focused, ImDrawList *drawList) { + + auto row = m_lines.lineIndexToRow(lineIndex); + auto lineStartScreenPos = m_lines.getLineStartScreenPos(0, row); + ImVec2 lineNumberStartScreenPos = ImVec2(m_lines.m_lineNumbersStartPos.x, lineStartScreenPos.y); + Coordinates lineCoords = lineCoordinates(lineIndex+1, 0); + + if (lineStartScreenPos == ImVec2(-1, -1)) { + for (auto key: m_lines.m_codeFoldKeys) { + if (key.contains(lineCoords) && m_lines.m_codeFoldState.contains(key) && !m_lines.m_codeFoldState[key]) { + row = m_lines.m_multiLinesToRow[lineIndex + 1]; + i32 multilineLineIndex = rowToLineIndex(row); + if (m_lines.m_rowToSegments.contains(row) && m_lines.m_rowToSegments[row].size() > 1) { + Segment result = *std::find_if(m_lines.m_rowToSegments[row].begin(), m_lines.m_rowToSegments[row].end(), [&](const Segment &segment) { + return segment.m_foldEnd == key.m_end; + }); + lineStartScreenPos = ImVec2(result.m_segment.m_start + m_lines.m_unfoldedLines[0].stringTextSize(std::string(m_lines.m_leadingLineSpaces[key.m_end.m_line], ' ')), m_lines.m_lineIndexToScreen[multilineLineIndex].y); + } + break; + } + } + } + + auto timeEnd = ImGui::GetTime() * 1000; + auto elapsed = timeEnd - m_lines.m_startTime; + auto foldedCursorPosition = m_lines.unfoldedToFoldedCoords(m_lines.m_state.m_cursorPosition); + if (foldedCursorPosition.m_line == (i32) lineIndex) { + if (focused && elapsed > s_cursorBlinkOnTime) { + float width = 1.0f; + u64 charIndex = m_lines.lineCoordsIndex(foldedCursorPosition); + float cx = m_lines.textDistanceToLineStart(foldedCursorPosition); + if (m_overwrite && charIndex < m_lines.m_unfoldedLines[foldedCursorPosition.m_line].size()) { + auto charSize = utf8CharLength(m_lines.m_unfoldedLines[foldedCursorPosition.m_line][charIndex]); + std::string s = m_lines.m_unfoldedLines[foldedCursorPosition.m_line].substr(charIndex, charSize); + width = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, s.c_str()).x; + } + ImVec2 rectStart(lineStartScreenPos.x + cx, lineStartScreenPos.y); + ImVec2 rectEnd(lineStartScreenPos.x + cx + width, lineStartScreenPos.y + m_lines.m_charAdvance.y); + drawList->AddRectFilled(rectStart, rectEnd, m_palette[(i32) PaletteIndex::Cursor]); + if (elapsed > s_cursorBlinkInterval) + m_lines.m_startTime = timeEnd; + } + if (!m_lines.hasSelection()) { + auto end = ImVec2(lineNumberStartScreenPos.x + contentSize.x + m_lines.m_lineNumberFieldWidth, lineStartScreenPos.y + m_lines.m_charAdvance.y); + drawList->AddRectFilled(lineStartScreenPos, end, m_palette[(i32) (focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]); + drawList->AddRect(lineStartScreenPos, end, m_palette[(i32) PaletteIndex::CurrentLineEdge], 1.0f); + } + } + } + + void TextEditor::drawButtons(float lineIndex) { + auto row = m_lines.lineIndexToRow(lineIndex); + auto lineStartScreenPos = m_lines.getLineStartScreenPos(0,row); + auto lineText = m_lines.m_unfoldedLines[lineIndex].m_chars; + Coordinates gotoKey = lineCoordinates( lineIndex + 1, 1); if (gotoKey != Invalid) { std::string errorLineColumn; bool found = false; - for (const auto& text: m_clickableText) { - if (lineText.starts_with(text)) { + for (auto text: m_lines.m_clickableText) { + if (lineText.find(text) == 0) { errorLineColumn = lineText.substr(text.size()); if (!errorLineColumn.empty()) { found = true; @@ -511,7 +1534,7 @@ namespace hex::ui { } if (found) { i32 currLine = 0, currColumn = 0; - if (auto idx = errorLineColumn.find(':'); idx != std::string::npos) { + if (auto idx = errorLineColumn.find(":"); idx != std::string::npos) { auto errorLine = errorLineColumn.substr(0, idx); if (!errorLine.empty()) currLine = std::stoi(errorLine) - 1; @@ -519,84 +1542,120 @@ namespace hex::ui { if (!errorColumn.empty()) currColumn = std::stoi(errorColumn) - 1; } - TextEditor::Coordinates errorPos = getSourceCodeEditor()->setCoordinates(currLine, currColumn); + TextEditor::Coordinates errorPos = getSourceCodeEditor()->m_lines.lineCoordinates(currLine, currColumn); if (errorPos != Invalid) { ImVec2 errorStart = ImVec2(lineStartScreenPos.x, lineStartScreenPos.y); - auto lineEnd = setCoordinates(lineNo, -1); + auto lineEnd = m_lines.lineCoordinates(lineIndex, -1); if (lineEnd != Invalid) { - ImVec2 errorEnd = ImVec2(lineStartScreenPos.x + textDistanceToLineStart(lineEnd), lineStartScreenPos.y + m_charAdvance.y); - ErrorGotoBox box = ErrorGotoBox(ImRect({errorStart, errorEnd}), errorPos, getSourceCodeEditor()); - m_errorGotoBoxes[gotoKey] = box; - CursorChangeBox cursorBox = CursorChangeBox(ImRect({errorStart, errorEnd})); - m_cursorBoxes[gotoKey] = cursorBox; + ImVec2 errorEnd = ImVec2(lineStartScreenPos.x + m_lines.textDistanceToLineStart(lineEnd), lineStartScreenPos.y + m_lines.m_charAdvance.y); + ErrorGotoBox box = ErrorGotoBox(ImRect(errorStart, errorEnd), errorPos, getSourceCodeEditor()); + m_lines.m_errorGotoBoxes[gotoKey] = box; + CursorChangeBox cursorBox = CursorChangeBox(ImRect(errorStart, errorEnd)); + m_lines.m_cursorBoxes[gotoKey] = cursorBox; } } } - if (m_cursorBoxes.contains(gotoKey)) { - auto box = m_cursorBoxes[gotoKey]; - if (box.trigger()) box.callback(); + if (m_lines.m_cursorBoxes.find(gotoKey) != m_lines.m_cursorBoxes.end()) { + auto box = m_lines.m_cursorBoxes[gotoKey]; + if (box.trigger()) + box.callback(); } - if (m_errorGotoBoxes.contains(gotoKey)) { - auto box = m_errorGotoBoxes[gotoKey]; - if (box.trigger()) box.callback(); + if (m_lines.m_errorGotoBoxes.find(gotoKey) != m_lines.m_errorGotoBoxes.end()) { + auto box = m_lines.m_errorGotoBoxes[gotoKey]; + if (box.trigger()) + box.callback(); + } + } + row = m_lines.lineIndexToRow(lineIndex); + if (m_lines.m_rowCodeFoldTooltips.contains(row)) { + for (auto codeFoldTooltip: m_lines.m_rowCodeFoldTooltips[row]) { + if (codeFoldTooltip.trigger()) + codeFoldTooltip.callback(); } } } - void TextEditor::drawText(Coordinates &lineStart, u64 i, u32 tokenLength, char color) { - auto &line = m_lines[lineStart.m_line]; - ImVec2 lineStartScreenPos = s_cursorScreenPosition + ImVec2(m_leftMargin, m_topMargin + std::floor(lineStart.m_line) * m_charAdvance.y); - auto textStart = textDistanceToLineStart(lineStart); - auto begin = lineStartScreenPos + ImVec2(textStart, 0); + void TextEditor::drawText(Coordinates &lineStart, u32 tokenLength, char color) { + auto row = m_lines.lineIndexToRow(lineStart.m_line); + auto begin = m_lines.getLineStartScreenPos(0,row); + + Line line = m_lines[lineStart.m_line]; + auto i = line.columnIndex(lineStart.m_column); + + begin.x += line.textSize(i); if (color <= (char) TextEditor::PaletteIndex::Comment && color >= (char) TextEditor::PaletteIndex::DocComment) fonts::CodeEditor().pushItalic(); TextUnformattedColoredAt(begin, m_palette[(i32) color], line.substr(i, tokenLength).c_str()); if (color <= (char) TextEditor::PaletteIndex::Comment && color >= (char) TextEditor::PaletteIndex::DocComment) - fonts::CodeEditor().pop(); + fonts::CodeEditor().pop(); + ErrorMarkers::iterator errorIt; - auto key = lineStart + Coordinates(1, 1); - if (errorIt = m_errorMarkers.find(key); errorIt != m_errorMarkers.end()) { + auto errorHoverBoxKey = lineStart + lineCoordinates( 1, 1); + if (errorIt = m_lines.m_errorMarkers.find(errorHoverBoxKey); errorIt != m_lines.m_errorMarkers.end()) { auto errorMessage = errorIt->second.second; auto errorLength = errorIt->second.first; - if (errorLength == 0) + if (errorLength == 0 && line.size() > (u32) i + 1) errorLength = line.size() - i - 1; + if (errorLength > 0) { + auto end = underWavesAt(begin, errorLength, m_palette[(i32) PaletteIndex::ErrorMarker]); + ErrorHoverBox box = ErrorHoverBox(ImRect(begin, end), errorHoverBoxKey, errorMessage.c_str()); + m_lines.m_errorHoverBoxes[errorHoverBoxKey] = box; + } + } - auto end = underwaves(begin, errorLength, m_palette[(i32) PaletteIndex::ErrorMarker]); - ErrorHoverBox box = ErrorHoverBox(ImRect({begin, end}), key, errorMessage.c_str()); - m_errorHoverBoxes[key] = box; + if (m_lines.m_errorHoverBoxes.find(errorHoverBoxKey) != m_lines.m_errorHoverBoxes.end()) { + auto errorHoverBox = m_lines.m_errorHoverBoxes[errorHoverBoxKey]; + if (errorHoverBox.trigger()) + errorHoverBox.callback(); } - if (m_errorHoverBoxes.contains(key)) { - auto box = m_errorHoverBoxes[key]; - if (box.trigger()) box.callback(); - } - lineStart = lineStart + Coordinates(0, tokenLength); + + lineStart = lineStart + lineCoordinates( 0, tokenLength); } - void TextEditor::postRender(const char *title, ImVec2 position, float lineNo) { - ImVec2 lineStartScreenPos = ImVec2(s_cursorScreenPosition.x + m_leftMargin, m_topMargin + s_cursorScreenPosition.y + std::floor(lineNo) * m_charAdvance.y); - float globalLineMax = m_lines.size(); - auto lineMax = std::clamp(lineNo + m_numberOfLinesDisplayed, 0.0F, globalLineMax - 1.0F); - if (!m_ignoreImGuiChild) - ImGui::EndChild(); + TextEditor::CodeFold::CodeFold(Lines *lines, TextEditor::Range key, const ImRect &startBox, const ImRect &endBox) : + ActionableBox(startBox), lines(lines), key(key), codeFoldStartCursorBox(startBox), codeFoldEndActionBox(endBox), codeFoldEndCursorBox(endBox) + { + if (lines->m_codeFolds.empty()) + return; + if (!lines->m_codeFolds.contains(key)) + lines->m_codeFolds[key] = *this; + if (!lines->m_codeFoldKeys.contains(key)) + lines->m_codeFoldKeys.insert(key); + lines->m_codeFoldKeyMap[key.m_start] = key.m_end; + lines->m_codeFoldValueMap[key.m_end] = key.m_start; + if (!lines->m_codeFoldState.contains(key)) + lines->m_codeFoldState[key] = true; +} - if (m_showLineNumbers && !m_ignoreImGuiChild) { - ImGui::BeginChild("##lineNumbers"); - ImGui::SetCursorScreenPos(ImVec2(position.x, lineStartScreenPos.y)); - ImGui::Dummy(ImVec2(m_lineNumberFieldWidth, (globalLineMax - lineMax - 1) * m_charAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight() - m_charAdvance.y)); + void TextEditor::postRender(float lineIndex, std::string title) { + lineIndex--; + float row = m_lines.lineIndexToRow(lineIndex); + auto lineStartScreenPos = m_lines.getLineStartScreenPos(0, row); + + float globalRowMax = m_lines.getGlobalRowMax(); + auto rowMax = 0; + if (globalRowMax > 0) + rowMax = std::clamp(row + m_lines.m_numberOfLinesDisplayed, 0.0F, globalRowMax - 1.0F); + + if (!m_lines.m_ignoreImGuiChild) { ImGui::EndChild(); + if (m_showLineNumbers) { + ImGui::BeginChild(title.c_str()); + ImGui::SetCursorScreenPos(ImVec2(m_lines.m_lineNumbersStartPos.x, lineStartScreenPos.y)); + ImGui::Dummy(ImVec2(m_lines.m_lineNumberFieldWidth, (globalRowMax - rowMax) * m_lines.m_charAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight() - m_lines.m_charAdvance.y)); + ImGui::EndChild(); + } + ImGui::BeginChild(m_lines.m_title.c_str()); } - if (!m_ignoreImGuiChild) - ImGui::BeginChild(title); ImGui::SetCursorScreenPos(lineStartScreenPos); if (m_showLineNumbers) - ImGui::Dummy(ImVec2(m_longestLineLength * m_charAdvance.x + m_charAdvance.x, (globalLineMax - lineMax - 2.0F) * m_charAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight())); + ImGui::Dummy(ImVec2(m_longestDrawnLineLength * m_lines.m_charAdvance.x + m_lines.m_charAdvance.x, std::floor((globalRowMax - rowMax) * m_lines.m_charAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight()))); else - ImGui::Dummy(ImVec2(m_longestLineLength * m_charAdvance.x + m_charAdvance.x, (globalLineMax - lineMax - 3.0F) * m_charAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight() - 1.0f)); - + ImGui::Dummy(ImVec2(m_longestDrawnLineLength * m_lines.m_charAdvance.x + m_lines.m_charAdvance.x, std::floor((globalRowMax - rowMax - 1_scaled) * m_lines.m_charAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight()))); if (m_topMarginChanged) { m_topMarginChanged = false; @@ -604,36 +1663,239 @@ namespace hex::ui { auto maxScroll = window->ScrollMax.y; if (maxScroll > 0) { float pixelCount; - if (m_newTopMargin > m_topMargin) { - pixelCount = m_newTopMargin - m_topMargin; + if (m_newTopMargin > m_lines.m_topMargin) { + pixelCount = m_newTopMargin - m_lines.m_topMargin; } else if (m_newTopMargin > 0) { - pixelCount = m_topMargin - m_newTopMargin; + pixelCount = m_lines.m_topMargin - m_newTopMargin; } else { - pixelCount = m_topMargin; + pixelCount = m_lines.m_topMargin; } auto oldScrollY = ImGui::GetScrollY(); - if (m_newTopMargin > m_topMargin) + if (m_newTopMargin > m_lines.m_topMargin) m_shiftedScrollY = oldScrollY + pixelCount; else m_shiftedScrollY = oldScrollY - pixelCount; ImGui::SetScrollY(m_shiftedScrollY); - m_topMargin = m_newTopMargin; + m_lines.m_topMargin = m_newTopMargin; } } } ImVec2 TextEditor::calculateCharAdvance() const { - /* Compute mCharAdvance regarding scaled font size (Ctrl + mouse wheel)*/ const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x; - return {fontSize, GImGui->FontSize * m_lineSpacing}; + return ImVec2(fontSize, GImGui->FontSize * m_lineSpacing); } - float TextEditor::textDistanceToLineStart(const Coordinates &aFrom) { - auto line = m_lines[aFrom.m_line]; - if (line.empty() || aFrom.m_column == 0) + float TextEditor::Lines::textDistanceToLineStart(const Coordinates &aFrom) { + if (m_lineIndexToScreen[aFrom.m_line] == ImVec2(-1,-1)) return 0.0f; + auto line = operator[](aFrom.m_line); + i32 colIndex = lineCoordsIndex(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.stringTextSize(substr2.c_str()); + line.m_lineMaxColumn = line.lineTextSize(); + return line.m_lineMaxColumn - distanceToEnd; + } + return line.stringTextSize(substr1.c_str()); + //return ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, substr.c_str(), nullptr, nullptr).x; + } - return line.textSize(line.columnIndex(aFrom.m_column)); + void TextEditor::drawCodeFolds(float lineIndex, ImDrawList *drawList) { + auto codeFoldKeyLine = lineIndex; + auto row = m_lines.lineIndexToRow(codeFoldKeyLine); + auto state = m_lines.m_rowToFoldSymbol[row]; + + if (m_lines.m_codeFoldHighlighted != NoCodeFoldSelected) { + Range key1, key2; + if (m_lines.m_foldedLines.contains(row)) { + auto &foldedLine = m_lines.m_foldedLines[row]; + if (m_lines.m_codeFoldValueMap.contains(foldedLine.m_full.m_start)) + key1 = Range(m_lines.m_codeFoldValueMap[foldedLine.m_full.m_start], foldedLine.m_full.m_start); + else + key1 = Range(foldedLine.m_full.m_start, m_lines.m_codeFoldKeyMap[foldedLine.m_full.m_start]); + if (m_lines.m_codeFoldKeyMap.contains(foldedLine.m_full.m_end)) + key2 = Range(foldedLine.m_full.m_end, m_lines.m_codeFoldKeyMap[foldedLine.m_full.m_end]); + else + key2 = Range(m_lines.m_codeFoldValueMap[foldedLine.m_full.m_end], foldedLine.m_full.m_end); + + if (m_lines.m_codeFoldHighlighted == key1) { + if (m_lines.m_codeFoldState.contains(m_lines.m_codeFoldHighlighted) && !m_lines.m_codeFoldState[m_lines.m_codeFoldHighlighted]) + state = Lines::FoldSymbol::Square; + else { + if (codeFoldKeyLine == key1.m_start.m_line) + state = Lines::FoldSymbol::Down; + else if (codeFoldKeyLine == key1.m_end.m_line) + state = Lines::FoldSymbol::Up; + } + } else if (m_lines.m_codeFoldHighlighted == key2) { + if (m_lines.m_codeFoldState.contains(key2) && !m_lines.m_codeFoldState[key2]) + state = Lines::FoldSymbol::Square; + else { + if (lineIndexToRow(codeFoldKeyLine) == lineIndexToRow(key2.m_start.m_line)) + state = Lines::FoldSymbol::Down; + else if (codeFoldKeyLine == key2.m_end.m_line) + state = Lines::FoldSymbol::Up; + } + } + } else if (m_lines.m_codeFoldHighlighted.m_start.m_line == codeFoldKeyLine) { + if (m_lines.m_codeFoldState.contains(m_lines.m_codeFoldHighlighted) && m_lines.m_codeFoldState[m_lines.m_codeFoldHighlighted]) + state = Lines::FoldSymbol::Down; + else + state = Lines::FoldSymbol::Square; + } else if (m_lines.m_codeFoldHighlighted.m_end.m_line == codeFoldKeyLine) { + if (m_lines.m_codeFoldState.contains(m_lines.m_codeFoldHighlighted) && m_lines.m_codeFoldState[m_lines.m_codeFoldHighlighted]) + state = Lines::FoldSymbol::Up; + else + state = Lines::FoldSymbol::Square; + } + } + + i32 lineColor; + Interval highlightedRowInterval = Interval(m_lines.lineIndexToRow(m_lines.m_codeFoldHighlighted.m_start.m_line), m_lines.lineIndexToRow(m_lines.m_codeFoldHighlighted.m_end.m_line)); + + if (highlightedRowInterval.contains(row) && (state == Lines::FoldSymbol::Line || + row == highlightedRowInterval.m_start || row == highlightedRowInterval.m_end)) + lineColor = ImGui::ColorConvertFloat4ToU32(ImGui::GetStyle().Colors[ImGuiCol_ScrollbarGrabActive]); + else + lineColor = ImGui::ColorConvertFloat4ToU32(ImGui::GetStyle().Colors[ImGuiCol_Border]); + + renderCodeFolds(row, drawList, lineColor, state); + + if (m_lines.m_matchedDelimiter.setNearCursor(&m_lines, m_lines.m_state.m_cursorPosition)) { + m_lines.m_matchedDelimiter.findMatchingDelimiter(&m_lines); + if (m_lines.isTrueMatchingDelimiter()) { + auto nearCursorScreenPos = m_lines.getLineStartScreenPos(0, lineIndexToRow(m_lines.m_matchedDelimiter.m_nearCursor.m_line)); + auto matchedScreenPos = m_lines.getLineStartScreenPos(0, lineIndexToRow(m_lines.m_matchedDelimiter.m_matched.m_line)); + + if (nearCursorScreenPos != ImVec2(-1, -1) && matchedScreenPos != ImVec2(-1, -1) && nearCursorScreenPos.y != matchedScreenPos.y) { + float lineX = m_lines.m_lineNumbersStartPos.x + m_lines.m_lineNumberFieldWidth - m_lines.m_charAdvance.x + 1_scaled; + ImVec2 p1 = ImVec2(lineX, std::min(matchedScreenPos.y, nearCursorScreenPos.y)); + ImVec2 p2 = ImVec2(lineX, std::max(matchedScreenPos.y, nearCursorScreenPos.y) + m_lines.m_charAdvance.y - 1_scaled); + drawList->AddLine(p1, p2, ImGui::ColorConvertFloat4ToU32(ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]), 1.0f); + } + } + } + + if (!m_lines.m_ignoreImGuiChild) + ImGui::EndChild(); + } + + void TextEditor::renderCodeFolds(i32 row, ImDrawList *drawList, i32 color, Lines::FoldSymbol state) { + auto boxSize = m_lines.m_charAdvance.x + (((u32)m_lines.m_charAdvance.x % 2) ? 2.0f : 1.0f); + auto verticalMargin = m_lines.m_charAdvance.y - boxSize; + auto horizontalMargin = m_lines.m_lineNumberFieldWidth - (boxSize - 1) / 2; + auto lineStartScreenPos = m_lines.getLineStartScreenPos(horizontalMargin, row); + auto numberLineStartScreenPos = ImVec2(m_lines.m_lineNumbersStartPos.x + m_lines.m_lineNumberFieldWidth, lineStartScreenPos.y); + + if (state == Lines::FoldSymbol::Square) { + renderSquare(numberLineStartScreenPos, drawList, boxSize - 1, verticalMargin, color); + renderPlus(numberLineStartScreenPos, drawList, boxSize, std::floor(verticalMargin / 2), color); + } else if (state == Lines::FoldSymbol::Down) { + renderPointingDown(numberLineStartScreenPos, drawList, boxSize - 1, verticalMargin, color); + renderMinus(numberLineStartScreenPos, drawList, boxSize, std::floor(verticalMargin / 4), color); + } else if (state == Lines::FoldSymbol::Up) { + renderPointingUp(numberLineStartScreenPos, drawList, boxSize - 1, verticalMargin, color); + renderMinus(numberLineStartScreenPos, drawList, boxSize, std::floor(3 * verticalMargin / 4), color); + } else { + auto startPos = numberLineStartScreenPos; + drawList->AddLine(startPos, startPos + ImVec2(0, m_lines.m_charAdvance.y), color, 1.0f); + } + } + + void TextEditor::renderVerticals(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color) { + ImVec2 p = ImVec2(lineStartScreenPos.x - boxSize / 2, lineStartScreenPos.y + verticalMargin); + ImVec2 py = ImVec2(0, boxSize); + drawList->AddLine(p, p + py, color, 1.0f); + ImVec2 px = ImVec2(boxSize, 0); + drawList->AddLine(p + px, p + px + py, color, 1.0f); + + for (i32 i = 1; i < boxSize; i++) { + ImVec2 pxi = ImVec2(i, 0); + drawList->AddLine(p + pxi, p + pxi + py, m_palette[(i32)PaletteIndex::Background],1.0f); + } + } + + void TextEditor::renderMinus(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color) { + ImVec2 p = ImVec2(lineStartScreenPos.x - (boxSize - 1) / 2 + 2, lineStartScreenPos.y + (boxSize - 1) / 2 + verticalMargin); + ImVec2 px = ImVec2(boxSize - 5, 0); + drawList->AddLine(p, p + px, color, 1.0f); + } + + void TextEditor::renderPlus(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color) { + renderMinus(lineStartScreenPos, drawList, boxSize, verticalMargin, color); + ImVec2 p = ImVec2(lineStartScreenPos.x, lineStartScreenPos.y + 2 + verticalMargin); + ImVec2 py = ImVec2(0, boxSize - 5); + drawList->AddLine(p, p + py, color, 1.0f); + } + + void TextEditor::renderTopHorizontal(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color) { + ImVec2 p = ImVec2(lineStartScreenPos.x - boxSize / 2, lineStartScreenPos.y + verticalMargin); + ImVec2 px = ImVec2(boxSize, 0); + drawList->AddLine(p, p + px, color, 1.0f); + } + + void TextEditor::renderBottomHorizontal(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color) { + ImVec2 p = ImVec2(lineStartScreenPos.x - boxSize / 2, lineStartScreenPos.y + boxSize + verticalMargin); + ImVec2 px = ImVec2(boxSize, 0); + drawList->AddLine(p, p + px, color, 1.0f); + } + + void TextEditor::renderSquare(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color) { + renderVerticals(lineStartScreenPos, drawList, boxSize, 0, color); + renderVerticals(lineStartScreenPos, drawList, boxSize, verticalMargin, color); + renderTopHorizontal(lineStartScreenPos, drawList, boxSize, 0, color); + renderBottomHorizontal(lineStartScreenPos, drawList, boxSize, verticalMargin, color); + } + + void TextEditor::renderPointingUp(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color) { + ImVec2 p1 = ImVec2(lineStartScreenPos.x - boxSize / 2, lineStartScreenPos.y + verticalMargin); + ImVec2 px = ImVec2(boxSize, 0); + ImVec2 py = ImVec2(0, boxSize); + + for (i32 i = 1; i < boxSize / 2; i++) { + ImVec2 pxi = ImVec2(i, 0); + ImVec2 pyi = ImVec2(0, boxSize / 2 - verticalMargin - i + 2); + drawList->AddLine(p1 + py + pxi, p1 + pxi + pyi, m_palette[(i32) PaletteIndex::Background], 1.0f); + } + + for (i32 i = boxSize / 2; i < boxSize; i++) { + ImVec2 pxi = ImVec2(i, 0); + ImVec2 pyi = ImVec2(0, i - boxSize / 2 - verticalMargin + 2); + drawList->AddLine(p1 + py + pxi, p1 + pxi + pyi, m_palette[(i32) PaletteIndex::Background], 1.0f); + } + + renderVerticals(lineStartScreenPos, drawList, boxSize, verticalMargin, color); + renderBottomHorizontal(lineStartScreenPos, drawList, boxSize, verticalMargin, color); + + ImVec2 p2 = lineStartScreenPos; + drawList->AddLine(p1, p2, color, 1.0f); + drawList->AddLine(p1 + px, p2, color, 1.0f); + } + + void TextEditor::renderPointingDown(ImVec2 lineStartScreenPos, ImDrawList *drawList, float boxSize, float verticalMargin, i32 color) { + ImVec2 p1 = ImVec2(lineStartScreenPos.x - boxSize / 2, lineStartScreenPos.y); + ImVec2 px = ImVec2(boxSize, 0); + ImVec2 py = ImVec2(0, boxSize); + + for (i32 i = 1; i < boxSize / 2; i++) { + ImVec2 pxi = ImVec2(i, 0); + ImVec2 pyi = ImVec2(0, verticalMargin - boxSize / 2 + i - 2); + drawList->AddLine(p1 + pxi, p1 + py + pxi + pyi, m_palette[(i32) PaletteIndex::Background], 1.0f); + } + for (i32 i = boxSize / 2; i < boxSize; i++) { + ImVec2 pxi = ImVec2(i, 0); + ImVec2 pyi = ImVec2(0, verticalMargin + boxSize / 2 - i - 2); + drawList->AddLine(p1 + pxi, p1 + py + pxi + pyi, m_palette[(i32) PaletteIndex::Background], 1.0f); + } + + renderVerticals(lineStartScreenPos, drawList, boxSize, 0, color); + renderTopHorizontal(lineStartScreenPos, drawList, boxSize, 0, color); + + ImVec2 p2 = lineStartScreenPos + ImVec2(0, verticalMargin + boxSize); + drawList->AddLine(p1 + py, p2, color, 1.0f); + drawList->AddLine(p1 + px + py, p2, color, 1.0f); } } \ 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 9090d036a..39b7a9d05 100644 --- a/plugins/ui/source/ui/text_editor/support.cpp +++ b/plugins/ui/source/ui/text_editor/support.cpp @@ -1,13 +1,40 @@ -#include #include #include +#include +#include +#include +#include + +#include namespace hex::ui { - using Coordinates = TextEditor::Coordinates; + using Interval = TextEditor::Interval; + using Coordinates = TextEditor::Range::Coordinates; + using Range = TextEditor::Range; using Line = TextEditor::Line; + using Lines = TextEditor::Lines; using LineIterator = TextEditor::LineIterator; using Range = TextEditor::Range; using FindReplaceHandler = TextEditor::FindReplaceHandler; + using CodeFold = TextEditor::CodeFold; + + + bool Interval::contains(i32 value, bool inclusive) const { + if (inclusive) + return value >= m_start && value <= m_end; + return value > m_start && value < m_end; + } + + bool Interval::contiguous(const Interval &other) const { + auto gap = m_start - other.m_end; + if (gap == 0 || gap == 1) + return true; + gap = other.m_start - m_end; + if (gap == 0 || gap == 1) + return true; + return false; + } + bool Coordinates::operator==(const Coordinates &o) const { return m_line == o.m_line && m_column == o.m_column; } @@ -140,12 +167,11 @@ namespace hex::ui { return *m_charsIter; } - LineIterator LineIterator::operator++() { - LineIterator iter = *this; - ++iter.m_charsIter; - ++iter.m_colorsIter; - ++iter.m_flagsIter; - return iter; + LineIterator &LineIterator::operator++() { + ++m_charsIter; + ++m_colorsIter; + ++m_flagsIter; + return *this; } LineIterator LineIterator::operator=(const LineIterator &other) { @@ -321,6 +347,17 @@ namespace hex::ui { return result; } + char Line::at(u64 index) const { + auto charsSize = (i64) m_chars.size(); + i64 signedIndex = (i64) index; + if (signedIndex < 0) + signedIndex = charsSize + signedIndex; + if (signedIndex >= (i64)m_chars.size()) + throw std::out_of_range("Line::at: index out of range"); + return m_chars[index]; + } + + char Line::operator[](u64 index) const { auto charsSize = (i64) m_chars.size(); if (charsSize == 0) @@ -328,9 +365,30 @@ namespace hex::ui { i64 signedIndex = std::clamp((i64) index,0 - charsSize,charsSize - 1); if (signedIndex < 0) return m_chars[charsSize + signedIndex]; + signedIndex = std::clamp(signedIndex, 0LL, charsSize - 1); return m_chars[signedIndex]; } + std::string Line::at(i64 index) const { + i64 utf8Length = TextEditor::stringCharacterCount(m_chars); + if (utf8Length == 0 || index >= utf8Length || index < -utf8Length) + throw std::out_of_range("Line::at: index out of range"); + if (index < 0) + index = utf8Length + index; + if (index < 0 || index >= utf8Length) + throw std::out_of_range("Line::at: index out of range"); + i64 utf8Start = 0; + for (i64 utf8Index = 0; utf8Index < index; ++utf8Index) { + if (utf8Start >= (i64) m_chars.size()) + throw std::out_of_range("Line::at: index out of range"); + utf8Start += TextEditor::utf8CharLength(m_chars[utf8Start]); + } + i64 utf8CharLen = TextEditor::utf8CharLength(m_chars[utf8Start]); + if (utf8Start + utf8CharLen > (i64) m_chars.size()) + throw std::out_of_range("Line::at: incomplete UTF-8 character at index"); + return m_chars.substr(utf8Start, utf8CharLen); + } + // C++ can't overload functions based on return type, so use any type other // than u64 to avoid ambiguity. std::string Line::operator[](i64 index) const { @@ -340,6 +398,7 @@ namespace hex::ui { index = std::clamp(index, -utf8Length, utf8Length - 1); if (index < 0) index = utf8Length + index; + index = std::clamp(index, 0LL, utf8Length - 1); i64 utf8Start = 0; for (i64 utf8Index = 0; utf8Index < index; ++utf8Index) { utf8Start += TextEditor::utf8CharLength(m_chars[utf8Start]); @@ -477,6 +536,18 @@ namespace hex::ui { return !m_colorized; } + Interval &Interval::operator=(const Interval &interval) { + m_start = interval.m_start; + m_end = interval.m_end; + return *this; + } + + Interval &Interval::operator=(Interval &&interval) noexcept { + m_start = std::move(interval.m_start); + m_end = std::move(interval.m_end); + return *this; + } + bool TextEditor::ActionableBox::trigger() { auto mousePos = ImGui::GetMousePos(); if (mousePos.x <= m_box.Min.x || mousePos.x >= m_box.Max.x || @@ -502,31 +573,53 @@ namespace hex::ui { ImGui::EndTooltip(); } + bool CodeFold::isDetected() { + return lines->m_codeFoldHighlighted == key; + } + TextEditor *TextEditor::getSourceCodeEditor() { if (m_sourceCodeEditor != nullptr) return m_sourceCodeEditor; return this; } - bool TextEditor::isEmpty() const { - auto size = m_lines.size(); + bool TextEditor::isEmpty() { + return m_lines.isEmpty(); + } + + bool Lines::isEmpty() { + auto size = m_unfoldedLines.size(); if (size > 1) return false; if (size == 0) return true; - return m_lines[0].empty(); + return m_unfoldedLines.at(0).empty(); + } + + void TextEditor::resetFoldedSelections() { + for (auto &[row,foldedLine] : m_lines.m_foldedLines) { + foldedLine.m_selection = Range(Invalid, Invalid); + } } bool TextEditor::EditorState::operator==(const EditorState &o) const { return m_selection == o.m_selection && m_cursorPosition == o.m_cursorPosition; } + void TextEditor::Lines::setSelection(const Range &selection) { + m_state.m_selection = lineCoordinates(const_cast(selection)); + } + void TextEditor::setSelection(const Range &selection) { - m_state.m_selection = setCoordinates(selection); + m_lines.setSelection(selection); + } + + Range TextEditor::Lines::getSelection() const { + return m_state.m_selection; } Range TextEditor::getSelection() const { - return m_state.m_selection; + return m_lines.getSelection(); } void TextEditor::selectWordUnderCursor() { @@ -535,14 +628,14 @@ namespace hex::ui { } void TextEditor::selectAll() { - setSelection(Range(Coordinates(this, 0, 0), Coordinates(this, -1, -1))); + setSelection(Range(m_lines.lineCoordinates(0, 0), m_lines.lineCoordinates(-1, -1))); } - bool TextEditor::hasSelection() const { + bool TextEditor::Lines::hasSelection() { return !isEmpty() && m_state.m_selection.m_end > m_state.m_selection.m_start; } - void TextEditor::addUndo(UndoRecords &value) { + void TextEditor::Lines::addUndo(std::vector value) { if (m_readOnly) return; @@ -551,7 +644,7 @@ namespace hex::ui { m_undoIndex++; } - TextEditor::PaletteIndex TextEditor::getColorIndexFromFlags(Line::Flags flags) { + TextEditor::PaletteIndex TextEditor::Lines::getColorIndexFromFlags(Line::Flags flags) { auto commentBits = flags.m_value & inComment; if (commentBits == (i32) Line::Comments::Global) return PaletteIndex::GlobalDocComment; @@ -587,12 +680,12 @@ namespace hex::ui { io.WantCaptureKeyboard = true; io.WantTextInput = true; - if (!m_readOnly && !ctrl && !shift && !alt && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))) + if (!m_lines.m_readOnly && !ctrl && !shift && !alt && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))) enterCharacter('\n', false); - else if (!m_readOnly && !ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Tab)) + else if (!m_lines.m_readOnly && !ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Tab)) enterCharacter('\t', shift); - if (!m_readOnly && !io.InputQueueCharacters.empty()) { + if (!m_lines.m_readOnly && !io.InputQueueCharacters.empty()) { for (i32 i = 0; i < io.InputQueueCharacters.Size; i++) { auto c = io.InputQueueCharacters[i]; if (c != 0 && (c == '\n' || c >= 32)) { @@ -623,11 +716,15 @@ namespace hex::ui { */ if (tripleClick) { + auto coordinates = screenPosCoordinates(ImGui::GetMousePos()); + if (coordinates == Invalid) + return; if (!ctrl) { - 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, lineMaxColumn(line)); + m_lines.m_state.m_cursorPosition = coordinates; + auto lineIndex = rowToLineIndex(lineIndexToRow(coordinates.m_line)); + auto line = m_lines[lineIndex]; + m_lines.m_state.m_selection.m_start = m_lines.foldedToUnfoldedCoords(Coordinates(lineIndex, 0)); + m_lines.m_state.m_selection.m_end = m_lines.foldedToUnfoldedCoords(Coordinates(lineIndex, line.maxColumn())); } m_lastClick = -1.0f; @@ -639,10 +736,13 @@ namespace hex::ui { */ else if (doubleClick) { + auto coordinates = screenPosCoordinates(ImGui::GetMousePos()); + if (coordinates == Invalid) + return; if (!ctrl) { - m_state.m_cursorPosition = screenPosToCoordinates(ImGui::GetMousePos()); - m_state.m_selection.m_start = findWordStart(m_state.m_cursorPosition); - m_state.m_selection.m_end = findWordEnd(m_state.m_cursorPosition); + m_lines.m_state.m_cursorPosition = coordinates; + m_lines.m_state.m_selection.m_start = findWordStart(m_lines.m_state.m_cursorPosition); + m_lines.m_state.m_selection.m_end = findWordEnd(m_lines.m_state.m_cursorPosition); } m_lastClick = (float) ImGui::GetTime(); @@ -653,77 +753,98 @@ namespace hex::ui { Left mouse button click */ else if (click) { + auto coordinates = screenPosCoordinates(ImGui::GetMousePos()); + if (coordinates == Invalid) + return; if (ctrl) { - m_state.m_cursorPosition = m_interactiveSelection.m_start = m_interactiveSelection.m_end = screenPosToCoordinates(ImGui::GetMousePos()); + m_lines.m_state.m_cursorPosition = m_lines.m_interactiveSelection.m_start = m_lines.m_interactiveSelection.m_end = coordinates; selectWordUnderCursor(); } else if (shift) { - m_interactiveSelection.m_end = screenPosToCoordinates(ImGui::GetMousePos()); - m_state.m_cursorPosition = m_interactiveSelection.m_end; - setSelection(m_interactiveSelection); + m_lines.m_state.m_cursorPosition = m_lines.m_interactiveSelection.m_end = coordinates; + setSelection(m_lines.m_interactiveSelection); } else { - m_state.m_cursorPosition = m_interactiveSelection.m_start = m_interactiveSelection.m_end = screenPosToCoordinates(ImGui::GetMousePos()); - setSelection(m_interactiveSelection); + m_lines.m_state.m_cursorPosition = m_lines.m_interactiveSelection.m_end = m_lines.m_interactiveSelection.m_start = coordinates; + setSelection(m_lines.m_interactiveSelection); } - resetCursorBlinkTime(); + m_lines.resetCursorBlinkTime(); - ensureCursorVisible(); + m_lines.ensureCursorVisible(); m_lastClick = (float) ImGui::GetTime(); } else if (rightClick) { - auto cursorPosition = screenPosToCoordinates(ImGui::GetMousePos()); + auto coordinates = screenPosCoordinates(ImGui::GetMousePos()); + if (coordinates == Invalid) + return; + auto cursorPosition = coordinates; - if (!hasSelection() || m_state.m_selection.m_start > cursorPosition || cursorPosition > m_state.m_selection.m_end) { - m_state.m_cursorPosition = m_interactiveSelection.m_start = m_interactiveSelection.m_end = cursorPosition; - setSelection(m_interactiveSelection); + if (!m_lines.hasSelection() || m_lines.m_state.m_selection.m_start > cursorPosition || cursorPosition > m_lines.m_state.m_selection.m_end) { + m_lines.m_state.m_cursorPosition = m_lines.m_interactiveSelection.m_start = m_lines.m_interactiveSelection.m_end = cursorPosition; + setSelection(m_lines.m_interactiveSelection); } - resetCursorBlinkTime(); + m_lines.resetCursorBlinkTime(); m_raiseContextMenu = true; ImGui::SetWindowFocus(); } // Mouse left button dragging (=> update selection) else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0)) { + auto coordinates = screenPosCoordinates(ImGui::GetMousePos()); + if (coordinates == Invalid) + return; io.WantCaptureMouse = true; - m_state.m_cursorPosition = m_interactiveSelection.m_end = screenPosToCoordinates(ImGui::GetMousePos()); - setSelection(m_interactiveSelection); - ensureCursorVisible(); + m_lines.m_state.m_cursorPosition = m_lines.m_interactiveSelection.m_end = coordinates; + setSelection(m_lines.m_interactiveSelection); + m_lines.ensureCursorVisible(); resetBlinking = true; } if (resetBlinking) - resetCursorBlinkTime(); + m_lines.resetCursorBlinkTime(); } } } + bool Lines::isTrueMatchingDelimiter() { + auto nearCoords = m_matchedDelimiter.m_nearCursor; + auto matchedCoords = m_matchedDelimiter.m_matched; + Line::Flags nearFlag(m_unfoldedLines[nearCoords.m_line].m_flags[nearCoords.m_column]); + Line::Flags matchedFlag(m_unfoldedLines[matchedCoords.m_line].m_flags[matchedCoords.m_column]); + if (nearFlag.m_bits.matchedDelimiter && matchedFlag.m_bits.matchedDelimiter) + return true; + return false; + } + // the index here is array index so zero based - void FindReplaceHandler::selectFound(TextEditor *editor, i32 found) { + void FindReplaceHandler::selectFound(Lines *lines, i32 found) { if (found < 0 || found >= (i64) m_matches.size()) return; - editor->setSelection(m_matches[found].m_selection); - editor->setCursorPosition(); + lines->setSelection(m_matches[found].m_selection); + lines->setUnfoldIfNeeded(true); + lines->setCursorPosition(); + lines->ensureSelectionNotFolded(); + lines->ensureCursorVisible(); } // The returned index is shown in the form // 'index of count' so 1 based - u32 FindReplaceHandler::findMatch(TextEditor *editor, i32 index) { + u32 FindReplaceHandler::findMatch(Lines *lines, i32 index) { - if (editor->m_textChanged || m_optionsChanged) { + if (lines->m_textChanged || m_optionsChanged) { std::string findWord = getFindWord(); if (findWord.empty()) return 0; resetMatches(); - findAllMatches(editor, findWord); + findAllMatches(lines, findWord); } - Coordinates targetPos = editor->m_state.m_cursorPosition; + Coordinates targetPos = lines->m_state.m_cursorPosition; i32 count = m_matches.size(); if (count == 0) { - editor->setCursorPosition(targetPos, true); + lines->setCursorPosition(targetPos, true); return 0; } - if (editor->hasSelection()) { + if (lines->hasSelection()) { i32 matchIndex = 0; for (i32 i = 0; i < count; i++) { - if (m_matches[i].m_selection == editor->m_state.m_selection) { + if (m_matches[i].m_selection == lines->m_state.m_selection) { matchIndex = i; break; } @@ -732,42 +853,42 @@ namespace hex::ui { while (matchIndex + index < 0) index += count; auto rem = (matchIndex + index) % count; - selectFound(editor, rem); + selectFound(lines, rem); return rem + 1; } } - targetPos = index > 0 ? editor->m_state.m_selection.m_end : (index < 0 ? editor->m_state.m_selection.m_start : editor->m_state.m_selection.m_start + Coordinates(0,1)); + targetPos = index > 0 ? lines->m_state.m_selection.m_end : (index < 0 ? lines->m_state.m_selection.m_start : lines->m_state.m_selection.m_start + lines->lineCoordinates( 0, 1)); if (index >= 0) { for (i32 i = 0; i < count; i++) { if (targetPos <= m_matches[i].m_selection.m_start) { - selectFound(editor, i); + selectFound(lines, i); return i + 1; } } - selectFound(editor, 0); + selectFound(lines, 0); return 1; } else { for (i32 i = count - 1; i >= 0; i--) { if (targetPos >= m_matches[i].m_selection.m_end) { - selectFound(editor, i); + selectFound(lines, i); return i + 1; } } - selectFound(editor, count - 1); + selectFound(lines, count - 1); return count; } } // returns 1 based index - u32 FindReplaceHandler::findPosition(TextEditor *editor, Coordinates pos, bool isNext) { - if (editor->m_textChanged || m_optionsChanged) { + u32 FindReplaceHandler::findPosition(Lines *lines, Coordinates pos, bool isNext) { + if (lines->m_textChanged || m_optionsChanged) { std::string findWord = getFindWord(); if (findWord.empty()) return 0; resetMatches(); - findAllMatches(editor, findWord); + findAllMatches(lines, findWord); } i32 count = m_matches.size(); @@ -795,8 +916,9 @@ namespace hex::ui { static const char metacharacters[] = R"(\.^$-+()[]{}|?*)"; std::string out; out.reserve(s.size()); - if (s[0] == '#') - out.push_back('#'); + if (s.empty()) + return {}; + out.push_back('#'); out.push_back('\\'); out.push_back('b'); for (auto ch: s) { @@ -809,35 +931,35 @@ namespace hex::ui { return out; } - void FindReplaceHandler::setFindWord(TextEditor *editor, const std::string &findWord) { + void FindReplaceHandler::setFindWord(Lines *lines, const std::string &findWord) { if (findWord != m_findWord) { - findAllMatches(editor, findWord); + findAllMatches(lines, findWord); m_findWord = findWord; } } - void FindReplaceHandler::setMatchCase(TextEditor *editor, bool matchCase) { + void FindReplaceHandler::setMatchCase(Lines *lines, bool matchCase) { if (matchCase != m_matchCase) { m_matchCase = matchCase; m_optionsChanged = true; - findAllMatches(editor, m_findWord); + findAllMatches(lines, m_findWord); } } - void FindReplaceHandler::setWholeWord(TextEditor *editor, bool wholeWord) { + void FindReplaceHandler::setWholeWord(Lines *lines, bool wholeWord) { if (wholeWord != m_wholeWord) { m_wholeWord = wholeWord; m_optionsChanged = true; - findAllMatches(editor, m_findWord); + findAllMatches(lines, m_findWord); } } - void FindReplaceHandler::setFindRegEx(TextEditor *editor, bool findRegEx) { + void FindReplaceHandler::setFindRegEx(Lines *lines, bool findRegEx) { if (findRegEx != m_findRegEx) { m_findRegEx = findRegEx; m_optionsChanged = true; - findAllMatches(editor, m_findWord); + findAllMatches(lines, m_findWord); } } @@ -898,7 +1020,7 @@ namespace hex::ui { }*/ // Performs actual search to fill mMatches - bool FindReplaceHandler::findNext(TextEditor *editor, u64 &byteIndex) { + bool FindReplaceHandler::findNext(Lines *lines, u64 &byteIndex) { u64 matchBytes = m_findWord.size(); @@ -906,7 +1028,7 @@ namespace hex::ui { if (!getMatchCase()) std::transform(wordLower.begin(), wordLower.end(), wordLower.begin(), ::tolower); - std::string textSrc = editor->getText(); + std::string textSrc = lines->getText(); if (!getMatchCase()) std::transform(textSrc.begin(), textSrc.end(), textSrc.begin(), ::tolower); @@ -963,7 +1085,7 @@ namespace hex::ui { if (textLoc == std::string::npos) return false; TextEditor::EditorState state; - state.m_selection = Range(TextEditor::stringIndexToCoordinates(textLoc, textSrc), TextEditor::stringIndexToCoordinates(textLoc + matchBytes, textSrc)); + state.m_selection = Range(lines->stringIndexCoords(textLoc, textSrc), lines->stringIndexCoords(textLoc + matchBytes, textSrc)); state.m_cursorPosition = state.m_selection.m_end; if (!m_matches.empty() && state == m_matches.back()) return false; @@ -972,16 +1094,16 @@ namespace hex::ui { return true; } - void FindReplaceHandler::findAllMatches(TextEditor *editor, std::string findWord) { + void FindReplaceHandler::findAllMatches(Lines *lines, std::string findWord) { if (findWord.empty()) { - editor->ensureCursorVisible(); + lines->ensureCursorVisible(); m_findWord = ""; m_matches.clear(); return; } - if (findWord == m_findWord && !editor->m_textChanged && !m_optionsChanged) + if (findWord == m_findWord && !lines->m_textChanged && !m_optionsChanged) return; if (m_optionsChanged) @@ -990,90 +1112,506 @@ namespace hex::ui { u64 byteIndex = 0; m_matches.clear(); m_findWord = findWord; - auto startingPos = editor->m_state.m_cursorPosition; - auto saveState = editor->m_state; - Coordinates begin = editor->setCoordinates(0, 0); - editor->m_state.m_cursorPosition = begin; + auto startingPos = lines->m_state.m_cursorPosition; + auto saveState = lines->m_state; + Coordinates begin = lines->lineCoordinates(0, 0); + lines->m_state.m_cursorPosition = begin; - if (!findNext(editor, byteIndex)) { - editor->m_state = saveState; - editor->ensureCursorVisible(); + if (!findNext(lines, byteIndex)) { + lines->m_state = saveState; + lines->ensureCursorVisible(); return; } TextEditor::EditorState state = m_matches.back(); while (state.m_cursorPosition < startingPos) { - if (!findNext(editor, byteIndex)) { - editor->m_state = saveState; - editor->ensureCursorVisible(); + if (!findNext(lines, byteIndex)) { + lines->m_state = saveState; + lines->ensureCursorVisible(); return; } state = m_matches.back(); } - while (findNext(editor, byteIndex)); + while (findNext(lines, byteIndex)); - editor->m_state = saveState; - editor->ensureCursorVisible(); + + lines->m_state = saveState; + lines->ensureCursorVisible(); return; } - bool FindReplaceHandler::replace(TextEditor *editor, bool right) { + bool FindReplaceHandler::replace(Lines *lines, bool right) { if (m_matches.empty() || m_findWord == m_replaceWord || m_findWord.empty()) return false; - auto state = editor->m_state; - if (editor->m_state.m_selection.contains(editor->m_state.m_cursorPosition)) { - 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->lineMaxColumn(editor->m_state.m_cursorPosition.m_line); + auto &state = lines->m_state; + if (state.m_selection.contains(state.m_cursorPosition)) { + state.m_cursorPosition = state.m_selection.m_start; + if (lines->isStartOfLine()) { + state.m_cursorPosition.m_line--; + state.m_cursorPosition.m_column = lines->lineMaxColumn(state.m_cursorPosition.m_line); } else - editor->m_state.m_cursorPosition.m_column--; + state.m_cursorPosition.m_column--; } i32 index = right ? 0 : -1; - auto matchIndex = findMatch(editor, index); + auto matchIndex = findMatch(lines, index); if (matchIndex != 0) { UndoRecord u; - u.m_before = editor->m_state; - u.m_removed = editor->getSelectedText(); - u.m_removedRange = editor->m_state.m_selection; - editor->deleteSelection(); + u.m_before = state; + u.m_removed = lines->getSelectedText(); + u.m_removedRange = state.m_selection; + lines->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); + std::string replacedText = std::regex_replace(lines->getText(), std::regex(m_findWord), m_replaceWord,std::regex_constants::format_first_only |std::regex_constants::format_no_copy); u.m_added = replacedText; } else u.m_added = m_replaceWord; - u.m_addedRange.m_start = editor->setCoordinates(editor->m_state.m_cursorPosition); - editor->insertText(u.m_added); + u.m_addedRange.m_start = lines->lineCoordinates(lines->m_state.m_cursorPosition); + lines->insertText(u.m_added); - editor->setCursorPosition(editor->m_state.m_selection.m_end); + u.m_addedRange.m_end = lines->lineCoordinates(lines->m_state.m_cursorPosition); - u.m_addedRange.m_end = editor->setCoordinates(editor->m_state.m_cursorPosition); - - editor->ensureCursorVisible(); + lines->ensureCursorVisible(); ImGui::SetKeyboardFocusHere(0); - u.m_after = editor->m_state; + u.m_after = state; m_undoBuffer.push_back(u); - editor->m_textChanged = true; + lines->m_textChanged = true; return true; } - editor->m_state = state; + lines->m_state = state; return false; } - bool FindReplaceHandler::replaceAll(TextEditor *editor) { + bool FindReplaceHandler::replaceAll(Lines *lines) { u32 count = m_matches.size(); m_undoBuffer.clear(); for (u32 i = 0; i < count; i++) - replace(editor, true); + replace(lines, true); - editor->addUndo(m_undoBuffer); + lines->addUndo(m_undoBuffer); return true; } -} + + ImRect TextEditor::Lines::getBoxForRow(u32 row) { + auto boxSize = m_charAdvance.x + (((u32) m_charAdvance.x % 2) ? 2.0f : 1.0f); + auto lineStartScreenPos = getLineStartScreenPos(0,row); + ImVec2 lineNumberStartScreenPos = ImVec2(m_lineNumbersStartPos.x + m_lineNumberFieldWidth, lineStartScreenPos.y); + ImRect result; + result.Min = ImVec2(lineNumberStartScreenPos.x - (boxSize - 1) / 2, lineNumberStartScreenPos.y); + result.Max = result.Min + ImVec2(boxSize - 1, m_charAdvance.y); + return result; + } + + void CodeFold::callback() { + if (isOpen()) + close(); + else + open(); + } + + bool CodeFold::startHovered() { + return codeFoldStartCursorBox.trigger(); + } + + bool CodeFold::endHovered() { + if (isOpen()) + return codeFoldEndCursorBox.trigger(); + return false; + } + + bool CodeFold::isOpen() { + return lines->m_codeFoldState[key]; + } + + void CodeFold::open() { + + lines->openCodeFold(key); + } + + void CodeFold::close() { + lines->closeCodeFold(key, true); + } + + void CodeFold::moveFold(float lineCount, float lineHeight) { + codeFoldStartCursorBox.shiftBoxVertically(lineCount, lineHeight); + codeFoldEndActionBox.shiftBoxVertically(lineCount, lineHeight); + codeFoldEndCursorBox.shiftBoxVertically(lineCount, lineHeight); + shiftBoxVertically(lineCount, lineHeight); + } + + Line &Lines::at(i32 index) { + if (index < -size() || index >= size()) + throw std::out_of_range("Line index out of range"); + if (index < 0) + index += size(); + auto row = lineIndexToRow(index); + if (row < 0 || row >= getGlobalRowMax()) + throw std::out_of_range("Line index out of range"); + if (!m_foldedLines.contains(row) || m_foldedLines.at(row).m_full.m_start.m_line != index) + return m_unfoldedLines.at(index); + return const_cast(m_foldedLines.at(row).m_foldedLine); + } + + Line &Lines::operator[](i32 index) { + if (size() != 0) + index = std::clamp(index,-size(), size()-1); + else + index = 0; + if (index < 0) + index += size(); + i32 row = lineIndexToRow(index); + if (getGlobalRowMax() != 0) + row = std::clamp(row,0, (i32)(getGlobalRowMax()-1)); + else + row = 0; + if (!m_foldedLines.contains(row) || m_foldedLines[row].m_full.m_start.m_line != index) + return m_unfoldedLines.at(index); + return const_cast(m_foldedLines[row].m_foldedLine); + } + + i32 Lines::size() const { + return (i32)m_unfoldedLines.size(); + } + + void clearCodeFolds(); + + u32 TextEditor::getfirstNonWhite(u32 lineIndex) { + if (lineIndex >= (u64)m_lines.size()) + return 0; + auto line = m_lines[lineIndex]; + u64 i = 0; + while (i < line.size() && line[i] == ' ') + i++; + return i + 1; + } + + i32 TextEditor::getTotalLines() const { + return (i32) m_lines.size(); + } + + using CodeFoldBlocks = TextEditor::CodeFoldBlocks; + //static std::chrono::steady_clock::time_point currentTime1; + void TextEditor::Lines::setAllCodeFolds() { + //std::chrono::steady_clock::time_point beforeLoop; + //std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + //std::chrono::duration time_span = now - currentTime1; + //auto lastCharInputTime = time_span.count(); + //log::debug("Before loading 1st id of line : {} ms", lastCharInputTime); + CodeFoldBlocks intervals = foldPointsFromSource(); + m_codeFoldKeys.clear(); + m_codeFolds.clear(); + m_codeFoldKeyMap.clear(); + m_codeFoldValueMap.clear(); + m_codeFoldKeyLineMap.clear(); + m_codeFoldValueLineMap.clear(); + m_codeFoldDelimiters.clear(); + for (auto interval : intervals) { + Range foldInterval(interval); + if (foldInterval.m_start.m_line > size() || foldInterval.m_end.m_line > size()) + return; + std::pair foldDelimiters = {}; + foldDelimiters.second = m_unfoldedLines[foldInterval.m_end.m_line].m_chars[foldInterval.m_end.m_column]; + if (foldDelimiters.second == '/') + foldDelimiters.first = foldDelimiters.second; + else + foldDelimiters.first = foldDelimiters.second - 2 + (foldDelimiters.second == ')'); + m_codeFoldKeys.insert(foldInterval); + m_codeFoldDelimiters[foldInterval] = foldDelimiters; + if (!m_codeFoldKeyMap.contains(foldInterval.m_start)) + m_codeFoldKeyMap[foldInterval.m_start] = foldInterval.m_end; + if (!m_codeFoldValueMap.contains(foldInterval.m_end)) + m_codeFoldValueMap[foldInterval.m_end] = foldInterval.m_start; + m_codeFoldKeyLineMap.insert(std::make_pair(foldInterval.m_start.m_line,foldInterval.m_start)); + m_codeFoldValueLineMap.insert(std::make_pair(foldInterval.m_end.m_line,foldInterval.m_end)); + } + + std::vector deactivated = getDeactivatedBlocks(); + for (auto interval : deactivated) { + m_codeFoldKeys.insert(interval); + if (!m_codeFoldKeyMap.contains(interval.m_start)) + m_codeFoldKeyMap[interval.m_start] = interval.m_end; + if (!m_codeFoldValueMap.contains(interval.m_end)) + m_codeFoldValueMap[interval.m_end] = interval.m_start; + m_codeFoldKeyLineMap.insert(std::make_pair(interval.m_start.m_line,interval.m_start)); + m_codeFoldValueLineMap.insert(std::make_pair(interval.m_end.m_line,interval.m_end)); + } + //currentTime1 = std::chrono::steady_clock::now(); + //time_span = currentTime1 - now; + //lastCharInputTime = time_span.count(); + // log::debug("After loading 1st id of line : {} ms", lastCharInputTime); + } + + void TextEditor::Lines::readHiddenLines() { + m_hiddenLines.clear(); + i32 lineIndex = 0; + const auto totalLines = (i32)m_unfoldedLines.size(); + while (lineIndex < totalLines && m_unfoldedLines[lineIndex].m_chars.starts_with("//+-")) + lineIndex++; + if (lineIndex > 0) { + setSelection(Range(lineCoordinates(0, 0), lineCoordinates(lineIndex, 0))); + auto hiddenLinesText = getSelectedText(); + auto lines = wolv::util::splitString(hiddenLinesText, "\n"); + for (i32 i = 0; i < lineIndex; i++) { + HiddenLine hiddenLine(i, m_unfoldedLines[i].m_chars); + m_hiddenLines.push_back(hiddenLine); + m_useSavedFoldStatesRequested = true; + } + deleteSelection(); + } + setAllCodeFolds(); + } + + void TextEditor::Lines::writeHiddenLines() { + if (m_hiddenLines.empty()) + return; + for (const auto &hiddenLine : m_hiddenLines) { + i32 lineIndex; + if (hiddenLine.m_lineIndex < 0) + lineIndex = size() + hiddenLine.m_lineIndex; + else + lineIndex = hiddenLine.m_lineIndex; + insertLine(lineIndex, hiddenLine.m_line); + } + } + + i32 TextEditor::getCodeFoldLevel(i32 line) const { + return std::count_if(m_lines.m_codeFoldKeys.begin(), m_lines.m_codeFoldKeys.end(), [line](const Range &key) { + return key.m_start.m_line <= line && key.m_end.m_line >= line; + }); + } + + void TextEditor::codeFoldCollapse(i32 level, bool recursive, bool all) { + + for (auto [key, codeFold]: m_lines.m_codeFolds) { + if (key.containsLine(m_lines.m_state.m_cursorPosition.m_line) || all) { + if (getCodeFoldLevel(key.m_start.m_line) >= level || level == 0) { + if (m_lines.m_codeFoldState.contains(key) && m_lines.m_codeFoldState[key]) { + m_lines.m_codeFoldState[key] = false; + m_lines.m_saveCodeFoldStateRequested = true; + } + if (recursive) { + for (const auto &k: m_lines.m_codeFoldKeys) { + if (key.contains(k) && m_lines.m_codeFoldState.contains(k) && m_lines.m_codeFoldState[k]) { + m_lines.m_codeFoldState[k] = false; + m_lines.m_saveCodeFoldStateRequested = true; + } + } + } + if (!all) { + break; + } + } + } + } + } + + void TextEditor::codeFoldExpand(i32 level, bool recursive, bool all) { + for (auto [key, codeFold]: m_lines.m_codeFolds) { + if (key.containsLine(m_lines.m_state.m_cursorPosition.m_line) || all) { + if (getCodeFoldLevel(key.m_start.m_line) >= level || level == 0) { + if (m_lines.m_codeFoldState.contains(key) && !m_lines.m_codeFoldState[key]) { + m_lines.m_codeFoldState[key] = true; + m_lines.m_saveCodeFoldStateRequested = true; + } + if (recursive) { + for (const auto &k: m_lines.m_codeFoldKeys) { + if (key.contains(k) && m_lines.m_codeFoldState.contains(k) && !m_lines.m_codeFoldState[k]) { + m_lines.m_codeFoldState[k] = true; + m_lines.m_saveCodeFoldStateRequested = true; + } + } + } + if (!all) { + break; + } + } + } + } + } + bool TextEditor::Lines::isTokenIdValid(i32 tokenId) { + return tokenId >= 0 && tokenId < (i32) m_tokens.size(); + } + + bool TextEditor::Lines::isLocationValid(Location location) { + const pl::api::Source *source; + try { + source = location.source; + } catch (const std::out_of_range &e) { + log::error("TextHighlighter::IsLocationValid: Out of range error: {}", e.what()); + return false; + } + if (source == nullptr) + return false; + i32 line = location.line - 1; + i32 col = location.column - 1; + i32 length = location.length; + + if ( line < 0 || line >= (i32) size()) + return false; + + if ( col < 0 || col > (i32) operator[](line).size()) + return false; + if (length < 0) + return false; + if (length > (i32) operator[](line).size()-col) + length -= (i32)( operator[](line).size()-col); + while (line + 1 < size() && m_firstTokenIdOfLine[line] == m_firstTokenIdOfLine[line + 1]) { + length -= (i32) operator[](line).size(); + line++; + } + + + if (length > (i32) operator[](line).size()-col && m_firstTokenIdOfLine[line + 1] != -1) + return false; + return true; + } + + void TextEditor::Lines::loadFirstTokenIdOfLine() { + auto code = getText(); + if (code.empty()) + return; + std::unique_ptr runtime = std::make_unique(); + ContentRegistry::PatternLanguage::configureRuntime(*runtime, nullptr); + std::ignore = runtime->preprocessString(code, pl::api::Source::DefaultSource); + m_tokens = runtime->getInternals().preprocessor->getResult(); + const u32 tokenCount = m_tokens.size(); + if (tokenCount == 0) + return; + + m_firstTokenIdOfLine.clear(); + u32 tokenId = 0; + u32 lineIndex = m_tokens.at(tokenId).location.line - 1; + const u32 count = size(); + m_firstTokenIdOfLine.resize(count, -1); + m_firstTokenIdOfLine.at(lineIndex) = 0; + tokenId++; + constexpr auto commentType = pl::core::Token::Type::Comment; + constexpr auto docCommentType = pl::core::Token::Type::DocComment; + u32 currentLine = lineIndex; + while (currentLine < count) { + while (tokenId < tokenCount && (currentLine >= lineIndex)) { + lineIndex = m_tokens.at(tokenId).location.line - 1; + tokenId++; + } + if (tokenId > tokenCount - 1) + break; + if (tokenId > 0) + tokenId--; + auto type = m_tokens.at(tokenId).type; + if ((type == docCommentType || type == commentType)) { + auto *comment = std::get_if(&(m_tokens.at(tokenId).value)); + auto *docComment = std::get_if(&(m_tokens.at(tokenId).value)); + if ((comment != nullptr && !comment->singleLine) || (docComment != nullptr && !docComment->singleLine)) { + auto commentTokenId = tokenId; + auto commentStartLine = m_tokens.at(tokenId).location.line - 1; + std::string value = m_tokens.at(tokenId).getFormattedValue(); + auto commentEndLine = commentStartLine + std::count(value.begin(), value.end(), '\n'); + //m_firstTokenIdOfLine.at(commentStartLine) = commentTokenId; + for (u32 i = commentStartLine; i <= commentEndLine; i++) { + m_firstTokenIdOfLine.at(i) = commentTokenId; + } + lineIndex = commentEndLine; + } else + m_firstTokenIdOfLine.at(lineIndex) = tokenId; + } else + m_firstTokenIdOfLine.at(lineIndex) = tokenId; + tokenId++; + currentLine = lineIndex; + } + + // if (lineIndex > currentLine) { + // m_firstTokenIdOfLine[lineIndex] = tokenId; + // } + // } + + if (tokenCount > 0 && (u32) m_firstTokenIdOfLine.back() != tokenCount - 1) + m_firstTokenIdOfLine.push_back(tokenCount - 1); +/* + i32 lastTokenIdRestored; + bool lastTokenIdChanged; + i32 savedLineIndex; + lineIndex = 0; + while (lineIndex < (i32) size()) { + if (m_firstTokenIdOfLine[lineIndex] == -1 && (operator[](lineIndex).empty() || operator[](lineIndex).m_chars.find_first_not_of(" ") == std::string::npos || operator[](lineIndex).m_chars.starts_with("#"))) { + if (lastTokenIdChanged) + savedLineIndex = lineIndex; + lastTokenIdChanged = false; + lineIndex++; + } else if (m_firstTokenIdOfLine[lineIndex] != -1 && !operator[](lineIndex).empty() && operator[](lineIndex).m_chars.find_first_not_of(" ") != std::string::npos && !operator[](lineIndex).m_chars.starts_with("#")) { + tokenId = m_firstTokenIdOfLine[lineIndex]; + if (tokenId != lastTokenIdRestored) + lastTokenIdChanged = true; + lineIndex++; + } else if (m_firstTokenIdOfLine[lineIndex] == -1 && !operator[](lineIndex).empty() && operator[](lineIndex).m_chars.find_first_not_of(" ") != std::string::npos && !operator[](lineIndex).m_chars.starts_with("#")) { + m_firstTokenIdOfLine[lineIndex] = tokenId; + if (!lastTokenIdChanged) { + for (i32 i = savedLineIndex; i < lineIndex; i++) + m_firstTokenIdOfLine[i] = tokenId; + } + if (tokenId != lastTokenIdRestored) { + lastTokenIdRestored = tokenId; + lastTokenIdChanged = true; + } + lineIndex++; + } else + lineIndex ++; + }*/ + + } + + pl::core::Location TextEditor::Lines::getLocation(i32 tokenId) { + + if (tokenId >= (i32) m_tokens.size()) + return Location::Empty(); + return m_tokens[tokenId].location; + } + + +// Get the token index for a given location. + i32 TextEditor::Lines::getTokenId(pl::core::Location location) { + if (location == m_tokens.at(0).location) + return 0; + if (location == m_tokens.back().location) + return (i32) m_tokens.size() - 1; + + if (!isLocationValid(location)) + return -1; + i32 line1 = location.line - 1; + i32 line2 = nextLine(line1); + auto tokenCount = m_tokens.size(); + i32 lineCount = m_firstTokenIdOfLine.size(); + if (line1 < 0 || line1 >= lineCount || tokenCount == 0) + return -1; + i32 tokenStart = m_firstTokenIdOfLine[line1]; + i32 tokenEnd = tokenCount - 1; + if (line2 < lineCount && line2 >= 0) + tokenEnd = m_firstTokenIdOfLine[line2] - 1; + + if (tokenStart == -1 || tokenEnd == -1 || tokenStart >= (i32) tokenCount) + return -1; + + for (i32 i = tokenStart; i <= tokenEnd; i++) { + auto length = m_tokens[i].location.length; + if (m_tokens[i].location.column + length + (length == 0) > location.column) + return i; + } + return -1; + } + + i32 TextEditor::Lines::nextLine(i32 line) { + if (line < 0 || line >= size()) + return -1; + auto currentTokenId = m_firstTokenIdOfLine[line]; + i32 i = 1; + while (line + i < size() && + (m_firstTokenIdOfLine[line + i] == currentTokenId || m_firstTokenIdOfLine[line + i] == (i32) 0xFFFFFFFF)) + i++; + return i + line; + } +} \ No newline at end of file diff --git a/plugins/ui/source/ui/text_editor/utf8.cpp b/plugins/ui/source/ui/text_editor/utf8.cpp index 85895b17a..cbf9839a4 100644 --- a/plugins/ui/source/ui/text_editor/utf8.cpp +++ b/plugins/ui/source/ui/text_editor/utf8.cpp @@ -6,6 +6,9 @@ namespace hex::ui { + + using Coordinates = TextEditor::Coordinates; + TextEditor::Line TextEditor::Line::trim(TrimMode trimMode) { if (m_chars.empty()) return m_emptyLine; @@ -127,11 +130,11 @@ namespace hex::ui { return line.indexColumn(stringIndex); } - i32 TextEditor::lineMaxColumn(i32 lineIndex) { - if (lineIndex >= (i64) m_lines.size() || lineIndex < 0) + i32 TextEditor::Lines::lineMaxColumn(i32 lineIndex) { + if (lineIndex >= (i64) size() || lineIndex < 0) return 0; - return m_lines[lineIndex].maxColumn(); + return operator[](lineIndex).maxColumn(); } // "Borrowed" from ImGui source @@ -174,15 +177,34 @@ namespace hex::ui { return size; } - TextEditor::Coordinates TextEditor::screenPosToCoordinates(const ImVec2 &position) { - ImVec2 local = position - ImGui::GetCursorScreenPos(); - i32 lineNo = std::max(0, (i32) floor(local.y / m_charAdvance.y)); - if (lineNo >= (i32) m_lines.size()) - return setCoordinates((i32) m_lines.size() - 1, -1); - else if (local.x < (m_leftMargin - 2_scaled) || m_lines[lineNo].empty()) - return setCoordinates(lineNo, 0); - std::string line = m_lines[lineNo].m_chars; - local.x -= (m_leftMargin - 5_scaled); + Coordinates TextEditor::screenPosCoordinates(const ImVec2 &position) { + auto boxSize = m_lines.m_charAdvance.x + (((u32)m_lines.m_charAdvance.x % 2) ? 2.0f : 1.0f); + if (m_lines.isEmpty()) + return m_lines.lineCoordinates( 0, 0); + auto lineSize = m_lines.size(); + if (position.y > m_lines.getLineStartScreenPos(0, lineIndexToRow(lineSize - 1)).y + m_lines.m_charAdvance.y) + return m_lines.lineCoordinates( -1, -1); + //if (position.y > m_lines.m_lineIndexToScreen[lineSize - 1].y + m_charAdvance.y) + // return m_lines.lineCoordinates( -1, -1); + + auto local = position - m_lines.m_cursorScreenPosition; + auto row = screenPosToRow(position); + + Coordinates result = lineCoordinates(0,0); + i32 lineIndex= rowToLineIndex((i32) std::floor(row)); + if (lineIndex < 0 || lineIndex >= (i32) m_lines.size()) + return Invalid; + result.m_line = lineIndex; + if (m_lines.m_codeFoldKeyLineMap.contains(lineIndex) || m_lines.m_codeFoldValueLineMap.contains(lineIndex)) { + if (local.x < (boxSize - 1)/2) + return Invalid; + } + else if (local.x < 0 || m_lines[result.m_line].empty()) + return m_lines.lineCoordinates( result.m_line, 0); + + + auto &line = m_lines[result.m_line].m_chars; + //local.x -= (m_leftMargin - 5_scaled); i32 count = 0; u64 length; i32 increase; @@ -190,17 +212,17 @@ namespace hex::ui { increase = TextEditor::utf8CharLength(line[count]); count += increase; std::string partialLine = line.substr(0, count); - length = ImGui::CalcTextSize(partialLine.c_str(), nullptr, false, m_charAdvance.x * count).x; + length = ImGui::CalcTextSize(partialLine.c_str(), nullptr, false, m_lines.m_charAdvance.x * count).x; } while (length < local.x && count < (i32) line.size() + increase); - auto result = getCharacterCoordinates(lineNo, count - increase); - result = setCoordinates(result); + result = m_lines.lineIndexCoords(lineIndex + 1, count - increase); + result = m_lines.foldedToUnfoldedCoords(result); if (result == Invalid) - return {0, 0}; + return Coordinates(0, 0); return result; } - TextEditor::Coordinates TextEditor::lineCoordsToIndexCoords(const Coordinates &coordinates) const { + Coordinates TextEditor::lineCoordsToIndexCoords(const Coordinates &coordinates) { if (coordinates.m_line >= (i64) m_lines.size()) return Invalid; @@ -208,38 +230,282 @@ namespace hex::ui { return {coordinates.m_line,line.columnIndex(coordinates.m_column)}; } - i32 TextEditor::lineCoordinatesToIndex(const Coordinates &coordinates) const { - if (coordinates.m_line >= (i64) m_lines.size()) + i32 TextEditor::Lines::lineCoordsIndex(const Coordinates &coordinates) { + if (coordinates.m_line >= (i64) size()) return -1; - const auto &line = m_lines[coordinates.m_line]; + auto &line = operator[](coordinates.m_line); return line.columnIndex(coordinates.m_column); } - TextEditor::Coordinates TextEditor::getCharacterCoordinates(i32 lineIndex, i32 strIndex) { - if (lineIndex < 0 || lineIndex >= (i32) m_lines.size()) - return {0, 0}; - auto &line = m_lines[lineIndex]; - return setCoordinates(lineIndex, line.indexColumn(strIndex)); + Coordinates TextEditor::Lines::lineIndexCoords(i32 lineNumber, i32 stringIndex) { + if (lineNumber < 1 || lineNumber > (i32) size()) + return lineCoordinates( 0, 0); + auto &line = operator[](lineNumber - 1); + return lineCoordinates(lineNumber - 1, line.indexColumn(stringIndex)); } - u64 TextEditor::getLineByteCount(i32 lineIndex) const { - if (lineIndex >= (i64) m_lines.size() || lineIndex < 0) - return 0; + std::vector TextEditor::Lines::unfoldedEllipsisCoordinates(Range delimiterCoordinates) { - auto &line = m_lines[lineIndex]; - return line.size(); + auto lineStart = m_unfoldedLines[delimiterCoordinates.m_start.m_line]; + auto row = lineIndexToRow(delimiterCoordinates.m_start.m_line); + float unfoldedSpan1, unfoldedSpan2, unfoldedSpan3; + float unprocessedSpan1, unprocessedSpan2, unprocessedSpan3; + bool adddsBothEnds = true; + if (delimiterCoordinates.m_start.m_line == delimiterCoordinates.m_end.m_line) { + unprocessedSpan1 = unfoldedSpan1 = delimiterCoordinates.m_end.m_column - delimiterCoordinates.m_start.m_column - 1; + unprocessedSpan3 = unfoldedSpan3 = 0.0f; + unprocessedSpan2 = unfoldedSpan2 = 0.0f; + } else if (!m_foldedLines[row].addsFullFirstLineToFold() && !m_foldedLines[row].addsLastLineToFold()) { + adddsBothEnds = false; + auto innerLine = m_unfoldedLines[delimiterCoordinates.m_start.m_line]; + unprocessedSpan1 = unfoldedSpan1 = std::max(innerLine.maxColumn() - 1, 0); + innerLine = m_unfoldedLines[delimiterCoordinates.m_end.m_line]; + unprocessedSpan3 = unfoldedSpan3 = std::max(innerLine.maxColumn() - 1, 0); + unfoldedSpan2 = 0; + for (i32 j = delimiterCoordinates.m_start.m_line + 1; j < delimiterCoordinates.m_end.m_line; j++) { + innerLine = m_unfoldedLines[j]; + unfoldedSpan2 += innerLine.maxColumn(); + } + unprocessedSpan2 = unfoldedSpan2; + } else { + unprocessedSpan1 = unfoldedSpan1 = std::max(lineStart.maxColumn() - delimiterCoordinates.m_start.m_column - 2, 0); + unprocessedSpan3 = unfoldedSpan3 = std::max(delimiterCoordinates.m_end.m_column - 1, 0); + unfoldedSpan2 = 0; + for (i32 j = delimiterCoordinates.m_start.m_line + 1; j < delimiterCoordinates.m_end.m_line; j++) { + auto innerLine = m_unfoldedLines[j]; + unfoldedSpan2 += innerLine.maxColumn(); + } + unprocessedSpan2 = unfoldedSpan2; + } + + auto totalUnfoldedSpan = unfoldedSpan1 + unfoldedSpan2 + unfoldedSpan3; + if (totalUnfoldedSpan < 2.0f) { + std::vector unfoldedEllipsisCoordinates(2); + unfoldedEllipsisCoordinates[0] = lineCoordinates( delimiterCoordinates.m_start.m_line, delimiterCoordinates.m_start.m_column + 1); + unfoldedEllipsisCoordinates[1] = delimiterCoordinates.m_end; + return unfoldedEllipsisCoordinates; + } + + float spanFragment = totalUnfoldedSpan / 2.0f; + + std::vector unfoldedEllipsisCoordinates(4); + if (adddsBothEnds) { + unfoldedEllipsisCoordinates[0] = lineCoordinates(delimiterCoordinates.m_start.m_line, delimiterCoordinates.m_start.m_column + 1); + unfoldedEllipsisCoordinates[3] = delimiterCoordinates.m_end; + } else { + unfoldedEllipsisCoordinates[0] = delimiterCoordinates.m_start; + unfoldedEllipsisCoordinates[1] = lineCoordinates(delimiterCoordinates.m_start.m_line, delimiterCoordinates.m_start.m_column + 1); + unfoldedEllipsisCoordinates[2] = delimiterCoordinates.m_end; + unfoldedEllipsisCoordinates[3] = lineCoordinates(delimiterCoordinates.m_end.m_line, delimiterCoordinates.m_end.m_column + 1); + return unfoldedEllipsisCoordinates; + } + + + i32 i = 1; + while ((unprocessedSpan1 > spanFragment || std::fabs(unprocessedSpan1-spanFragment) < 0.001) && i < 3) { + unfoldedEllipsisCoordinates[i] = lineCoordinates( unfoldedEllipsisCoordinates[0].m_line, 1 + unfoldedEllipsisCoordinates[0].m_column + std::round(i * spanFragment)); + unprocessedSpan1 -= spanFragment; + i++; + } + auto leftOver = unprocessedSpan1; + unprocessedSpan2 += leftOver; + if ((unprocessedSpan2 > spanFragment || std::fabs(unprocessedSpan2 - spanFragment) < 0.001) && i < 3) { + float lineLength = 0.0f; + for (i32 j = delimiterCoordinates.m_start.m_line + 1; j < delimiterCoordinates.m_end.m_line; j++) { + auto currentLineLength = (float) m_unfoldedLines[j].maxColumn(); + lineLength += currentLineLength + leftOver; + leftOver = 0.0f; + while ((lineLength > spanFragment || std::fabs(lineLength-spanFragment) < 0.001) && i < 3) { + unfoldedEllipsisCoordinates[i] = lineCoordinates( j, std::round(currentLineLength - lineLength + spanFragment)); + unprocessedSpan2 -= spanFragment; + lineLength -= spanFragment; + i++; + } + } + } + unprocessedSpan3 += unprocessedSpan2; + leftOver = unprocessedSpan2; + auto firstI = i; + while ((unprocessedSpan3 >= spanFragment || std::fabs(unprocessedSpan3-spanFragment) < 0.001) && i < 3) { + unfoldedEllipsisCoordinates[i] = lineCoordinates( unfoldedEllipsisCoordinates[3].m_line, std::round((i - firstI + 1) * (spanFragment - leftOver))); + unprocessedSpan3 -= spanFragment; + i++; + } + + return unfoldedEllipsisCoordinates; } - TextEditor::Coordinates TextEditor::stringIndexToCoordinates(i32 strIndex, const std::string &input) { + Coordinates TextEditor::Lines::foldedToUnfoldedCoords(const Coordinates &coords) { + auto row = lineIndexToRow(coords.m_line); + if (row == -1.0 || !m_foldedLines.contains(row)) + return coords; + FoldedLine foldedLine = m_foldedLines[row]; + auto foldedSegments = foldedLine.m_foldedSegments; + i32 foundIndex = -1; + i32 loopLimit = (i32) 2 * foldedLine.m_keys.size(); + if (loopLimit == 0) + return coords; + Range::EndsInclusive endsInclusive = Range::EndsInclusive::Start; + for (i32 i = 0; i <= loopLimit; i++) { + if (Range(foldedSegments[i], foldedSegments[i + 1]).contains(coords, endsInclusive)) { + foundIndex = i; + break; + } + + if ((i + 1) % 2) + endsInclusive = Range::EndsInclusive::Both; + else if ((i + 1) == loopLimit) + endsInclusive = Range::EndsInclusive::End; + else + endsInclusive = Range::EndsInclusive::None; + + } + if (foundIndex < 0) + return coords; + + Range key = foldedLine.m_keys[(foundIndex / 2)-(foundIndex == loopLimit)]; + + if (foundIndex % 2) { + + Range delimiterRange = foldedLine.findDelimiterCoordinates(key); + std::vector unfoldedEllipsisCoordinates = this->unfoldedEllipsisCoordinates(delimiterRange); + + if (unfoldedEllipsisCoordinates.size() > 2) + return unfoldedEllipsisCoordinates[coords.m_column - foldedLine.m_ellipsisIndices[foundIndex / 2]]; + else { + auto ellipsisColumn = foldedLine.m_ellipsisIndices[foundIndex / 2]; + if (coords.m_column == ellipsisColumn || coords.m_column == ellipsisColumn + 2) + return unfoldedEllipsisCoordinates[0]; + else + return unfoldedEllipsisCoordinates[1]; + } + } else { + auto unfoldedSegmentStart = foldedLine.m_unfoldedSegments[foundIndex]; + auto foldedSegmentStart = foldedLine.m_foldedSegments[foundIndex]; + if (foundIndex == 0) { + if (lineNeedsDelimiter(key.m_start.m_line)) { + auto line = m_unfoldedLines[key.m_start.m_line]; + auto delimiterCoordinates = foldedLine.findDelimiterCoordinates(key); + if (coords.m_column > line.maxColumn()) + return delimiterCoordinates.m_start; + else + return lineCoordinates( unfoldedSegmentStart.m_line, coords.m_column); + } + return lineCoordinates( unfoldedSegmentStart.m_line, coords.m_column); + } else + return lineCoordinates( unfoldedSegmentStart.m_line, coords.m_column - foldedSegmentStart.m_column + unfoldedSegmentStart.m_column); + } + } + + Coordinates TextEditor::nextCoordinate(TextEditor::Coordinates coordinate) { + auto line = m_lines[coordinate.m_line]; + if (line.isEndOfLine(coordinate.m_column)) + return Coordinates(coordinate.m_line + 1, 0); + else + return Coordinates(coordinate.m_line,coordinate.m_column + 1); + } + bool TextEditor::testfoldMaps(TextEditor::Range toTest) { + bool result = true; + for (auto test = toTest.getStart(); test <= toTest.getEnd(); test = nextCoordinate(test)) { + auto data = test; + auto folded = m_lines.unfoldedToFoldedCoords(data); + auto unfolded = m_lines.foldedToUnfoldedCoords(folded); + + result = result && (data == unfolded); + } + return result; + } + + Coordinates TextEditor::Lines::unfoldedToFoldedCoords(const Coordinates &coords) { + auto row = lineIndexToRow(coords.m_line); + Coordinates result; + if (row == -1 || !m_foldedLines.contains(row)) + return coords; + + FoldedLine foldedLine = m_foldedLines[row]; + result.m_line = foldedLine.m_full.m_start.m_line; + + i32 foundIndex = -1; + i32 loopLimit = (i32) 2 * foldedLine.m_keys.size(); + + if (loopLimit == 0) + return coords; + Range::EndsInclusive endsInclusive = Range::EndsInclusive::Start; + for (i32 i = 0; i <= loopLimit; i++) { + Range range = Range(foldedLine.m_unfoldedSegments[i], foldedLine.m_unfoldedSegments[i + 1]); + + if (range.contains(coords, endsInclusive)) { + foundIndex = i; + break; + } + if ((i + 1) % 2) + endsInclusive = Range::EndsInclusive::Both; + else if ((i + 1) == loopLimit) + endsInclusive = Range::EndsInclusive::End; + else + endsInclusive = Range::EndsInclusive::None; + } + if (foundIndex < 0) + return coords; + + Range key = foldedLine.m_keys[(foundIndex / 2) - (foundIndex == loopLimit)]; + + if (foundIndex % 2) { + result.m_column = foldedLine.m_ellipsisIndices[foundIndex / 2]; + Range delimiterRange = foldedLine.findDelimiterCoordinates(key); + std::vector unfoldedEllipsisCoordinates = this->unfoldedEllipsisCoordinates(delimiterRange); + + if (unfoldedEllipsisCoordinates.size() > 2) { + if (coords == unfoldedEllipsisCoordinates[0]) + return result; + if (coords == unfoldedEllipsisCoordinates[3]) { + result.m_column += 3; + return result; + } + if (Range(unfoldedEllipsisCoordinates[0], unfoldedEllipsisCoordinates[1]).contains(coords, Range::EndsInclusive::End)) { + result.m_column += 1; + return result; + } + if (Range(unfoldedEllipsisCoordinates[1], unfoldedEllipsisCoordinates[2]).contains(coords, Range::EndsInclusive::End)) { + result.m_column += 2; + return result; + } + return coords; + } else { + if (coords > unfoldedEllipsisCoordinates[0]) + result.m_column += 3; + return result; + } + } else { + if (foundIndex == 0) { + if (foldedLine.firstLineNeedsDelimiter()) { + auto line = m_unfoldedLines[foldedLine.m_full.m_start.m_line]; + if (coords > lineCoordinates(foldedLine.m_full.m_start.m_line, line.maxColumn())) + result.m_column = foldedLine.m_ellipsisIndices[0] - 1; + else + result.m_column = coords.m_column; + return result; + } + + result.m_column = coords.m_column; + return result; + } else + result.m_column = coords.m_column - foldedLine.m_unfoldedSegments[foundIndex].m_column + foldedLine.m_foldedSegments[foundIndex].m_column; + + return result; + } + } + + Coordinates TextEditor::Lines::stringIndexCoords(i32 strIndex, const std::string &input) { if (strIndex < 0 || strIndex > (i32) input.size()) - return {0, 0}; + return lineCoordinates( 0, 0); std::string str = input.substr(0, strIndex); - i32 line = std::count(str.begin(), str.end(), '\n'); + auto line = std::count(str.begin(), str.end(), '\n'); auto index = str.find_last_of('\n'); str = str.substr(index + 1); - i32 col = TextEditor::stringCharacterCount(str); + auto col = stringCharacterCount(str); - return {line, col}; + return lineCoordinates( line, col); } }