Code fold support for pattern editor that mimics Clion's but with additional features

- Supports open/close delimiters {} () [] <> and keywords import, #include, #ifdef/#ifndef
- Supports block comments (doc or not) and consecutive single line comments.
- Closed folds are retained when exiting and reopening ImHex (saved in  pattern file itself)
- Folds can be chained together by closing and opening successive folds on the same line (only for delimited folds)
- Supports all styles for delimiter placing and allows comments between head and opening delimiter.
- Tooltip shows unfolded code when ellipsis are hovered.
- line numbers update on folded line navigation.
- Line+Column numbers displayed on tooltip by hovering line number field and pressing the shift key.
- Selections and breakpoints rendered on fold sections.
- Vertical line on left margin for matching delimiter visualization.
- Intuitive fold indicators inspired by Clion's editor code folds.
- Fold indicators change dynamically and highlight on hovering.
- Shortcuts to open/close single/all folds or recursively using same keys as Clion's editor.
- Folds open automatically on edits, find/replace, and breakpoints hit.
- Folds also open by clicking on ellipsis.
- Entirely original source code with no third party dependencies.

Not implemented:
- Custom folds based on selection. There is no syntax in pattern language to define independent blocks

Other fixes/changes/improvements
- Improved line number background color for better visibility.
This commit is contained in:
paxcut
2025-12-30 13:44:49 -07:00
committed by paxcut
parent 854535fec6
commit d4879572fa
14 changed files with 5257 additions and 1466 deletions

2
dist/ImHex.run.xml vendored
View File

@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ImHex" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$CMakeCurrentBuildDir$" PASS_PARENT_ENVS_2="true" PROJECT_NAME="ImHex" TARGET_NAME="imhex_all" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="ImHex" RUN_TARGET_NAME="main">
<configuration default="false" name="ImHex" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$CMakeCurrentBuildDir$" PASS_PARENT_ENVS_2="true" PROJECT_NAME="ImHex" TARGET_NAME="imhex_all" CONFIG_NAME="Debug-MinGW" RUN_TARGET_PROJECT_NAME="ImHex" RUN_TARGET_NAME="main">
<envs>
<env name="NO_DEBUG_BANNER" value="1" />
</envs>

View File

