Compare commits

..

16 Commits

Author SHA1 Message Date
paxcut
3b2e95b9d0 fixed UB created by erasing map elements in range for loop. 2026-03-24 06:43:41 -07:00
paxcut
c677aae7b1 fixed choppy text rendering when dragging the scrollbar with mouse (credit goes to WerWolv) and top margin adjustment when find/replace popup window first appears if it is blocking the entry under the cursor. 2026-03-20 10:46:34 -07:00
paxcut
f70268ea40 fixed crash when the marched delimiter near cursor becomes rii large if lines are removed. 2026-03-20 10:46:34 -07:00
paxcut
d454714428 fix for langs unit tests. I noticed a while back that the string Constants was defined twice in the english language file, so I removed the first one to what ended up in this pr. Apparently some other commit removed the second one so when i rebased the code this ended up deleting both instances thus producing the unit test failure that I assumed was not something I needed to worry about. 2026-03-20 10:46:34 -07:00
paxcut
8b938faf01 fixes for compilation errors 2026-03-20 10:46:34 -07:00
paxcut
324170e0d8 fixes for compilation errors 2026-03-20 10:46:34 -07:00
paxcut
e463df9fc4 fixes for compilation errors 2026-03-20 10:46:34 -07:00
paxcut
fe348d33e7 fixes for compilation errors 2026-03-20 10:46:34 -07:00
paxcut
f9343c8e94 reverse_view is not widely available yet. 2026-03-20 10:46:34 -07:00
paxcut
1670607e38 Final touches to code folding for the pattern editor. Also added some recent fixes to make rebasing easier. 2026-03-20 10:46:34 -07:00
paxcut
c92b55e1b6 merge some of master's changes 2026-03-20 10:46:34 -07:00
paxcut
77eff651ed fix: code folding was not restored when loading projects or opening/importing patterns 2026-03-20 10:46:34 -07:00
paxcut
219f588cbe Made code faster and cleaned some code. 2026-03-20 10:46:34 -07:00
paxcut
0e2d7ee3bc fix: template argument delimiters failed to create folds.
The problem was using the identifier type to distinguish template delimiters from operators because types are not guaranteed to be set. The fix uses colors of the highlighting instead which should always be set when the folds are detected.
2026-03-20 10:46:34 -07:00
paxcut
b57d9118c1 revert unintended commit 2026-03-20 10:46:34 -07:00
paxcut
d4879572fa 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.
2026-03-20 10:46:34 -07:00
15 changed files with 5522 additions and 1882 deletions

View File