@@ -1,26 +1,79 @@
#pragma once
#include <pl/core/token.hpp>
#include <pl/core/preprocessor.hpp>
#include <pl/pattern_language.hpp>
#include <pl/helpers/safe_iterator.hpp>
#include <ui/text_editor.hpp>
#include <hex/helpers/types.hpp>
#include <hex/helpers/logger.hpp>
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<std::string,Interval>;
using OrderedBlocks = std::map<Interval,std::string>;
using Scopes = std::set<Interval>;
using UnorderedBlocks = std::map<std::string,TokenInterval>;
using OrderedBlocks = std::map<TokenInterval,std::string>;
using Scopes = std::set<TokenInterval>;
using Location = pl::core::Location;
using VectorString = std::vector<std::string>;
using TokenIter = pl::hlp::SafeIterator<std::vector<Token>::const_iterator>;
@@ -36,6 +89,7 @@ namespace hex::plugin::builtin {
using TokenSequence = std::vector<Token>;
using TokenIdVector = std::vector<i32>;
using Instances = std::map<std::string,std::vector<i32>>;
using CodeFoldBlocks = ui::TextEditor::CodeFoldBlocks;
struct ParentDefinition;
struct Definition {
@@ -83,6 +137,7 @@ namespace hex::plugin::builtin {
using Variables = std::map<std::string,std::vector<Definition>>;
/// to define UDT and function variables
using VariableMap = std::map<std::string,Variables>;
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<bool> m_needsToUpdateColors = true;
//std::atomic<bool> m_wasInterrupted = false;
//std::atomic<bool> m_interrupt = false;
//std::atomic<bool> 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<TokenInterval> 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);

View File

@@ -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",

View File

@@ -11,61 +11,21 @@
#include <toasts/toast_notification.hpp>
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<TokenInterval> TextHighlighter::searchRangeForBlocks(TokenInterval interval) {
m_curr = m_startToken + interval.m_start;
std::vector<TokenInterval> result;
u32 nestedLevel = 0;
std::vector<i32> 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<Token> keywords, UnorderedBlocks &tokenRange,
OrderedBlocks &tokenRangeInv, bool fullName, VariableScopes *blocks) {
bool addArgumentBlock = !fullName;
std::vector<i32> 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<TokenInterval> 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<Identifier *> 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<Identifier>(&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<Identifier>(&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<Identifier>(&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<Token::Identifier>(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<Operator>(&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<Keyword>(&m_requiredInputs.fullTokens.at(range.start).value); endToken > m_curr; next()) {
for (keyword = std::get_if<Keyword>(&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<Token::Identifier>(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<IdentifierType> 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<Interval> ranges;
std::set<TokenInterval> 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<Interval> ranges;
std::set<TokenInterval> 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<Interval &>(*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<TokenInterval>::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);

View File

@@ -19,9 +19,11 @@
#include <pl/core/ast/ast_node_variable_decl.hpp>
#include <pl/core/ast/ast_node_builtin_type.hpp>
#include <hex/helpers/fs.hpp>
#include <hex/helpers/utils.hpp>
#include <hex/helpers/magic.hpp>
#include <hex/helpers/binary_pattern.hpp>
#include <hex/helpers/default_paths.hpp>
#include <banners/banner_button.hpp>
@@ -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;

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,919 @@
#include <ui/text_editor.hpp>
#include <pl/core/tokens.hpp>
#include <pl/core/token.hpp>
#include <wolv/utils/string.hpp>
#include <hex/helpers/logger.hpp>
#include <pl/api.hpp>
namespace hex::ui {
using namespace pl::core;
using Interval = TextEditor::Interval;
using Token = pl::core::Token;
using Coordinates = TextEditor::Coordinates;
/*
std::pair<i32, i32> 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<Interval> TextEditor::Lines::searchRangeForBlocks(Interval interval) {
m_curr = m_startToken + interval.m_start;
std::vector<Interval> result;
u32 nestedLevel = 0;
std::vector<i32> 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<Token::DocComment *>(getValue<Token::DocComment>(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<Token::Comment *>(getValue<Token::Comment>(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<Token::Keyword *>(getValue<Token::Keyword>(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<Token::Directive *>(getValue<Token::Directive>(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<Token::Comment>(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<Token::DocComment>(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<Interval> TextEditor::Lines::blocksFromGlobal() {
std::set<Interval> 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<i32>(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<Coordinates, Coordinates> TextEditor::Lines::getDelimiterLineNumbers(i32 start, i32 end, const std::string &delimiters) {
std::pair<Coordinates, Coordinates> 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<Token::Separator *>(getValue<Token::Separator>(0)); separator == nullptr || *separator != openSeparator) {
if (const auto *opener = const_cast<Token::Operator *>(getValue<Token::Operator>(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<Token::Separator *>(getValue<Token::Separator>(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<Token::Separator *>(getValue<Token::Separator>(0)); separator == nullptr || *separator != closeSeparator) {
if (const auto *closer = const_cast<Token::Operator *>(getValue<Token::Operator>(0)); closer == nullptr || *closer != closeOperator) {
if (const auto *separator2 = const_cast<Token::Separator *>(getValue<Token::Separator>(1)); separator2 != nullptr && *separator2 == closeSeparator) {
next();
} else if (const auto *closer2 = const_cast<Token::Operator *>(getValue<Token::Operator>(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 &currentTokenId, 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 &currentTokenId, Location &location) {
currentTokenId++;
m_curr = m_startToken + currentTokenId;
location = m_curr->location;
lineIndex = location.line - 1;
}
void TextEditor::Lines::moveToLocationColumn(i32 locationColumn, i32 &currentTokenId, 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 &currentTokenId, 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<Token::Identifier *>(getValue<Token::Identifier>(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<i32, char> TextEditor::Lines::findMatchingDelimiter(i32 from) {
std::string blockDelimiters = "{}[]()<>";
std::pair<i32, char> 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<Token::Identifier *>(getValue<Token::Identifier>(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<i32> 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<i32> 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<i32, FoldedLine> 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<i32, FoldedLine> 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<typename T>
T *TextEditor::Lines::getValue(const i32 index) {
return const_cast<T*>(std::get_if<T>(&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<i32>(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<auto S>
bool TextEditor::Lines::sequenceImpl() {
if constexpr (S == Normal)
return true;
else if constexpr (S == Not)
return false;
else
std::unreachable();
}
template<auto S>
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<auto S>
bool TextEditor::Lines::sequenceImpl(const auto &... args) {
return (matchOne<S>(args) && ...);
}
template<auto S>
bool TextEditor::Lines::sequence(const Token &token, const auto &... args) {
partBegin();
return sequenceImpl<S>(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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@
namespace hex::ui {
extern TextEditor::Palette s_paletteBase;
template<class InputIt1, class InputIt2, class BinaryPredicate>
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::Range> TextEditor::Lines::getDeactivatedBlocks() {
colorizeInternal();
std::vector<Range> 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<char>(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();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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<Coordinates> 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<Coordinates> 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<Coordinates> 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<Coordinates> 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<Coordinates> 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);
}
}