@@ -1,31 +1,63 @@
#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>
#include <utility>
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);
bool operator<(const TokenInterval &other) const;
bool operator>(const TokenInterval &other) const;
bool operator==(const TokenInterval &other) const;
bool operator!=(const TokenInterval &other) const;
bool operator<=(const TokenInterval &other) const;
bool operator>=(const TokenInterval &other) const;
[[nodiscard]] bool contains(const TokenInterval &other) const;
[[nodiscard]] bool contains(i32 value) const;
[[nodiscard]] bool contiguous(const TokenInterval &other) const;
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>;
using StringVector = std::vector<std::string>;
using StringSet = std::set<std::string>;
using SafeTokenIterator = pl::hlp::SafeIterator<std::vector<Token>::const_iterator>;
using VariableScopes = std::map<std::string,Scopes>;
using Inheritances = std::map<std::string,VectorString>;
using Inheritances = std::map<std::string,StringSet>;
using IdentifierTypeColor = std::map<Identifier::IdentifierType,ui::TextEditor::PaletteIndex>;
using TokenTypeColor = std::map<Token::Type,ui::TextEditor::PaletteIndex>;
using TokenColor = std::map<i32, ui::TextEditor::PaletteIndex>;
@@ -35,16 +67,18 @@ namespace hex::plugin::builtin {
using CompileErrors = std::vector<CompileError>;
using TokenSequence = std::vector<Token>;
using TokenIdVector = std::vector<i32>;
using TokenIdSet = std::set<i32>;
using Instances = std::map<std::string,std::vector<i32>>;
using CodeFoldBlocks = ui::TextEditor::CodeFoldBlocks;
struct ParentDefinition;
struct Definition {
Definition()= default;
Definition(IdentifierType identifierType, std::string typeStr,i32 tokenId, Location location) : idType(identifierType), typeStr(typeStr), tokenIndex(tokenId),location(location) {}
IdentifierType idType;
Definition(IdentifierType identifierType, std::string typeStr,i32 tokenId, Location location) : idType(identifierType), typeStr(std::move(typeStr)), tokenIndex(tokenId),location(location) {}
IdentifierType idType{};
std::string typeStr;
i32 tokenIndex;
Location location;
i32 tokenIndex{};
Location location{};
};
struct ParentDefinition {
@@ -60,13 +94,13 @@ namespace hex::plugin::builtin {
private:
TextHighlighter *m_textHighlighter;
Types definedTypes;
VectorString usedNamespaces;
StringVector usedNamespaces;
ParsedImports parsedImports;
Str2StrMap importedHeaders;
TokenSequence fullTokens;
std::string editedText;
CompileErrors compileErrors;
VectorString linesOfColors;
StringVector linesOfColors;
public:
RequiredInputs() : m_textHighlighter(nullptr) {};
explicit RequiredInputs(TextHighlighter *textHighlighter) : m_textHighlighter(textHighlighter) {}
@@ -83,11 +117,12 @@ 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;
StringVector m_lines;
TokenIdVector m_firstTokenIdOfLine;
ViewPatternEditor *m_viewPatternEditor;
ViewPatternEditor *m_viewPatternEditor{};
TokenColor m_tokenColors;
@@ -111,52 +146,25 @@ namespace hex::plugin::builtin {
Str2StrMap m_typeDefMap;
Str2StrMap m_typeDefInvMap;
VectorString m_UDTs;
std::set<i32> m_taggedIdentifiers;
std::set<i32> m_memberChains;
std::set<i32> m_scopeChains;
StringVector m_UDTs;
TokenIdSet m_taggedIdentifiers;
TokenIdSet m_memberChains;
TokenIdSet m_scopeChains;
TokenIdSet m_identifierTokenIds;
RequiredInputs m_requiredInputs;
TokenIter m_curr;
TokenIter m_startToken, m_originalPosition, m_partOriginalPosition;
SafeTokenIterator m_curr;
SafeTokenIterator m_startToken, m_originalPosition, m_partOriginalPosition;
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;
@@ -166,6 +174,7 @@ namespace hex::plugin::builtin {
ViewPatternEditor* getViewPatternEditor();
void setViewPatternEditor(ViewPatternEditor *viewPatternEditor);
void setTokenIds();
TextHighlighter();
~TextHighlighter();
explicit TextHighlighter(ViewPatternEditor *viewPatternEditor) : m_viewPatternEditor(viewPatternEditor) {}
@@ -202,6 +211,7 @@ namespace hex::plugin::builtin {
/// 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 +263,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);
@@ -264,8 +274,8 @@ namespace hex::plugin::builtin {
bool findIdentifierDefinition(Definition &result, const std::string &optionalIdentifierName = "", std::string optionalName = "", bool optional = false);
/// To deal with the Parent keyword
std::optional<Definition> setChildrenTypes();
bool findParentTypes(VectorString &parentTypes, const std::string &optionalName="");
bool findAllParentTypes(VectorString &parentTypes, std::vector<Identifier *> &identifiers, std::string &optionalFullName);
bool findParentTypes(StringVector &parentTypes, const std::string &optionalName="");
bool findAllParentTypes(StringVector &parentTypes, std::vector<Identifier *> &identifiers, std::string &optionalFullName);
bool tryParentType(const std::string &parentType, std::string &variableName, std::optional<Definition> &result, std::vector<Identifier *> &identifiers);
/// Convenience function
bool isTokenIdValid(i32 tokenId);
@@ -278,6 +288,8 @@ namespace hex::plugin::builtin {
pl::core::Location getLocation(i32 tokenId);
/// Calculate the token index of a source code, line and column numbers
i32 getTokenId(pl::core::Location location);
i32 getTokenId(SafeTokenIterator tokenIterator);
i32 getTokenId();
/// Calculate the function or template argument position from token indices
i32 getArgumentNumber(i32 start,i32 arg);
/// Calculate the token index of a function or template argument position
@@ -288,18 +300,18 @@ namespace hex::plugin::builtin {
/// The following functions were copied from the parser and some were modified
template<typename T> T *getValue(const i32 index);
template<typename T> T *getValue(i32 index);
void next(i32 count = 1);
bool begin();
void partBegin();
void reset();
void partReset();
bool resetIfFailed(const bool value) ;
bool resetIfFailed(bool value);
template<auto S = Normal> bool sequenceImpl();
template<auto S = Normal> bool matchOne(const Token &token);
template<auto S = Normal> bool sequenceImpl(const auto &... args);
template<auto S = Normal> bool sequence(const Token &token, const auto &... args);
bool isValid();
bool peek(const Token &token, const i32 index = 0);
bool peek(const Token &token, i32 index = 0);
};
}

View File

@@ -60,7 +60,7 @@ namespace hex::plugin::builtin {
[[nodiscard]] bool isPinned() const { return m_isPinned; }
void setPinned(const bool pinned) { m_isPinned = pinned; }
[[nodiscard]] virtual ImGuiWindowFlags getFlags() const { return ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse; }
[[nodiscard]] virtual ImGuiWindowFlags getFlags() const { return ImGuiWindowFlags_AlwaysAutoResize; }
private:
bool m_isPinned = false;

View File

@@ -23,7 +23,7 @@ namespace hex::plugin::builtin {
std::string& get(prv::Provider *provider);
[[nodiscard]] bool hasProviderSpecificSource(prv::Provider *provider) const;
bool isSynced() const;
[[nodiscard]] bool isSynced() const;
void enableSync(bool enabled);
private:
@@ -41,18 +41,9 @@ namespace hex::plugin::builtin {
void drawAlwaysVisibleContent() override;
std::unique_ptr<pl::PatternLanguage> *getPatternLanguage() { return &m_editorRuntime; }
ui::TextEditor *getTextEditor();
u32 getRunningParsers () const { return m_runningParsers;}
u32 getRunningEvaluators () const { return m_runningEvaluators;}
u32 getRunningHighlighters () const { return m_runningHighlighters;}
void incrementRunningParsers( i32 amount) { m_runningParsers += amount; }
void incrementRunningEvaluators( i32 amount) { m_runningEvaluators += amount; }
void incrementRunningHighlighters( i32 amount) { m_runningHighlighters += amount; }
bool hasUnevaluatedChanges(prv::Provider *provider) const;
void setChangesWereParsed(bool changesWereParsed) { m_changesWereParsed = changesWereParsed;}
void setChangesWereColored(bool changesWereColored) { m_changesWereColored = changesWereColored; }
void drawContent() override;
void setPopupWindowHeight(u32 height) { m_popupWindowHeight = height; }
u32 getPopupWindowHeight() const { return m_popupWindowHeight; }
enum class DangerousFunctionPerms : u8 { Ask, Allow, Deny };
void drawHelpText() override;
@@ -64,14 +55,14 @@ namespace hex::plugin::builtin {
class PopupAcceptPattern;
struct PatternVariable {
bool inVariable;
bool outVariable;
bool inVariable{};
bool outVariable{};
pl::core::Token::ValueType type;
pl::core::Token::ValueType type{};
pl::core::Token::Literal value;
};
enum class EnvVarType
enum class EnvVarType : u8
{
Integer,
Float,
@@ -137,15 +128,8 @@ namespace hex::plugin::builtin {
std::mutex m_logMutex;
PerProvider<ui::TextEditor::Coordinates> m_cursorPosition;
PerProvider<ImVec2> m_scroll;
PerProvider<ImVec2> m_consoleScroll;
PerProvider<ui::TextEditor::Coordinates> m_consoleCursorPosition;
PerProvider<ui::TextEditor::Range> m_selection;
PerProvider<ui::TextEditor::Range> m_consoleSelection;
PerProvider<size_t> m_consoleLongestLineLength;
PerProvider<ui::TextEditor::Breakpoints> m_breakpoints;
PerProvider<std::optional<pl::core::err::PatternLanguageError>> m_lastEvaluationError;
PerProvider<std::vector<pl::core::err::CompileError>> m_lastCompileError;
PerProvider<const std::vector<pl::core::Evaluator::StackTrace>*> m_callStack;
@@ -168,7 +152,6 @@ namespace hex::plugin::builtin {
ContentRegistry::Settings::SettingsVariable<bool, "hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.pattern_parent_highlighting"> m_parentHighlightingEnabled = false; bool m_replaceMode = false;
bool m_openFindReplacePopUp = false;
bool m_openGotoLinePopUp = false;
bool m_patternEvaluating = false;
std::map<std::fs::path, std::string> m_patternNames;
PerProvider<wolv::io::ChangeTracker> m_changeTracker;
PerProvider<bool> m_ignoreNextChangeEvent;

View File

@@ -1125,6 +1125,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

@@ -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>
@@ -1287,11 +1289,11 @@ namespace hex::plugin::builtin {
if (ImGui::BeginChild("##debugger", size, true)) {
auto &evaluator = runtime.getInternals().evaluator;
m_breakpoints = m_textEditor.get(provider).getBreakpoints();
evaluator->setBreakpoints(m_breakpoints);
ui::TextEditor::Breakpoints breakpoints = m_textEditor.get(provider).getBreakpoints();
evaluator->setBreakpoints(breakpoints);
m_breakpoints = evaluator->getBreakpoints();
m_textEditor.get(provider).setBreakpoints(m_breakpoints);
breakpoints = evaluator->getBreakpoints();
m_textEditor.get(provider).setBreakpoints(breakpoints);
if (*m_breakpointHit) {
auto displayValue = [&](const auto &parent, size_t index) {
@@ -1429,12 +1431,12 @@ namespace hex::plugin::builtin {
{
if (m_textEditor.get(provider).isBreakpointsChanged()) {
m_breakpoints = m_textEditor.get(provider).getBreakpoints();
ui::TextEditor::Breakpoints breakpoints = m_textEditor.get(provider).getBreakpoints();
m_textEditor.get(provider).clearBreakpointsChanged();
const auto &runtime = ContentRegistry::PatternLanguage::getRuntime();
auto &evaluator = runtime.getInternals().evaluator;
if (evaluator) {
evaluator->setBreakpoints(m_breakpoints);
evaluator->setBreakpoints(breakpoints);
}
}
@@ -1474,6 +1476,8 @@ 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().setAllCodeFolds();
m_textEditor.get(provider).getLines().applyCodeFoldStates();
m_allStepsCompleted = true;
}
@@ -1639,7 +1643,8 @@ namespace hex::plugin::builtin {
this->evaluatePattern(code, provider);
m_textEditor.get(provider).setText(code, true);
m_sourceCode.get(provider) = code;
m_textEditor.get(provider).removeHiddenLinesFromPattern();
m_sourceCode.get(provider) = m_textEditor.get(provider).getText();
if (trackFile) {
m_changeTracker.get(provider) = wolv::io::ChangeTracker(file);
m_changeTracker.get(provider).startTracking([this, provider]{ this->handleFileChange(provider); });
@@ -1653,7 +1658,7 @@ namespace hex::plugin::builtin {
ContentRegistry::PatternLanguage::configureRuntime(*m_editorRuntime, nullptr);
const auto &ast = m_editorRuntime->parseString(code, pl::api::Source::DefaultSource);
m_textEditor.get(provider).setLongestLineLength(m_editorRuntime->getInternals().preprocessor.get()->getLongestLineLength());
m_textEditor.get(provider).setLongestLineLength(m_editorRuntime->getInternals().preprocessor->getLongestLineLength());
auto &patternVariables = m_patternVariables.get(provider);
auto oldPatternVariables = std::move(patternVariables);
@@ -1704,7 +1709,7 @@ namespace hex::plugin::builtin {
m_consoleEditor.get(provider).clearActionables();
m_console.get(provider).clear();
m_consoleLongestLineLength.get(provider) = 0;
m_consoleEditor.get(provider).setLongestLineLength(0);
m_consoleNeedsUpdate = true;
m_consoleEditor.get(provider).setText("");
@@ -1777,8 +1782,7 @@ namespace hex::plugin::builtin {
default: break;
}
}
if (m_consoleLongestLineLength.get(provider) < line.size()) {
m_consoleLongestLineLength.get(provider) = line.size();
if (m_consoleEditor.get(provider).getLongestLineLength() < line.size()) {
m_consoleEditor.get(provider).setLongestLineLength(line.size());
}
m_console.get(provider).emplace_back(line);
@@ -1836,6 +1840,7 @@ namespace hex::plugin::builtin {
return;
m_textEditor.get(provider).setText(wolv::util::preprocessText(code));
m_textEditor.get(provider).removeHiddenLinesFromPattern();
m_sourceCode.get(provider) = code;
m_hasUnevaluatedChanges.get(provider) = true;
});
@@ -1847,12 +1852,18 @@ namespace hex::plugin::builtin {
EventProviderOpened::subscribe(this, [this](prv::Provider *provider) {
m_textEditor.get(provider).setLanguageDefinition(PatternLanguage());
m_textEditor.get(provider).setShowWhitespaces(false);
m_textEditor.get(provider).setCursorPosition(ui::TextEditor::Coordinates(0, 0),false,false);
//if (getLastFocusedView() == this) {
// m_textEditor.get(provider).setFocus(true);
//}
m_consoleEditor.get(provider).setLanguageDefinition(ConsoleLog());
m_consoleEditor.get(provider).setShowWhitespaces(false);
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: ";
@@ -1864,37 +1875,26 @@ namespace hex::plugin::builtin {
m_envVarEntries.get(provider).emplace_back(0, "", i128(0), EnvVarType::Integer);
m_debuggerDrawer.get(provider) = std::make_unique<ui::PatternDrawer>();
m_cursorPosition.get(provider) = ui::TextEditor::Coordinates(0, 0);
});
EventProviderChanged::subscribe(this, [this](prv::Provider *oldProvider, prv::Provider *newProvider) {
if (oldProvider != nullptr) {
m_sourceCode.get(oldProvider) = m_textEditor.get(oldProvider).getText();
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();
m_breakpoints.get(oldProvider) = m_textEditor.get(oldProvider).getBreakpoints();
m_consoleCursorPosition.get(oldProvider) = m_consoleEditor.get(oldProvider).getCursorPosition();
m_consoleSelection.get(oldProvider) = m_consoleEditor.get(oldProvider).getSelection();
m_consoleLongestLineLength.get(oldProvider) = m_consoleEditor.get(oldProvider).getLongestLineLength();
m_consoleScroll.get(oldProvider) = m_consoleEditor.get(oldProvider).getScroll();
}
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).setScroll(m_scroll.get(newProvider));
m_textEditor.get(newProvider).setSelection(m_selection.get(newProvider));
m_textEditor.get(newProvider).setBreakpoints(m_breakpoints.get(newProvider));
m_textEditor.get(newProvider).setTextChanged(false);
m_hasUnevaluatedChanges.get(newProvider) = true;
m_consoleEditor.get(newProvider).setText(wolv::util::combineStrings(m_console.get(newProvider), "\n"));
m_consoleEditor.get(newProvider).setCursorPosition(m_consoleCursorPosition.get(newProvider));
m_consoleEditor.get(newProvider).setLongestLineLength(m_consoleLongestLineLength.get(newProvider));
m_consoleEditor.get(newProvider).setSelection(m_consoleSelection.get(newProvider));
m_consoleEditor.get(newProvider).setScroll(m_consoleScroll.get(newProvider));
}
//if (getLastFocusedView() == this) {
// m_textEditor.get(newProvider).setFocus(false);
//}
});
@@ -2126,16 +2126,16 @@ namespace hex::plugin::builtin {
const auto &runtime = ContentRegistry::PatternLanguage::getRuntime();
auto &evaluator = runtime.getInternals().evaluator;
m_breakpoints = m_textEditor.get(ImHexApi::Provider::get()).getBreakpoints();
evaluator->setBreakpoints(m_breakpoints);
ui::TextEditor::Breakpoints breakpoints = m_textEditor.get(ImHexApi::Provider::get()).getBreakpoints();
evaluator->setBreakpoints(breakpoints);
if (m_breakpoints->contains(line))
if (breakpoints.contains(line))
evaluator->removeBreakpoint(line);
else
evaluator->addBreakpoint(line);
m_breakpoints = evaluator->getBreakpoints();
m_textEditor.get(ImHexApi::Provider::get()).setBreakpoints(m_breakpoints);
breakpoints = evaluator->getBreakpoints();
m_textEditor.get(ImHexApi::Provider::get()).setBreakpoints(breakpoints);
}, [] { return ImHexApi::Provider::isValid(); },
this);
@@ -2332,20 +2332,17 @@ namespace hex::plugin::builtin {
.required = false,
.load = [this](prv::Provider *provider, const std::fs::path &basePath, const Tar &tar) {
const auto sourceCode = wolv::util::preprocessText(tar.readString(basePath));
m_sourceCode.get(provider) = sourceCode;
if (provider == ImHexApi::Provider::get())
m_textEditor.get(provider).setText(sourceCode);
m_textEditor.get(provider).setText(sourceCode);
m_textEditor.get(provider).removeHiddenLinesFromPattern();
m_sourceCode.get(provider) = m_textEditor.get(provider).getText();
m_hasUnevaluatedChanges.get(provider) = true;
return true;
},
.store = [this](prv::Provider *provider, const std::fs::path &basePath, const Tar &tar) {
if (provider == ImHexApi::Provider::get())
m_sourceCode.get(provider) = m_textEditor.get(provider).getText();
m_sourceCode.get(provider) = m_textEditor.get(provider).getText();
const auto &sourceCode = m_sourceCode.get(provider);
auto sourceCode = m_textEditor.get(provider).getText(true);
tar.writeString(basePath, wolv::util::trim(sourceCode));
return true;
@@ -2535,7 +2532,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
@@ -2564,12 +2591,12 @@ namespace hex::plugin::builtin {
// Wait until evaluation has finished
while (m_runningEvaluators > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::this_thread::sleep_for(std::chrono::milliseconds(100ll));
}
auto lock = std::scoped_lock(ContentRegistry::PatternLanguage::getRuntimeLock());
auto evaluationResult = m_lastEvaluationResult.load();
int evaluationResult = m_lastEvaluationResult.load();
nlohmann::json result = {
{ "handle", provider->getID() },
@@ -2588,7 +2615,7 @@ namespace hex::plugin::builtin {
// Wait until evaluation has finished
while (m_runningEvaluators > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::this_thread::sleep_for(std::chrono::milliseconds(100ll));
}
auto lock = std::scoped_lock(ContentRegistry::PatternLanguage::getRuntimeLock());
@@ -2612,7 +2639,7 @@ namespace hex::plugin::builtin {
// Wait until evaluation has finished
while (m_runningEvaluators > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::this_thread::sleep_for(std::chrono::milliseconds(100ll));
}
auto lock = std::scoped_lock(ContentRegistry::PatternLanguage::getRuntimeLock());
@@ -2644,6 +2671,7 @@ namespace hex::plugin::builtin {
m_changeEventAcknowledgementPending.get(provider) = true;
hex::ui::BannerButton::open(ICON_VS_INFO, "hex.builtin.provider.file.reload_changes", ImColor(66, 104, 135), "hex.builtin.provider.file.reload_changes.reload", [this, provider] {
m_changeEventAcknowledgementPending.get(provider) = false;
loadPatternFile(m_changeTracker.get(provider).getPath(), provider, true);
});
}
@@ -2706,7 +2734,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 +2757,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,864 @@
#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 <hex/api/content_registry/pattern_language.hpp>
#include <pl/core/preprocessor.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;
using RangeFromCoordinates = TextEditor::RangeFromCoordinates;
using CodeFoldState = TextEditor::CodeFoldState;
void TextEditor::Lines::skipAttribute() {
if (sequence(tkn::Separator::LeftBracket, tkn::Separator::LeftBracket)) {
while (!sequence(tkn::Separator::RightBracket, tkn::Separator::RightBracket))
next();
}
}
Interval TextEditor::Lines::findBlockInRange(Interval interval) {
Interval result = NotValid;
auto tokenStart = SafeTokenIterator(m_tokens.begin(), m_tokens.end());
bool foundKeyword = false;
bool foundComment = false;
m_curr = tokenStart + interval.m_start;
while (interval.m_end >= getTokenId()) {
if (peek(tkn::Separator::EndOfProgram))
return NotValid;
if (result.m_start = getTokenId(); result.m_start < 0)
return NotValid;
while (true) {
if (const auto *docComment = const_cast<Token::DocComment *>(getValue<Token::DocComment>(0)); docComment != nullptr && getTokenId() == m_firstTokenIdOfLine.at(m_curr->location.line - 1)) {
if (foundKeyword)
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 && getTokenId() == m_firstTokenIdOfLine.at(m_curr->location.line - 1)) {
if (foundKeyword)
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 && getTokenId() == m_firstTokenIdOfLine.at(m_curr->location.line - 1)) {
if (foundComment)
break;
foundKeyword = true;
i32 lineIndex = m_curr->location.line - 1;
auto nextIndex = nextLineIndex(lineIndex);
if (nextIndex == lineIndex || nextIndex == -1)
break;
auto tokenId = m_firstTokenIdOfLine.at(nextIndex);
next(tokenId - getTokenId());
} else if (const auto *directive = const_cast<Token::Directive *>(getValue<Token::Directive>(0));directive != nullptr && *directive == Token::Directive::Include && getTokenId() == m_firstTokenIdOfLine.at(m_curr->location.line - 1)) {
if (foundComment)
break;
foundKeyword = true;
i32 lineIndex = m_curr->location.line - 1;
auto nextIndex = nextLineIndex(lineIndex);
if (nextIndex == lineIndex || nextIndex == -1)
break;
auto tokenId = m_firstTokenIdOfLine.at(nextIndex);
next(tokenId - getTokenId());
} else
break;
}
if (foundKeyword || foundComment) {
auto currentId = getTokenId();
if (peek(tkn::Separator::EndOfProgram) || (currentId > 0 && currentId < (i32) m_tokens.size())) {
next(-1);
if (result.m_end = getTokenId(); 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 = SafeTokenIterator(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 {(i32) (location.line - 1), (i32) (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 {(i32)(location.line + lineCount - 2), (i32)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 {(i32)(location.line - 1), (i32)(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 {(i32)(location.line + lineCount - 2), (i32)endColumn};
}
}
m_curr = save;
return result;
}
//comments imports and includes
void TextEditor::Lines::nonDelimitedFolds() {
auto size = m_tokens.size();
if (size > 0) {
Interval block = {0,static_cast<i32>(size-1)};
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;
}
}
}
RangeFromCoordinates TextEditor::Lines::getDelimiterLineNumbers(i32 start, i32 end, const std::string &delimiters) {
RangeFromCoordinates result = {Invalid, Invalid};
Coordinates first = Invalid;
auto tokenStart = SafeTokenIterator(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 = Token::Operator{};
Token::Operator closeOperator = Token::Operator{};
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() == 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) {
i32 tempLineIndex;
if (tempLineIndex = nextLineIndex(lineIndex); lineIndex == tempLineIndex) {
lineIndex++;
currentTokenId = -1;
location = Location::Empty();
return;
}
lineIndex = tempLineIndex;
currentTokenId = m_firstTokenIdOfLine[lineIndex];
m_curr = m_startToken + currentTokenId;
location = m_curr->location;
}
void TextEditor::Lines::incrementTokenId(i32 &lineIndex, i32 &currentTokenId, Location &location) {
currentTokenId++;
m_curr = m_startToken + currentTokenId;
location = m_curr->location;
lineIndex = location.line - 1;
}
void TextEditor::Lines::moveToStringIndex(i32 stringIndex, i32 &currentTokenId, Location &location) {
auto curr = m_curr;
i32 tempTokenId;
auto &line = operator[](location.line - 1);
while (stringIndex > line.columnIndex(m_curr->location.column) + (i32) m_curr->location.length && m_curr->location.line == location.line)
next();
if (peek(tkn::Separator::EndOfProgram))
return;
if (tempTokenId = getTokenId(); tempTokenId < 0) {
m_curr = curr;
location = curr->location;
} else {
location = m_curr->location;
currentTokenId = tempTokenId;
}
}
void TextEditor::Lines::resetToTokenId(i32 &lineIndex, i32 &currentTokenId, Location &location) {
m_curr = m_startToken + currentTokenId;
location = m_curr->location;
lineIndex = location.line - 1;
}
i32 TextEditor::Lines::findNextDelimiter(bool openOnly) {
while (!peek(tkn::Separator::EndOfProgram)) {
if (peek(tkn::Separator::LeftBrace) || peek(tkn::Separator::LeftBracket) || peek(tkn::Separator::LeftParenthesis) || peek(tkn::Operator::BoolLessThan))
return getTokenId();
if (!openOnly) {
if (peek(tkn::Separator::RightBrace) || peek(tkn::Separator::RightBracket) || peek(tkn::Separator::RightParenthesis) || peek(tkn::Operator::BoolGreaterThan))
return getTokenId();
}
next();
}
return getTokenId();
}
TextEditor::CodeFoldBlocks TextEditor::Lines::foldPointsFromSource() {
auto code = getText();
if (code.empty())
return m_foldPoints;
std::unique_ptr<pl::PatternLanguage> runtime = std::make_unique<pl::PatternLanguage>();
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_foldPoints;
loadFirstTokenIdOfLine();
if (m_firstTokenIdOfLine.empty())
return m_foldPoints;
m_foldPoints.clear();
nonDelimitedFolds();
std::string openDelimiters = "{[(<";
size_t topLine = 0;
m_startToken = SafeTokenIterator(m_tokens.begin(), m_tokens.end());
m_curr = m_startToken;
auto location = m_curr->location;
i32 lineIndex = topLine;
i32 currentTokenId = 0;
while (currentTokenId < (i32) tokenCount) {
if (currentTokenId = findNextDelimiter(true); !peek(tkn::Separator::EndOfProgram)) {
if (currentTokenId < 0) {
return m_foldPoints;
}
auto line = operator[](m_curr->location.line - 1);
size_t stringIndex = line.columnIndex(m_curr->location.column);
std::string openDelimiter = std::string(1, line[static_cast<u64>(stringIndex - 1)]);
location = m_curr->location;
if (auto idx = openDelimiters.find(openDelimiter); idx != std::string::npos) {
if (idx == 3) {
if (currentTokenId == 0) {
return m_foldPoints;
}
next(-1);
auto column = m_curr[0].location.column - 1;
if (line.m_colors[column] != (char) PaletteIndex::UserDefinedType) {
next(2);
if (peek(tkn::Separator::EndOfProgram, -1)) {
return m_foldPoints;
}
if (currentTokenId = getTokenId(); 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_startToken + end.first); currentTokenId < 0 || currentTokenId >= (i32) m_tokens.size()) {
return m_foldPoints;
}
incrementTokenId(lineIndex, currentTokenId, location);
} else {
return m_foldPoints;
}
} else {
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 = SafeTokenIterator(m_tokens.begin(), m_tokens.end());
const i32 tokenCount = m_tokens.size();
if (from >= tokenCount)
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[static_cast<u64>(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;
i32 lineIndex = location.line - 1;
while (currentTokenId < tokenCount) {
if (currentTokenId = findNextDelimiter(false); !peek(tkn::Separator::EndOfProgram)) {
if (currentTokenId < 0) {
return result;
}
line = operator[](m_curr->location.line - 1);
std::string currentChar = std::string(1, line[(u64)(m_curr->location.column - 1)]);
location = m_curr->location;
if (auto idx = blockDelimiters.find(currentChar); idx != std::string::npos) {
if (currentChar == closeDelimiter) {
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(); currentTokenId < 0)
return result;
resetToTokenId(lineIndex, currentTokenId, location);
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(tokenStart + end.first); currentTokenId < 0 || currentTokenId >= (i32) m_tokens.size())
return result;
incrementTokenId(lineIndex, currentTokenId, location);
} else
return result;
}
} else {
return result;
}
} else {
return result;
}
}
return result;
}
void TextEditor::saveCodeFoldStates() {
m_lines.saveCodeFoldStates();
}
void TextEditor::Lines::saveCodeFoldStates() {
i32 codeFoldIndex = 0;
Indices closedFoldIncrements;
for (auto key: m_codeFoldKeys) {
if (m_codeFoldState.contains(key) && !m_codeFoldState[key]) {
closedFoldIncrements.push_back(codeFoldIndex);
codeFoldIndex = 1;
} else
codeFoldIndex++;
}
if (!m_hiddenLines.empty())
m_hiddenLines.clear();
if (!closedFoldIncrements.empty()) {
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;
m_hiddenLines.emplace_back(lineIndex, result);
}
}
void TextEditor::applyCodeFoldStates() {
m_lines.applyCodeFoldStates();
}
void TextEditor::Lines::setCodeFoldState(CodeFoldState state) {
m_codeFoldState = state;
saveCodeFoldStates();
}
CodeFoldState TextEditor::Lines::getCodeFoldState() const {
return m_codeFoldState;
}
void TextEditor::Lines::resetCodeFoldStates() {
m_codeFoldState.clear();
for (auto key: m_codeFoldKeys)
m_codeFoldState[key] = true;
}
void TextEditor::Lines::applyCodeFoldStates() {
std::string commentLine;
for (const auto& line: m_hiddenLines) {
if (line.m_line.starts_with("//+-#:")) {
commentLine = line.m_line;
break;
}
}
if (commentLine.size() < 6 || !commentLine.starts_with("//+-#:")) {
resetCodeFoldStates();
return;
}
auto states = commentLine.substr(6);
i32 count;
StringVector stringVector;
if (states.empty())
count = 0;
else {
stringVector = wolv::util::splitString(states, ",", true);
count = stringVector.size();
}
if (count == 1 && stringVector[0].empty())
return;
Indices closedFoldIncrements(count);
i32 value = 0;
for (i32 i = 0; i < count; ++i) {
auto stateStr = stringVector[i];
std::from_chars(stateStr.data(), stateStr.data() + stateStr.size(), value);
closedFoldIncrements[i] = value;
}
resetCodeFoldStates();
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 = 0.0f;
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];
}
FoldedLines 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_globalRowMaxChanged = true;
}
m_foldedLines[currentFoldedLine.m_row].loadSegments();
}
void TextEditor::Lines::openCodeFold(const Range &key) {
for (const 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) {
FoldedLines 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;
m_globalRowMaxChanged = 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 (count == 0)
return;
i32 id = getTokenId();
if (count > 0)
m_curr += std::min(count,static_cast<i32>(m_tokens.size() - id));
else
m_curr += -std::min(-count,id);
}
enum : u32 { Normal = 0, 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;
}
return isLocationValid(token.location);
}
bool TextEditor::Lines::peek(const Token &token, const i32 index) {
if (!isValid())
return false;
i32 id = getTokenId();
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

@@ -5,6 +5,7 @@
namespace hex::ui {
extern TextEditor::Palette s_paletteBase;
using Keys = TextEditor::Keys;
template<class InputIt1, class InputIt2, class BinaryPredicate>
bool equals(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, BinaryPredicate p) {
@@ -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,25 +41,59 @@ namespace hex::ui {
}
}
void TextEditor::colorize() {
Keys TextEditor::Lines::getDeactivatedBlocks() {
colorizeInternal();
Keys 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;
std::smatch results;
std::string id;
if (m_languageDefinition.m_tokenize == nullptr) {
m_languageDefinition.m_tokenize = [](strConstIter, strConstIter, strConstIter &, strConstIter &, PaletteIndex &) { return false; };
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 +107,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 +200,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;
@@ -177,7 +213,7 @@ namespace hex::ui {
auto withinString = false;
auto withinBlockComment = false;
auto withinNotDef = false;
auto currentLine = endLine;
i32 currentLine;
auto commentLength = 0;
auto matchedBracket = false;
@@ -185,7 +221,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 +249,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 +263,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 +415,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 +437,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,10 @@
namespace hex::ui {
using Coordinates = TextEditor::Coordinates;
using Segments = TextEditor::Segments;
TextEditor::Line TextEditor::Line::trim(TrimMode trimMode) {
if (m_chars.empty())
return m_emptyLine;
@@ -56,12 +60,11 @@ namespace hex::ui {
}
i32 TextEditor::Line::stringTextSize(const std::string &str) const {
i32 result = 0;
if (str.empty())
return 0;
if (ImGui::GetFont() == nullptr) {
fonts::CodeEditor().push();
result = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, str.c_str(), nullptr, nullptr).x;
i32 result = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, str.c_str(), nullptr, nullptr).x;
fonts::CodeEditor().pop();
return result;
}
@@ -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,31 @@ 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);
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 >= 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;
i32 count = 0;
u64 length;
i32 increase;
@@ -190,17 +209,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 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 +227,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 > 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;
Segments 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) {
Segments 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;
Segments 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);
Segments 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 {coordinate.m_line + 1, 0};
else
return {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);
Segments 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);
}
}