mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-04-02 21:47:40 -05:00
improv: moved text editor to the ui plugin. (#2397)
Reorganized source code into files named in the fashion of imhex and split large functions into smaller ones. Moved all function definitions out of the header except for one-liners. All variable types were switched to use imHex standard (u8,...) and removed duplicated functions that were needed when the text editor was isolated. Minor improvements to find/replace while making sure they still worked with utf-8 chars.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
#include <pl/core/token.hpp>
|
||||
#include <pl/core/preprocessor.hpp>
|
||||
#include <pl/helpers/safe_iterator.hpp>
|
||||
#include <TextEditor.h>
|
||||
#include <ui/text_editor.hpp>
|
||||
#include <hex/helpers/types.hpp>
|
||||
|
||||
namespace pl {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <hex/ui/view.hpp>
|
||||
|
||||
#include <TextEditor.h>
|
||||
#include <ui/text_editor.hpp>
|
||||
|
||||
#include <list>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
|
||||
#include <TextEditor.h>
|
||||
#include <ui/text_editor.hpp>
|
||||
#include <popups/popup_file_chooser.hpp>
|
||||
#include <content/text_highlighting/pattern_language.hpp>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <imgui_internal.h>
|
||||
#include <implot.h>
|
||||
#include <imnodes.h>
|
||||
#include <TextEditor.h>
|
||||
#include <ui/text_editor.hpp>
|
||||
#include <romfs/romfs.hpp>
|
||||
|
||||
#include <wolv/io/file.hpp>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <TextEditor.h>
|
||||
#include <ui/text_editor.hpp>
|
||||
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
@@ -13,6 +13,12 @@ add_imhex_plugin(
|
||||
source/ui/visualizer_drawer.cpp
|
||||
source/ui/menu_items.cpp
|
||||
source/ui/pattern_value_editor.cpp
|
||||
source/ui/text_editor/editor.cpp
|
||||
source/ui/text_editor/highlighter.cpp
|
||||
source/ui/text_editor/navigate.cpp
|
||||
source/ui/text_editor/render.cpp
|
||||
source/ui/text_editor/support.cpp
|
||||
source/ui/text_editor/utf8.cpp
|
||||
INCLUDES
|
||||
include
|
||||
LIBRARIES
|
||||
|
||||
698
plugins/ui/include/ui/text_editor.hpp
Normal file
698
plugins/ui/include/ui/text_editor.hpp
Normal file
@@ -0,0 +1,698 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
namespace hex::ui {
|
||||
using strConstIter = std::string::const_iterator;
|
||||
|
||||
class TextEditor {
|
||||
|
||||
public:
|
||||
// indices of the arrays that contain the lines (vector) and the columns (a string) of the
|
||||
// text editor. Negative values indicate the distance to the last element of the array.
|
||||
// When comparing coordinates ensure they have the same sign because coordinates don't have
|
||||
// information about the size of the array. Currently positive coordinates are always bigger
|
||||
// than negatives even if that gives a wrong result.
|
||||
struct Coordinates {
|
||||
i32 m_line, m_column;
|
||||
|
||||
Coordinates() : m_line(0), m_column(0) {}
|
||||
Coordinates(i32 line, i32 column) : m_line(line), m_column(column) {}
|
||||
bool operator==(const Coordinates &o) const;
|
||||
bool operator!=(const Coordinates &o) const;
|
||||
bool operator<(const Coordinates &o) const;
|
||||
bool operator>(const Coordinates &o) const;
|
||||
bool operator<=(const Coordinates &o) const;
|
||||
bool operator>=(const Coordinates &o) const;
|
||||
Coordinates operator+(const Coordinates &o) const;
|
||||
Coordinates operator-(const Coordinates &o) const;
|
||||
};
|
||||
|
||||
inline static const Coordinates Invalid = Coordinates(0x80000000, 0x80000000);
|
||||
|
||||
struct Selection {
|
||||
Coordinates m_start;
|
||||
Coordinates m_end;
|
||||
|
||||
Selection() = default;
|
||||
|
||||
Selection(Coordinates start, Coordinates end) : m_start(start), m_end(end) {
|
||||
if (m_start > m_end) {
|
||||
std::swap(m_start, m_end);
|
||||
}
|
||||
}
|
||||
|
||||
Coordinates getSelectedLines();
|
||||
Coordinates getSelectedColumns();
|
||||
bool isSingleLine();
|
||||
bool contains(Coordinates coordinates, int8_t endsInclusive=1);
|
||||
};
|
||||
|
||||
struct EditorState {
|
||||
Selection m_selection;
|
||||
Coordinates m_cursorPosition;
|
||||
};
|
||||
|
||||
class FindReplaceHandler {
|
||||
public:
|
||||
FindReplaceHandler();
|
||||
typedef std::vector<EditorState> Matches;
|
||||
Matches &getMatches() { return m_matches; }
|
||||
bool findNext(TextEditor *editor);
|
||||
u32 findMatch(TextEditor *editor, bool isNex);
|
||||
bool replace(TextEditor *editor, bool right);
|
||||
bool replaceAll(TextEditor *editor);
|
||||
std::string &getFindWord() { return m_findWord; }
|
||||
|
||||
void setFindWord(TextEditor *editor, const std::string &findWord) {
|
||||
if (findWord != m_findWord) {
|
||||
findAllMatches(editor, findWord);
|
||||
m_findWord = findWord;
|
||||
}
|
||||
}
|
||||
|
||||
std::string &getReplaceWord() { return m_replaceWord; }
|
||||
void setReplaceWord(const std::string &replaceWord) { m_replaceWord = replaceWord; }
|
||||
void selectFound(TextEditor *editor, i32 found);
|
||||
void findAllMatches(TextEditor *editor, std::string findWord);
|
||||
u32 findPosition(TextEditor *editor, Coordinates pos, bool isNext);
|
||||
bool getMatchCase() const { return m_matchCase; }
|
||||
|
||||
void setMatchCase(TextEditor *editor, bool matchCase) {
|
||||
if (matchCase != m_matchCase) {
|
||||
m_matchCase = matchCase;
|
||||
m_optionsChanged = true;
|
||||
findAllMatches(editor, m_findWord);
|
||||
}
|
||||
}
|
||||
|
||||
bool getWholeWord() const { return m_wholeWord; }
|
||||
void setWholeWord(TextEditor *editor, bool wholeWord) {
|
||||
if (wholeWord != m_wholeWord) {
|
||||
m_wholeWord = wholeWord;
|
||||
m_optionsChanged = true;
|
||||
findAllMatches(editor, m_findWord);
|
||||
}
|
||||
}
|
||||
|
||||
bool getFindRegEx() const { return m_findRegEx; }
|
||||
void setFindRegEx(TextEditor *editor, bool findRegEx) {
|
||||
if (findRegEx != m_findRegEx) {
|
||||
m_findRegEx = findRegEx;
|
||||
m_optionsChanged = true;
|
||||
findAllMatches(editor, m_findWord);
|
||||
}
|
||||
}
|
||||
|
||||
void resetMatches() {
|
||||
m_matches.clear();
|
||||
m_findWord = "";
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_findWord;
|
||||
std::string m_replaceWord;
|
||||
bool m_matchCase;
|
||||
bool m_wholeWord;
|
||||
bool m_findRegEx;
|
||||
bool m_optionsChanged;
|
||||
Matches m_matches;
|
||||
};
|
||||
|
||||
enum class PaletteIndex {
|
||||
Default,
|
||||
Identifier,
|
||||
Directive,
|
||||
Operator,
|
||||
Separator,
|
||||
BuiltInType,
|
||||
Keyword,
|
||||
NumericLiteral,
|
||||
StringLiteral,
|
||||
CharLiteral,
|
||||
Cursor,
|
||||
Background,
|
||||
LineNumber,
|
||||
Selection,
|
||||
Breakpoint,
|
||||
ErrorMarker,
|
||||
PreprocessorDeactivated,
|
||||
CurrentLineFill,
|
||||
CurrentLineFillInactive,
|
||||
CurrentLineEdge,
|
||||
ErrorText,
|
||||
WarningText,
|
||||
DebugText,
|
||||
DefaultText,
|
||||
Attribute,
|
||||
PatternVariable,
|
||||
LocalVariable,
|
||||
CalculatedPointer,
|
||||
TemplateArgument,
|
||||
Function,
|
||||
View,
|
||||
FunctionVariable,
|
||||
FunctionParameter,
|
||||
UserDefinedType,
|
||||
PlacedVariable,
|
||||
GlobalVariable,
|
||||
NameSpace,
|
||||
TypeDef,
|
||||
UnkIdentifier,
|
||||
DocComment,
|
||||
DocBlockComment,
|
||||
BlockComment,
|
||||
GlobalDocComment,
|
||||
Comment,
|
||||
PreprocIdentifier,
|
||||
Max
|
||||
};
|
||||
|
||||
|
||||
typedef std::vector<std::pair<std::regex, PaletteIndex>> RegexList;
|
||||
|
||||
struct Identifier {
|
||||
Coordinates m_location;
|
||||
std::string m_declaration;
|
||||
};
|
||||
|
||||
using String = std::string;
|
||||
using Identifiers = std::unordered_map<std::string, Identifier>;
|
||||
using Keywords = std::unordered_set<std::string>;
|
||||
using ErrorMarkers = std::map<Coordinates, std::pair<u32, std::string>>;
|
||||
using Breakpoints = std::unordered_set<u32>;
|
||||
using Palette = std::array<ImU32, (u64) PaletteIndex::Max>;
|
||||
using Glyph = uint8_t;
|
||||
|
||||
class ActionableBox {
|
||||
|
||||
ImRect m_box;
|
||||
public:
|
||||
ActionableBox() = default;
|
||||
explicit ActionableBox(const ImRect &box) : m_box(box) {}
|
||||
|
||||
virtual bool trigger() {
|
||||
return ImGui::IsMouseHoveringRect(m_box.Min, m_box.Max);
|
||||
}
|
||||
|
||||
virtual void callback() {}
|
||||
};
|
||||
|
||||
class CursorChangeBox : public ActionableBox {
|
||||
public:
|
||||
CursorChangeBox() = default;
|
||||
explicit CursorChangeBox(const ImRect &box) : ActionableBox(box) {}
|
||||
|
||||
void callback() override {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
}
|
||||
};
|
||||
|
||||
class ErrorGotoBox : public ActionableBox {
|
||||
Coordinates m_pos;
|
||||
public:
|
||||
ErrorGotoBox() = default;
|
||||
|
||||
ErrorGotoBox(const ImRect &box, const Coordinates &pos, TextEditor *editor) : ActionableBox(box), m_pos(pos), m_editor(editor) {}
|
||||
|
||||
bool trigger() override {
|
||||
return ActionableBox::trigger() && ImGui::IsMouseClicked(0);
|
||||
}
|
||||
|
||||
void callback() override {
|
||||
m_editor->jumpToCoords(m_pos);
|
||||
}
|
||||
|
||||
private:
|
||||
TextEditor *m_editor;
|
||||
};
|
||||
|
||||
using ErrorGotoBoxes = std::map<Coordinates, ErrorGotoBox>;
|
||||
using CursorBoxes = std::map<Coordinates, CursorChangeBox>;
|
||||
|
||||
class ErrorHoverBox : public ActionableBox {
|
||||
Coordinates m_pos;
|
||||
std::string m_errorText;
|
||||
public:
|
||||
ErrorHoverBox() = default;
|
||||
ErrorHoverBox(const ImRect &box, const Coordinates &pos, const char *errorText) : ActionableBox(box), m_pos(pos), m_errorText(errorText) {}
|
||||
|
||||
void callback() override {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::Text("Error at line %d:", m_pos.m_line);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.2f, 1.0f));
|
||||
ImGui::TextUnformatted(m_errorText.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
};
|
||||
|
||||
using ErrorHoverBoxes = std::map<Coordinates, ErrorHoverBox>;
|
||||
|
||||
// A line of text in the pattern editor consists of three strings; the character encoding, the color encoding and the flags.
|
||||
// The char encoding is utf-8, the color encoding are indices to the color palette and the flags are used to override the colors
|
||||
// depending on priorities; e.g. comments, strings, etc.
|
||||
|
||||
class Line {
|
||||
public:
|
||||
struct FlagBits {
|
||||
bool comment: 1;
|
||||
bool blockComment: 1;
|
||||
bool docComment: 1;
|
||||
bool blockDocComment: 1;
|
||||
bool globalDocComment: 1;
|
||||
bool deactivated: 1;
|
||||
bool preprocessor: 1;
|
||||
bool matchedBracket: 1;
|
||||
};
|
||||
|
||||
union Flags {
|
||||
Flags(char value) : m_value(value) {}
|
||||
Flags(FlagBits bits) : m_bits(bits) {}
|
||||
FlagBits m_bits;
|
||||
char m_value;
|
||||
};
|
||||
|
||||
constexpr static char inComment = 31;
|
||||
|
||||
class LineIterator {
|
||||
public:
|
||||
strConstIter m_charsIter;
|
||||
strConstIter m_colorsIter;
|
||||
strConstIter m_flagsIter;
|
||||
|
||||
LineIterator(const LineIterator &other) : m_charsIter(other.m_charsIter), m_colorsIter(other.m_colorsIter), m_flagsIter(other.m_flagsIter) {}
|
||||
|
||||
LineIterator() = default;
|
||||
|
||||
char operator*();
|
||||
LineIterator operator++();
|
||||
LineIterator operator=(const LineIterator &other);
|
||||
bool operator!=(const LineIterator &other) const;
|
||||
bool operator==(const LineIterator &other) const;
|
||||
LineIterator operator+(i32 n);
|
||||
i32 operator-(LineIterator l);
|
||||
};
|
||||
|
||||
enum class LinePart {
|
||||
Chars,
|
||||
Utf8,
|
||||
Colors,
|
||||
Flags
|
||||
};
|
||||
|
||||
LineIterator begin() const;
|
||||
LineIterator end() const;
|
||||
|
||||
std::string m_chars;
|
||||
std::string m_colors;
|
||||
std::string m_flags;
|
||||
bool m_colorized = false;
|
||||
|
||||
Line() : m_chars(), m_colors(), m_flags(), m_colorized(false) {}
|
||||
explicit Line(const char *line) { Line(std::string(line)); }
|
||||
explicit Line(const std::string &line) : m_chars(line), m_colors(std::string(line.size(), 0x00)), m_flags(std::string(line.size(), 0x00)), m_colorized(false) {}
|
||||
Line(const Line &line) : m_chars(line.m_chars), m_colors(line.m_colors), m_flags(line.m_flags), m_colorized(line.m_colorized) {}
|
||||
|
||||
i32 getCharacterColumn(i32 index) const;
|
||||
LineIterator begin();
|
||||
LineIterator end();
|
||||
Line &operator=(const Line &line);
|
||||
Line &operator=(Line &&line) noexcept;
|
||||
u64 size() const;
|
||||
char front(LinePart part = LinePart::Chars) const;
|
||||
std::string frontUtf8(LinePart part = LinePart::Chars) const;
|
||||
void push_back(char c);
|
||||
bool empty() const;
|
||||
std::string substr(u64 start, u64 length = (u64) -1, LinePart part = LinePart::Chars) const;
|
||||
char operator[](u64 index) const;
|
||||
// C++ can't overload functions based on return type, so use any type other
|
||||
// than u64 to avoid ambiguity.
|
||||
std::string operator[](i64 column) const;
|
||||
void setNeedsUpdate(bool needsUpdate);
|
||||
void append(const char *text);
|
||||
void append(const char text);
|
||||
void append(const std::string &text);
|
||||
void append(const Line &line);
|
||||
void append(LineIterator begin, LineIterator end);
|
||||
void insert(LineIterator iter, const std::string &text);
|
||||
void insert(LineIterator iter, const char text);
|
||||
void insert(LineIterator iter, strConstIter beginString, strConstIter endString);
|
||||
void insert(LineIterator iter, const Line &line);
|
||||
void insert(LineIterator iter, LineIterator beginLine, LineIterator endLine);
|
||||
void erase(LineIterator begin);
|
||||
void erase(LineIterator begin, u64 count);
|
||||
void erase(u64 start, u64 length = (u64) -1);
|
||||
void clear();
|
||||
void setLine(const std::string &text);
|
||||
void setLine(const Line &text);
|
||||
bool needsUpdate() const;
|
||||
};
|
||||
|
||||
using Lines = std::vector<Line>;
|
||||
|
||||
struct LanguageDefinition {
|
||||
typedef std::pair<std::string, PaletteIndex> TokenRegexString;
|
||||
typedef std::vector<TokenRegexString> TokenRegexStrings;
|
||||
|
||||
typedef bool(*TokenizeCallback)(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin,
|
||||
strConstIter &out_end, PaletteIndex &paletteIndex);
|
||||
|
||||
std::string m_name;
|
||||
Keywords m_keywords;
|
||||
Identifiers m_identifiers;
|
||||
Identifiers m_preprocIdentifiers;
|
||||
std::string m_singleLineComment, m_commentEnd, m_commentStart, m_globalDocComment, m_docComment, m_blockDocComment;
|
||||
char m_preprocChar;
|
||||
bool m_autoIndentation;
|
||||
TokenizeCallback m_tokenize;
|
||||
TokenRegexStrings m_tokenRegexStrings;
|
||||
bool m_caseSensitive;
|
||||
|
||||
LanguageDefinition() : m_name(""), m_keywords({}), m_identifiers({}), m_preprocIdentifiers({}),
|
||||
m_singleLineComment(""), m_commentEnd(""),
|
||||
m_commentStart(""), m_globalDocComment(""), m_docComment(""), m_blockDocComment(""),
|
||||
m_preprocChar('#'), m_autoIndentation(true), m_tokenize(nullptr),
|
||||
m_tokenRegexStrings({}), m_caseSensitive(true) {}
|
||||
|
||||
static const LanguageDefinition &CPlusPlus();
|
||||
static const LanguageDefinition &HLSL();
|
||||
static const LanguageDefinition &GLSL();
|
||||
static const LanguageDefinition &C();
|
||||
static const LanguageDefinition &SQL();
|
||||
static const LanguageDefinition &AngelScript();
|
||||
static const LanguageDefinition &Lua();
|
||||
};
|
||||
TextEditor();
|
||||
~TextEditor();
|
||||
|
||||
private:
|
||||
class UndoRecord {
|
||||
public:
|
||||
UndoRecord() {}
|
||||
|
||||
~UndoRecord() {}
|
||||
|
||||
UndoRecord( const std::string &added,
|
||||
const TextEditor::Selection addedSelection,
|
||||
const std::string &removed,
|
||||
const TextEditor::Selection removedSelection,
|
||||
TextEditor::EditorState &before,
|
||||
TextEditor::EditorState &after);
|
||||
|
||||
void undo(TextEditor *editor);
|
||||
void redo(TextEditor *editor);
|
||||
|
||||
std::string m_added;
|
||||
Selection m_addedSelection;
|
||||
std::string m_removed;
|
||||
Selection m_removedSelection;
|
||||
EditorState m_before;
|
||||
EditorState m_after;
|
||||
};
|
||||
|
||||
typedef std::vector<UndoRecord> UndoBuffer;
|
||||
|
||||
struct MatchedBracket {
|
||||
bool m_active = false;
|
||||
bool m_changed = false;
|
||||
Coordinates m_nearCursor = {};
|
||||
Coordinates m_matched = {};
|
||||
static const std::string s_separators;
|
||||
static const std::string s_operators;
|
||||
|
||||
MatchedBracket(const MatchedBracket &other) : m_active(other.m_active), m_changed(other.m_changed),
|
||||
m_nearCursor(other.m_nearCursor),
|
||||
m_matched(other.m_matched) {}
|
||||
|
||||
MatchedBracket() : m_active(false), m_changed(false), m_nearCursor(0, 0), m_matched(0, 0) {}
|
||||
MatchedBracket(bool active, bool changed, const Coordinates &nearCursor, const Coordinates &matched)
|
||||
: m_active(active), m_changed(changed), m_nearCursor(nearCursor), m_matched(matched) {}
|
||||
|
||||
bool checkPosition(TextEditor *editor, const Coordinates &from);
|
||||
bool isNearABracket(TextEditor *editor, const Coordinates &from);
|
||||
i32 detectDirection(TextEditor *editor, const Coordinates &from);
|
||||
void findMatchingBracket(TextEditor *editor);
|
||||
bool isActive() const { return m_active; }
|
||||
bool hasChanged() const { return m_changed; }
|
||||
};
|
||||
|
||||
|
||||
public:
|
||||
// Rendering
|
||||
ImVec2 underwaves(ImVec2 pos, u32 nChars, ImColor color = ImGui::GetStyleColorVec4(ImGuiCol_Text), const ImVec2 &size_arg = ImVec2(0, 0));
|
||||
void setTabSize(i32 value);
|
||||
float getPageSize() const;
|
||||
bool isEndOfLine(const Coordinates &coordinates) const;
|
||||
bool isEndOfFile(const Coordinates &coordinates) const;
|
||||
bool isEndOfLine() const;
|
||||
bool isStartOfLine() const;
|
||||
void setTopLine();
|
||||
void render(const char *title, const ImVec2 &size = ImVec2(), bool border = false);
|
||||
|
||||
inline void setShowCursor(bool value) { m_showCursor = value; }
|
||||
inline void setShowLineNumbers(bool value) { m_showLineNumbers = value; }
|
||||
inline void setShowWhitespaces(bool value) { m_showWhitespaces = value; }
|
||||
inline bool isShowingWhitespaces() const { return m_showWhitespaces; }
|
||||
inline i32 getTabSize() const { return m_tabSize; }
|
||||
ImVec2 &getCharAdvance() { return m_charAdvance; }
|
||||
void clearGotoBoxes() { m_errorGotoBoxes.clear(); }
|
||||
void clearCursorBoxes() { m_cursorBoxes.clear(); }
|
||||
void addClickableText(std::string text) { m_clickableText.push_back(text); }
|
||||
void setErrorMarkers(const ErrorMarkers &markers) { m_errorMarkers = markers; }
|
||||
Breakpoints &getBreakpoints() { return m_breakpoints; }
|
||||
void setBreakpoints(const Breakpoints &markers) { m_breakpoints = markers; }
|
||||
void setLongestLineLength(u64 line) { m_longestLineLength = line; }
|
||||
u64 getLongestLineLength() const { return m_longestLineLength; }
|
||||
void setTopMarginChanged(i32 newMargin);
|
||||
void setFocusAtCoords(const Coordinates &coords);
|
||||
void clearErrorMarkers();
|
||||
void clearActionables();
|
||||
private:
|
||||
void ensureCursorVisible();
|
||||
void resetCursorBlinkTime();
|
||||
void renderText(const char *title, const ImVec2 &lineNumbersStartPos, const ImVec2 &textEditorSize);
|
||||
void setFocus();
|
||||
void preRender();
|
||||
void drawSelection(float lineNo);
|
||||
void drawLineNumbers(ImVec2 position, float lineNo, const ImVec2 &contentSize, bool focused, ImDrawList *drawList);
|
||||
void renderCursor(float lineNo, ImDrawList *drawList);
|
||||
void renderGotoButtons(float lineNo);
|
||||
void drawText(Coordinates &lineStart, u64 i, u32 tokenLength, char color);
|
||||
void postRender(const char *title, ImVec2 position, float lineNo);
|
||||
ImVec2 calculateCharAdvance() const;
|
||||
float textDistanceToLineStart(const Coordinates &from) const;
|
||||
// Highlighting
|
||||
public:
|
||||
void colorize();
|
||||
void setLanguageDefinition(const LanguageDefinition &aLanguageDef);
|
||||
static const Palette &getPalette();
|
||||
static void setPalette(const Palette &value);
|
||||
static const Palette &getDarkPalette();
|
||||
static const Palette &getLightPalette();
|
||||
static const Palette &getRetroBluePalette();
|
||||
bool isColorizerEnabled() const { return m_colorizerEnabled; }
|
||||
const LanguageDefinition &getLanguageDefinition() const { return m_languageDefinition; }
|
||||
void setNeedsUpdate(u32 line, bool needsUpdate);
|
||||
void setColorizedLine(u64 line, const std::string &tokens);
|
||||
private:
|
||||
void colorizeRange();
|
||||
void colorizeInternal();
|
||||
//Editing
|
||||
public:
|
||||
void deleteWordLeft();
|
||||
void deleteWordRight();
|
||||
void backspace();
|
||||
bool canUndo();
|
||||
bool canRedo() const;
|
||||
void undo(i32 steps = 1);
|
||||
void redo(i32 steps = 1);
|
||||
void copy();
|
||||
void cut();
|
||||
void paste();
|
||||
void deleteChar();
|
||||
void insertText(const std::string &value);
|
||||
void insertText(const char *value);
|
||||
void appendLine(const std::string &value);
|
||||
void setOverwrite(bool value) { m_overwrite = value; }
|
||||
bool isOverwrite() const { return m_overwrite; }
|
||||
void setText(const std::string &text, bool undo = false);
|
||||
std::string getText() const;
|
||||
std::vector<std::string> getTextLines() const;
|
||||
std::string getSelectedText() const;
|
||||
std::string getLineText(i32 line) const;
|
||||
inline void setTextChanged(bool value) { m_textChanged = value; }
|
||||
inline bool isTextChanged() { return m_textChanged; }
|
||||
inline void setReadOnly(bool value) { m_readOnly = value; }
|
||||
inline void setHandleMouseInputs(bool value) { m_handleMouseInputs = value; }
|
||||
inline bool isHandleMouseInputsEnabled() const { return m_handleKeyboardInputs; }
|
||||
inline void setHandleKeyboardInputs(bool value) { m_handleKeyboardInputs = value; }
|
||||
inline bool isHandleKeyboardInputsEnabled() const { return m_handleKeyboardInputs; }
|
||||
private:
|
||||
std::string getText(const Selection &selection) const;
|
||||
void deleteRange(const Selection &selection);
|
||||
i32 insertTextAt(Coordinates &where, const std::string &value);
|
||||
void removeLine(i32 start, i32 end);
|
||||
void removeLine(i32 index);
|
||||
Line &insertLine(i32 index);
|
||||
void insertLine(i32 index, const std::string &text);
|
||||
void enterCharacter(ImWchar character, bool shift);
|
||||
void deleteSelection();
|
||||
// Navigating
|
||||
public:
|
||||
void jumpToLine(i32 line = -1);
|
||||
void jumpToCoords(const Coordinates &coords);
|
||||
void moveUp(i32 amount = 1, bool select = false);
|
||||
void moveDown(i32 amount = 1, bool select = false);
|
||||
void moveLeft(i32 amount = 1, bool select = false, bool wordMode = false);
|
||||
void moveRight(i32 amount = 1, bool select = false, bool wordMode = false);
|
||||
void moveTop(bool select = false);
|
||||
void moveBottom(bool select = false);
|
||||
void moveHome(bool select = false);
|
||||
void moveEnd(bool select = false);
|
||||
void moveToMatchedBracket(bool select = false);
|
||||
void setScrollY();
|
||||
Coordinates getCursorPosition() const { return setCoordinates(m_state.m_cursorPosition); }
|
||||
void setCursorPosition(const Coordinates &position);
|
||||
void setCursorPosition();
|
||||
private:
|
||||
Coordinates setCoordinates(const Coordinates &value) const;
|
||||
Coordinates setCoordinates(i32 line, i32 column) const;
|
||||
Selection setCoordinates(const Selection &value) const;
|
||||
void advance(Coordinates &coordinates) const;
|
||||
Coordinates findWordStart(const Coordinates &from) const;
|
||||
Coordinates findWordEnd(const Coordinates &from) const;
|
||||
Coordinates findPreviousWord(const Coordinates &from) const;
|
||||
Coordinates findNextWord(const Coordinates &from) const;
|
||||
u32 skipSpaces(const Coordinates &from);
|
||||
//Support
|
||||
public:
|
||||
void setSelection(const Selection &selection);
|
||||
Selection getSelection() const;
|
||||
void selectWordUnderCursor();
|
||||
void selectAll();
|
||||
bool hasSelection() const;
|
||||
void refreshSearchResults();
|
||||
i32 getTotalLines() const { return (i32) m_lines.size(); }
|
||||
FindReplaceHandler *getFindReplaceHandler() { return &m_findReplaceHandler; }
|
||||
void setSourceCodeEditor(TextEditor *editor) { m_sourceCodeEditor = editor; }
|
||||
inline void clearBreakpointsChanged() { m_breakPointsChanged = false; }
|
||||
inline bool isBreakpointsChanged() { return m_breakPointsChanged; }
|
||||
inline void setImGuiChildIgnored(bool value) { m_ignoreImGuiChild = value; }
|
||||
inline bool isImGuiChildIgnored() const { return m_ignoreImGuiChild; }
|
||||
bool raiseContextMenu() { return m_raiseContextMenu; }
|
||||
void clearRaiseContextMenu() { m_raiseContextMenu = false; }
|
||||
TextEditor *GetSourceCodeEditor();
|
||||
bool isEmpty() const;
|
||||
private:
|
||||
void addUndo(UndoRecord &value);
|
||||
TextEditor::PaletteIndex getColorIndexFromFlags(Line::Flags flags);
|
||||
void handleKeyboardInputs();
|
||||
void handleMouseInputs();
|
||||
// utf8
|
||||
public:
|
||||
static i32 imTextCharToUtf8(char *buffer, i32 buf_size, u32 c);
|
||||
static void imTextCharToUtf8(std::string &buffer, u32 c);
|
||||
static i32 utf8CharLength(uint8_t c);
|
||||
static i32 getStringCharacterCount(const std::string &str);
|
||||
static TextEditor::Coordinates stringIndexToCoordinates(i32 strIndex, const std::string &input);
|
||||
private:
|
||||
|
||||
Coordinates screenPosToCoordinates(const ImVec2 &position) const;
|
||||
Coordinates lineCoordsToIndexCoords(const Coordinates &coordinates) const;
|
||||
i32 lineCoordinatesToIndex(const Coordinates &coordinates) const;
|
||||
Coordinates getCharacterCoordinates(i32 line, i32 index) const;
|
||||
i32 getLineCharacterCount(i32 line) const;
|
||||
u64 getLineByteCount(i32 line) const;
|
||||
i32 getLineMaxColumn(i32 line) const;
|
||||
|
||||
public:
|
||||
FindReplaceHandler m_findReplaceHandler;
|
||||
private:
|
||||
float m_lineSpacing = 1.0F;
|
||||
Lines m_lines;
|
||||
EditorState m_state = {};
|
||||
UndoBuffer m_undoBuffer;
|
||||
i32 m_undoIndex = 0;
|
||||
bool m_scrollToBottom = false;
|
||||
float m_topMargin = 0.0F;
|
||||
float m_newTopMargin = 0.0F;
|
||||
float m_oldTopMargin = 0.0F;
|
||||
bool m_topMarginChanged = false;
|
||||
|
||||
i32 m_tabSize = 4;
|
||||
bool m_overwrite = false;
|
||||
bool m_readOnly = false;
|
||||
bool m_withinRender = false;
|
||||
bool m_scrollToCursor = false;
|
||||
bool m_scrollToTop = false;
|
||||
bool m_textChanged = false;
|
||||
bool m_colorizerEnabled = true;
|
||||
float m_lineNumberFieldWidth = 0.0F;
|
||||
u64 m_longestLineLength = 0;
|
||||
float m_leftMargin = 10.0;
|
||||
float m_topLine = 0.0F;
|
||||
bool m_setTopLine = false;
|
||||
bool m_breakPointsChanged = false;
|
||||
bool m_handleKeyboardInputs = true;
|
||||
bool m_handleMouseInputs = true;
|
||||
bool m_ignoreImGuiChild = false;
|
||||
bool m_showWhitespaces = true;
|
||||
|
||||
MatchedBracket m_matchedBracket = {};
|
||||
Palette m_palette = {};
|
||||
LanguageDefinition m_languageDefinition = {};
|
||||
RegexList m_regexList;
|
||||
bool m_updateFlags = true;
|
||||
Breakpoints m_breakpoints = {};
|
||||
ErrorMarkers m_errorMarkers = {};
|
||||
ErrorHoverBoxes m_errorHoverBoxes = {};
|
||||
ErrorGotoBoxes m_errorGotoBoxes = {};
|
||||
CursorBoxes m_cursorBoxes = {};
|
||||
ImVec2 m_charAdvance = {};
|
||||
Selection m_interactiveSelection = {};
|
||||
u64 m_startTime = 0;
|
||||
std::vector<std::string> m_defines;
|
||||
TextEditor *m_sourceCodeEditor = nullptr;
|
||||
float m_shiftedScrollY = 0;
|
||||
float m_scrollYIncrement = 0.0F;
|
||||
bool m_setScrollY = false;
|
||||
float m_numberOfLinesDisplayed = 0;
|
||||
float m_lastClick = -1.0F;
|
||||
bool m_showCursor = true;
|
||||
bool m_showLineNumbers = true;
|
||||
bool m_raiseContextMenu = false;
|
||||
Coordinates m_focusAtCoords = {};
|
||||
bool m_updateFocus = false;
|
||||
|
||||
std::vector<std::string> m_clickableText;
|
||||
|
||||
static const i32 s_cursorBlinkInterval;
|
||||
static const i32 s_cursorBlinkOnTime;
|
||||
static ImVec2 s_cursorScreenPosition;
|
||||
};
|
||||
|
||||
bool tokenizeCStyleString(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end);
|
||||
bool tokenizeCStyleCharacterLiteral(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end);
|
||||
bool tokenizeCStyleIdentifier(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end);
|
||||
bool tokenizeCStyleNumber(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end);
|
||||
bool tokenizeCStyleOperator(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end);
|
||||
bool tokenizeCStyleSeparator(strConstIter in_begin, strConstIter in_end, strConstIter &out_begin, strConstIter &out_end);
|
||||
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <wolv/math_eval/math_evaluator.hpp>
|
||||
#include <TextEditor.h>
|
||||
#include <ui/text_editor.hpp>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
|
||||
21
plugins/ui/source/ui/text_editor/LICENSE.txt
Normal file
21
plugins/ui/source/ui/text_editor/LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 BalazsJako
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
811
plugins/ui/source/ui/text_editor/editor.cpp
Normal file
811
plugins/ui/source/ui/text_editor/editor.cpp
Normal file
@@ -0,0 +1,811 @@
|
||||
#include <ui/text_editor.hpp>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <regex>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <wolv/utils/string.hpp>
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include "imgui.h"
|
||||
|
||||
namespace hex::ui {
|
||||
|
||||
const i32 TextEditor::s_cursorBlinkInterval = 1200;
|
||||
const i32 TextEditor::s_cursorBlinkOnTime = 800;
|
||||
ImVec2 TextEditor::s_cursorScreenPosition;
|
||||
|
||||
TextEditor::FindReplaceHandler::FindReplaceHandler() : m_matchCase(false), m_wholeWord(false), m_findRegEx(false) {}
|
||||
const std::string TextEditor::MatchedBracket::s_separators = "()[]{}";
|
||||
const std::string TextEditor::MatchedBracket::s_operators = "<>";
|
||||
|
||||
TextEditor::TextEditor() {
|
||||
m_startTime = ImGui::GetTime() * 1000;
|
||||
setLanguageDefinition(LanguageDefinition::HLSL());
|
||||
m_lines.push_back(Line());
|
||||
}
|
||||
|
||||
TextEditor::~TextEditor() {
|
||||
}
|
||||
|
||||
std::string TextEditor::getText(const Selection &from) const {
|
||||
std::string result;
|
||||
auto selection = setCoordinates(from);
|
||||
auto columns = selection.getSelectedColumns();
|
||||
if (selection.isSingleLine())
|
||||
result = m_lines[selection.m_start.m_line].substr(columns.m_line, columns.m_column, Line::LinePart::Utf8);
|
||||
else {
|
||||
auto lines = selection.getSelectedLines();
|
||||
result = m_lines[lines.m_line].substr(columns.m_line, (u64) -1, Line::LinePart::Utf8) + '\n';
|
||||
for (i32 i = lines.m_line + 1; i < lines.m_column; i++) {
|
||||
result += m_lines[i].m_chars + '\n';
|
||||
}
|
||||
result += m_lines[lines.m_column].substr(0, columns.m_column, Line::LinePart::Utf8);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void TextEditor::deleteRange(const Selection &rangeToDelete) {
|
||||
if (m_readOnly)
|
||||
return;
|
||||
Selection selection = setCoordinates(rangeToDelete);
|
||||
auto columns = selection.getSelectedColumns();
|
||||
|
||||
if (selection.isSingleLine()) {
|
||||
auto &line = m_lines[selection.m_start.m_line];
|
||||
line.erase(columns.m_line, columns.m_column);
|
||||
} else {
|
||||
auto lines = selection.getSelectedLines();
|
||||
auto &firstLine = m_lines[lines.m_line];
|
||||
auto &lastLine = m_lines[lines.m_column];
|
||||
firstLine.erase(columns.m_line,(u64) -1);
|
||||
lastLine.erase(0, columns.m_column);
|
||||
|
||||
if (!lastLine.empty()) {
|
||||
firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end());
|
||||
firstLine.m_colorized = false;
|
||||
}
|
||||
if (lines.m_line < lines.m_column)
|
||||
removeLine(lines.m_line + 1, lines.m_column);
|
||||
}
|
||||
|
||||
m_textChanged = true;
|
||||
}
|
||||
|
||||
void TextEditor::appendLine(const std::string &value) {
|
||||
auto text = wolv::util::replaceStrings(wolv::util::preprocessText(value), "\000", ".");
|
||||
if (text.empty())
|
||||
return;
|
||||
if (isEmpty()) {
|
||||
m_lines[0].m_chars = text;
|
||||
m_lines[0].m_colors = std::string(text.size(), 0);
|
||||
m_lines[0].m_flags = std::string(text.size(), 0);
|
||||
} else
|
||||
m_lines.push_back(Line(text));
|
||||
|
||||
setCursorPosition(setCoordinates((i32) m_lines.size() - 1, 0));
|
||||
m_lines.back().m_colorized = false;
|
||||
ensureCursorVisible();
|
||||
m_textChanged = true;
|
||||
}
|
||||
|
||||
|
||||
i32 TextEditor::insertTextAt(Coordinates /* inout */ &where, const std::string &value) {
|
||||
if (value.empty())
|
||||
return 0;
|
||||
auto start = setCoordinates(where);
|
||||
if (start == Invalid)
|
||||
return 0;
|
||||
auto &line = m_lines[start.m_line];
|
||||
auto stringVector = wolv::util::splitString(value, "\n", false);
|
||||
auto lineCount = (i32) stringVector.size();
|
||||
where.m_line += lineCount - 1;
|
||||
where.m_column += getStringCharacterCount(stringVector[lineCount - 1]);
|
||||
stringVector[lineCount - 1].append(line.substr(start.m_column,(u64) -1, Line::LinePart::Utf8));
|
||||
line.erase(start.m_column, (u64) -1);
|
||||
|
||||
line.append(stringVector[0]);
|
||||
line.m_colorized = false;
|
||||
for (i32 i = 1; i < lineCount; i++) {
|
||||
insertLine(start.m_line + i, stringVector[i]);
|
||||
m_lines[start.m_line + i].m_colorized = false;
|
||||
}
|
||||
m_textChanged = true;
|
||||
return lineCount;
|
||||
}
|
||||
|
||||
void TextEditor::deleteWordLeft() {
|
||||
const auto wordEnd = getCursorPosition();
|
||||
const auto wordStart = findPreviousWord(getCursorPosition());
|
||||
setSelection(Selection(wordStart, wordEnd));
|
||||
backspace();
|
||||
}
|
||||
|
||||
void TextEditor::deleteWordRight() {
|
||||
const auto wordStart = getCursorPosition();
|
||||
const auto wordEnd = findNextWord(getCursorPosition());
|
||||
setSelection(Selection(wordStart, wordEnd));
|
||||
backspace();
|
||||
}
|
||||
|
||||
void TextEditor::removeLine(i32 lineStart, i32 lineEnd) {
|
||||
|
||||
ErrorMarkers errorMarker;
|
||||
u32 uLineStart = static_cast<u32>(lineStart);
|
||||
u32 uLineEnd = static_cast<u32>(lineEnd);
|
||||
for (auto &i: m_errorMarkers) {
|
||||
ErrorMarkers::value_type e(i.first.m_line >= lineStart ? setCoordinates(i.first.m_line - 1, i.first.m_column) : i.first, i.second);
|
||||
if (e.first.m_line >= lineStart && e.first.m_line <= lineEnd)
|
||||
continue;
|
||||
errorMarker.insert(e);
|
||||
}
|
||||
m_errorMarkers = std::move(errorMarker);
|
||||
|
||||
Breakpoints breakpoints;
|
||||
for (auto breakpoint: m_breakpoints) {
|
||||
if (breakpoint <= uLineStart || breakpoint >= uLineEnd) {
|
||||
if (breakpoint >= uLineEnd) {
|
||||
breakpoints.insert(breakpoint - 1);
|
||||
m_breakPointsChanged = true;
|
||||
} else
|
||||
breakpoints.insert(breakpoint);
|
||||
}
|
||||
}
|
||||
|
||||
m_breakpoints = std::move(breakpoints);
|
||||
// use clamp to ensure valid results instead of assert.
|
||||
auto start = std::clamp(lineStart, 0, (i32) m_lines.size() - 1);
|
||||
auto end = std::clamp(lineEnd, 0, (i32) m_lines.size());
|
||||
if (start > end)
|
||||
std::swap(start, end);
|
||||
|
||||
m_lines.erase(m_lines.begin() + lineStart, m_lines.begin() + lineEnd + 1);
|
||||
|
||||
m_textChanged = true;
|
||||
}
|
||||
|
||||
void TextEditor::removeLine(i32 index) {
|
||||
removeLine(index, index);
|
||||
}
|
||||
|
||||
void TextEditor::insertLine(i32 index, const std::string &text) {
|
||||
if (index < 0 || index > (i32) m_lines.size())
|
||||
return;
|
||||
auto &line = insertLine(index);
|
||||
line.append(text);
|
||||
line.m_colorized = false;
|
||||
m_textChanged = true;
|
||||
}
|
||||
|
||||
TextEditor::Line &TextEditor::insertLine(i32 index) {
|
||||
|
||||
if (isEmpty())
|
||||
return *m_lines.insert(m_lines.begin(), Line());
|
||||
|
||||
if (index == (i32)m_lines.size())
|
||||
return *m_lines.insert(m_lines.end(), Line());
|
||||
|
||||
auto newLine = Line();
|
||||
|
||||
TextEditor::Line &result = *m_lines.insert(m_lines.begin() + index, newLine);
|
||||
result.m_colorized = false;
|
||||
|
||||
ErrorMarkers errorMarker;
|
||||
for (auto &i: m_errorMarkers)
|
||||
errorMarker.insert(ErrorMarkers::value_type(i.first.m_line >= index ? setCoordinates(i.first.m_line + 1, i.first.m_column) : i.first, i.second));
|
||||
m_errorMarkers = std::move(errorMarker);
|
||||
|
||||
Breakpoints breakpoints;
|
||||
for (auto breakpoint: m_breakpoints) {
|
||||
if (breakpoint >= (u32)index) {
|
||||
breakpoints.insert(breakpoint + 1);
|
||||
m_breakPointsChanged = true;
|
||||
} else
|
||||
breakpoints.insert(breakpoint);
|
||||
}
|
||||
if (m_breakPointsChanged)
|
||||
m_breakpoints = std::move(breakpoints);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void TextEditor::setText(const std::string &text, bool undo) {
|
||||
UndoRecord u;
|
||||
if (!m_readOnly && undo) {
|
||||
u.m_before = m_state;
|
||||
u.m_removed = getText();
|
||||
u.m_removedSelection.m_start = setCoordinates(0, 0);
|
||||
u.m_removedSelection.m_end = setCoordinates(-1, -1);
|
||||
if (u.m_removedSelection.m_start == Invalid || u.m_removedSelection.m_end == Invalid)
|
||||
return;
|
||||
}
|
||||
auto vectorString = wolv::util::splitString(text, "\n", false);
|
||||
auto lineCount = vectorString.size();
|
||||
if (lineCount == 0) {
|
||||
m_lines.resize(1);
|
||||
m_lines[0].clear();
|
||||
} else {
|
||||
m_lines.resize(lineCount);
|
||||
u64 i = 0;
|
||||
for (auto line: vectorString) {
|
||||
m_lines[i].setLine(line);
|
||||
m_lines[i].m_colorized = false;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (!m_readOnly && undo) {
|
||||
u.m_added = text;
|
||||
u.m_addedSelection.m_start = setCoordinates(0, 0);
|
||||
u.m_addedSelection.m_end = setCoordinates(-1, -1);
|
||||
if (u.m_addedSelection.m_start == Invalid || u.m_addedSelection.m_end == Invalid)
|
||||
return;
|
||||
}
|
||||
m_textChanged = true;
|
||||
m_scrollToTop = true;
|
||||
if (!m_readOnly && undo) {
|
||||
u.m_after = m_state;
|
||||
|
||||
addUndo(u);
|
||||
}
|
||||
|
||||
colorize();
|
||||
}
|
||||
|
||||
void TextEditor::enterCharacter(ImWchar character, bool shift) {
|
||||
if (m_readOnly)
|
||||
return;
|
||||
|
||||
UndoRecord u;
|
||||
|
||||
u.m_before = m_state;
|
||||
|
||||
resetCursorBlinkTime();
|
||||
|
||||
if (hasSelection()) {
|
||||
if (character == '\t') {
|
||||
|
||||
auto start = m_state.m_selection.m_start;
|
||||
auto end = m_state.m_selection.m_end;
|
||||
auto originalEnd = end;
|
||||
|
||||
start.m_column = 0;
|
||||
|
||||
if (end.m_column == 0 && end.m_line > 0)
|
||||
--end.m_line;
|
||||
if (end.m_line >= (i32) m_lines.size())
|
||||
end.m_line = isEmpty() ? 0 : (i32) m_lines.size() - 1;
|
||||
end.m_column = getLineMaxColumn(end.m_line);
|
||||
|
||||
u.m_removedSelection = Selection(start, end);
|
||||
u.m_removed = getText(u.m_removedSelection);
|
||||
|
||||
bool modified = false;
|
||||
|
||||
for (i32 i = start.m_line; i <= end.m_line; i++) {
|
||||
auto &line = m_lines[i];
|
||||
if (shift) {
|
||||
if (!line.empty()) {
|
||||
auto index = line.m_chars.find_first_not_of(' ', 0);
|
||||
if (index == std::string::npos)
|
||||
index = line.size() - 1;
|
||||
if (index == 0) continue;
|
||||
u64 spacesToRemove = (index % m_tabSize) ? (index % m_tabSize) : m_tabSize;
|
||||
spacesToRemove = std::min(spacesToRemove, line.size());
|
||||
line.erase(line.begin(), spacesToRemove);
|
||||
line.m_colorized = false;
|
||||
modified = true;
|
||||
}
|
||||
} else {
|
||||
auto spacesToInsert = m_tabSize - (start.m_column % m_tabSize);
|
||||
std::string spaces(spacesToInsert, ' ');
|
||||
line.insert(line.begin(), spaces.begin(), spaces.end());
|
||||
line.m_colorized = false;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
Coordinates rangeEnd;
|
||||
if (originalEnd.m_column != 0) {
|
||||
end = setCoordinates(end.m_line, -1);
|
||||
if (end == Invalid)
|
||||
return;
|
||||
rangeEnd = end;
|
||||
u.m_added = getText(Selection(start, end));
|
||||
} else {
|
||||
end = setCoordinates(originalEnd.m_line, 0);
|
||||
rangeEnd = setCoordinates(end.m_line - 1, -1);
|
||||
if (end == Invalid || rangeEnd == Invalid)
|
||||
return;
|
||||
u.m_added = getText(Selection(start, rangeEnd));
|
||||
}
|
||||
|
||||
u.m_addedSelection = Selection(start, rangeEnd);
|
||||
u.m_after = m_state;
|
||||
|
||||
m_state.m_selection = Selection(start, end);
|
||||
addUndo(u);
|
||||
|
||||
m_textChanged = true;
|
||||
|
||||
ensureCursorVisible();
|
||||
}
|
||||
|
||||
return;
|
||||
} // c == '\t'
|
||||
else {
|
||||
u.m_removed = getSelectedText();
|
||||
u.m_removedSelection = Selection(m_state.m_selection);
|
||||
deleteSelection();
|
||||
}
|
||||
} // HasSelection
|
||||
|
||||
auto coord = setCoordinates(m_state.m_cursorPosition);
|
||||
u.m_addedSelection.m_start = coord;
|
||||
|
||||
if (m_lines.empty())
|
||||
m_lines.push_back(Line());
|
||||
|
||||
if (character == '\n') {
|
||||
insertLine(coord.m_line + 1);
|
||||
auto &line = m_lines[coord.m_line];
|
||||
auto &newLine = m_lines[coord.m_line + 1];
|
||||
|
||||
if (m_languageDefinition.m_autoIndentation)
|
||||
for (u64 it = 0; it < line.size() && isascii(line[it]) && isblank(line[it]); ++it)
|
||||
newLine.push_back(line[it]);
|
||||
|
||||
const u64 whitespaceSize = newLine.size();
|
||||
i32 charStart;
|
||||
i32 charPosition;
|
||||
auto charIndex = lineCoordinatesToIndex(coord);
|
||||
if (charIndex < (i32) whitespaceSize && m_languageDefinition.m_autoIndentation) {
|
||||
charStart = (i32) whitespaceSize;
|
||||
charPosition = charIndex;
|
||||
} else {
|
||||
charStart = charIndex;
|
||||
charPosition = (i32) whitespaceSize;
|
||||
}
|
||||
newLine.insert(newLine.end(), line.begin() + charStart, line.end());
|
||||
line.erase(line.begin() + charStart,(u64) -1);
|
||||
line.m_colorized = false;
|
||||
setCursorPosition(getCharacterCoordinates(coord.m_line + 1, charPosition));
|
||||
u.m_added = (char) character;
|
||||
u.m_addedSelection.m_end = setCoordinates(m_state.m_cursorPosition);
|
||||
} else if (character == '\t') {
|
||||
auto &line = m_lines[coord.m_line];
|
||||
auto charIndex = lineCoordinatesToIndex(coord);
|
||||
|
||||
if (!shift) {
|
||||
auto spacesToInsert = m_tabSize - (charIndex % m_tabSize);
|
||||
std::string spaces(spacesToInsert, ' ');
|
||||
line.insert(line.begin() + charIndex, spaces.begin(), spaces.end());
|
||||
line.m_colorized = false;
|
||||
setCursorPosition(getCharacterCoordinates(coord.m_line, charIndex + spacesToInsert));
|
||||
u.m_added = spaces;
|
||||
u.m_addedSelection.m_end = setCoordinates(m_state.m_cursorPosition);
|
||||
} else {
|
||||
auto spacesToRemove = (charIndex % m_tabSize);
|
||||
if (spacesToRemove == 0) spacesToRemove = m_tabSize;
|
||||
spacesToRemove = std::min(spacesToRemove, (i32) line.size());
|
||||
auto spacesRemoved = 0;
|
||||
for (i32 j = 0; j < spacesToRemove; j++) {
|
||||
if (*(line.begin() + (charIndex - 1)) == ' ') {
|
||||
line.erase(line.begin() + (charIndex - 1));
|
||||
charIndex -= 1;
|
||||
spacesRemoved++;
|
||||
}
|
||||
}
|
||||
std::string spaces(spacesRemoved, ' ');
|
||||
u.m_removed = spaces;
|
||||
u.m_removedSelection = Selection(Coordinates(coord.m_line, charIndex), Coordinates(coord.m_line, charIndex + spacesRemoved));
|
||||
line.m_colorized = false;
|
||||
setCursorPosition(getCharacterCoordinates(coord.m_line, std::max(0, charIndex)));
|
||||
}
|
||||
u.m_addedSelection.m_end = setCoordinates(m_state.m_cursorPosition);
|
||||
} else {
|
||||
std::string buf = "";
|
||||
imTextCharToUtf8(buf, character);
|
||||
if (buf.size() > 0) {
|
||||
auto &line = m_lines[coord.m_line];
|
||||
auto charIndex = lineCoordinatesToIndex(coord);
|
||||
|
||||
if (m_overwrite && charIndex < (i32) line.size()) {
|
||||
i64 column = coord.m_column;
|
||||
std::string c = line[column];
|
||||
auto charCount = getStringCharacterCount(c);
|
||||
auto d = c.size();
|
||||
|
||||
u.m_removedSelection = Selection(m_state.m_cursorPosition, getCharacterCoordinates(coord.m_line, coord.m_column + charCount));
|
||||
u.m_removed = std::string(line.m_chars.begin() + charIndex, line.m_chars.begin() + charIndex + d);
|
||||
line.erase(line.begin() + charIndex, d);
|
||||
line.m_colorized = false;
|
||||
}
|
||||
auto charCount = getStringCharacterCount(buf);
|
||||
if (buf == "{")
|
||||
buf += "}";
|
||||
else if (buf == "[")
|
||||
buf += "]";
|
||||
else if (buf == "(")
|
||||
buf += ")";
|
||||
if ((buf == "}" || buf == "]" || buf == ")") && buf == line.substr(charIndex, charCount))
|
||||
buf = "";
|
||||
|
||||
if (buf == "\"") {
|
||||
if (buf == line.substr(charIndex, charCount)) {
|
||||
if (line.m_colors[charIndex + 1] == (char) PaletteIndex::StringLiteral)
|
||||
buf += "\"";
|
||||
else
|
||||
buf = "";
|
||||
} else
|
||||
buf += "\"";
|
||||
}
|
||||
|
||||
if (buf == "'") {
|
||||
if (buf == line.substr(charIndex, charCount)) {
|
||||
if (line.m_colors[charIndex + 1] == (char) PaletteIndex::CharLiteral)
|
||||
buf += "'";
|
||||
else
|
||||
buf = "";
|
||||
} else
|
||||
buf += "'";
|
||||
}
|
||||
|
||||
line.insert(line.begin() + charIndex, buf.begin(), buf.end());
|
||||
line.m_colorized = false;
|
||||
u.m_added = buf;
|
||||
u.m_addedSelection.m_end = getCharacterCoordinates(coord.m_line, charIndex + buf.size());
|
||||
setCursorPosition(getCharacterCoordinates(coord.m_line, charIndex + charCount));
|
||||
} else
|
||||
return;
|
||||
}
|
||||
|
||||
u.m_after = m_state;
|
||||
m_textChanged = true;
|
||||
|
||||
addUndo(u);
|
||||
colorize();
|
||||
refreshSearchResults();
|
||||
ensureCursorVisible();
|
||||
}
|
||||
|
||||
void TextEditor::refreshSearchResults() {
|
||||
std::string findWord = m_findReplaceHandler.getFindWord();
|
||||
if (!findWord.empty()) {
|
||||
m_findReplaceHandler.resetMatches();
|
||||
m_findReplaceHandler.findAllMatches(this, findWord);
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::insertText(const std::string &value) {
|
||||
insertText(value.c_str());
|
||||
}
|
||||
|
||||
void TextEditor::insertText(const char *value) {
|
||||
if (value == nullptr)
|
||||
return;
|
||||
|
||||
auto pos = setCoordinates(m_state.m_cursorPosition);
|
||||
//auto start = std::min(pos, m_state.m_selection.m_start);
|
||||
|
||||
insertTextAt(pos, value);
|
||||
m_lines[pos.m_line].m_colorized = false;
|
||||
|
||||
setSelection(Selection(pos, pos));
|
||||
setCursorPosition(pos);
|
||||
refreshSearchResults();
|
||||
colorize();
|
||||
}
|
||||
|
||||
void TextEditor::deleteSelection() {
|
||||
|
||||
if (m_state.m_selection.m_end == m_state.m_selection.m_start)
|
||||
return;
|
||||
|
||||
deleteRange(m_state.m_selection);
|
||||
|
||||
setSelection(Selection(m_state.m_selection.m_start, m_state.m_selection.m_start));
|
||||
setCursorPosition(m_state.m_selection.m_start);
|
||||
refreshSearchResults();
|
||||
colorize();
|
||||
}
|
||||
|
||||
void TextEditor::deleteChar() {
|
||||
resetCursorBlinkTime();
|
||||
|
||||
if (isEmpty() || m_readOnly)
|
||||
return;
|
||||
|
||||
UndoRecord u;
|
||||
u.m_before = m_state;
|
||||
|
||||
if (hasSelection()) {
|
||||
u.m_removed = getSelectedText();
|
||||
u.m_removedSelection = m_state.m_selection;
|
||||
deleteSelection();
|
||||
} else {
|
||||
auto pos = setCoordinates(m_state.m_cursorPosition);
|
||||
setCursorPosition(pos);
|
||||
auto &line = m_lines[pos.m_line];
|
||||
|
||||
if (pos.m_column == getLineMaxColumn(pos.m_line)) {
|
||||
if (pos.m_line == (i32) m_lines.size() - 1)
|
||||
return;
|
||||
|
||||
u.m_removed = '\n';
|
||||
u.m_removedSelection.m_start = u.m_removedSelection.m_end = setCoordinates(m_state.m_cursorPosition);
|
||||
advance(u.m_removedSelection.m_end);
|
||||
|
||||
auto &nextLine = m_lines[pos.m_line + 1];
|
||||
line.insert(line.end(), nextLine.begin(), nextLine.end());
|
||||
line.m_colorized = false;
|
||||
removeLine(pos.m_line + 1);
|
||||
|
||||
} else {
|
||||
i64 charIndex = lineCoordinatesToIndex(pos);
|
||||
u.m_removedSelection.m_start = u.m_removedSelection.m_end = setCoordinates(m_state.m_cursorPosition);
|
||||
u.m_removedSelection.m_end.m_column++;
|
||||
u.m_removed = getText(u.m_removedSelection);
|
||||
|
||||
auto d = utf8CharLength(line[charIndex][0]);
|
||||
line.erase(line.begin() + charIndex, d);
|
||||
line.m_colorized = false;
|
||||
}
|
||||
|
||||
m_textChanged = true;
|
||||
|
||||
colorize();
|
||||
}
|
||||
|
||||
u.m_after = m_state;
|
||||
addUndo(u);
|
||||
refreshSearchResults();
|
||||
}
|
||||
|
||||
void TextEditor::backspace() {
|
||||
resetCursorBlinkTime();
|
||||
if (isEmpty() || m_readOnly)
|
||||
return;
|
||||
|
||||
UndoRecord u;
|
||||
u.m_before = m_state;
|
||||
|
||||
if (hasSelection()) {
|
||||
u.m_removed = getSelectedText();
|
||||
u.m_removedSelection = m_state.m_selection;
|
||||
deleteSelection();
|
||||
} else {
|
||||
auto pos = setCoordinates(m_state.m_cursorPosition);
|
||||
auto &line = m_lines[pos.m_line];
|
||||
|
||||
if (pos.m_column == 0) {
|
||||
if (pos.m_line == 0)
|
||||
return;
|
||||
|
||||
u.m_removed = '\n';
|
||||
u.m_removedSelection.m_start = u.m_removedSelection.m_end = setCoordinates(pos.m_line - 1, -1);
|
||||
advance(u.m_removedSelection.m_end);
|
||||
|
||||
auto &prevLine = m_lines[pos.m_line - 1];
|
||||
auto prevSize = getLineMaxColumn(pos.m_line - 1);
|
||||
if (prevSize == 0)
|
||||
prevLine = line;
|
||||
else
|
||||
prevLine.insert(prevLine.end(), line.begin(), line.end());
|
||||
prevLine.m_colorized = false;
|
||||
|
||||
|
||||
ErrorMarkers errorMarker;
|
||||
for (auto &i: m_errorMarkers)
|
||||
errorMarker.insert(ErrorMarkers::value_type(i.first.m_line - 1 == m_state.m_cursorPosition.m_line ? setCoordinates(i.first.m_line - 1, i.first.m_column) : i.first, i.second));
|
||||
m_errorMarkers = std::move(errorMarker);
|
||||
removeLine(m_state.m_cursorPosition.m_line);
|
||||
--m_state.m_cursorPosition.m_line;
|
||||
m_state.m_cursorPosition.m_column = prevSize;
|
||||
} else {
|
||||
pos.m_column -= 1;
|
||||
i64 column = pos.m_column;
|
||||
std::string charToRemove = line[column];
|
||||
if (pos.m_column < (i32) line.size() - 1) {
|
||||
std::string charToRemoveNext = line[column + 1];
|
||||
if (charToRemove == "{" && charToRemoveNext == "}") {
|
||||
charToRemove += "}";
|
||||
m_state.m_cursorPosition.m_column += 1;
|
||||
} else if (charToRemove == "[" && charToRemoveNext == "]") {
|
||||
charToRemove += "]";
|
||||
m_state.m_cursorPosition.m_column += 1;
|
||||
} else if (charToRemove == "(" && charToRemoveNext == ")") {
|
||||
charToRemove += ")";
|
||||
m_state.m_cursorPosition.m_column += 1;
|
||||
} else if (charToRemove == "\"" && charToRemoveNext == "\"") {
|
||||
charToRemove += "\"";
|
||||
m_state.m_cursorPosition.m_column += 1;
|
||||
} else if (charToRemove == "'" && charToRemoveNext == "'") {
|
||||
charToRemove += "'";
|
||||
m_state.m_cursorPosition.m_column += 1;
|
||||
}
|
||||
}
|
||||
u.m_removedSelection = Selection(pos, m_state.m_cursorPosition);
|
||||
u.m_removed = charToRemove;
|
||||
auto charStart = lineCoordinatesToIndex(pos);
|
||||
auto charEnd = lineCoordinatesToIndex(m_state.m_cursorPosition);
|
||||
line.erase(line.begin() + charStart, charEnd - charStart);
|
||||
m_state.m_cursorPosition = pos;
|
||||
line.m_colorized = false;
|
||||
}
|
||||
|
||||
m_textChanged = true;
|
||||
|
||||
ensureCursorVisible();
|
||||
colorize();
|
||||
}
|
||||
|
||||
u.m_after = m_state;
|
||||
addUndo(u);
|
||||
refreshSearchResults();
|
||||
}
|
||||
|
||||
void TextEditor::copy() {
|
||||
if (hasSelection()) {
|
||||
ImGui::SetClipboardText(getSelectedText().c_str());
|
||||
} else {
|
||||
if (!isEmpty()) {
|
||||
std::string str;
|
||||
const auto &line = m_lines[setCoordinates(m_state.m_cursorPosition).m_line];
|
||||
std::copy(line.m_chars.begin(), line.m_chars.end(), std::back_inserter(str));
|
||||
ImGui::SetClipboardText(str.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::cut() {
|
||||
if (m_readOnly) {
|
||||
copy();
|
||||
} else {
|
||||
if (!hasSelection()) {
|
||||
auto lineIndex = setCoordinates(m_state.m_cursorPosition).m_line;
|
||||
if (lineIndex < 0 || lineIndex >= (i32) m_lines.size())
|
||||
return;
|
||||
setSelection(Selection(setCoordinates(lineIndex, 0), setCoordinates(lineIndex + 1, 0)));
|
||||
}
|
||||
UndoRecord u;
|
||||
u.m_before = m_state;
|
||||
u.m_removed = getSelectedText();
|
||||
u.m_removedSelection = m_state.m_selection;
|
||||
|
||||
copy();
|
||||
deleteSelection();
|
||||
|
||||
u.m_after = m_state;
|
||||
addUndo(u);
|
||||
}
|
||||
refreshSearchResults();
|
||||
}
|
||||
|
||||
void TextEditor::paste() {
|
||||
if (m_readOnly)
|
||||
return;
|
||||
|
||||
auto clipText = ImGui::GetClipboardText();
|
||||
if (clipText != nullptr) {
|
||||
auto len = strlen(clipText);
|
||||
if (len > 0) {
|
||||
std::string text = wolv::util::preprocessText(clipText);
|
||||
UndoRecord u;
|
||||
u.m_before = m_state;
|
||||
|
||||
if (hasSelection()) {
|
||||
u.m_removed = getSelectedText();
|
||||
u.m_removedSelection = m_state.m_selection;
|
||||
deleteSelection();
|
||||
}
|
||||
|
||||
u.m_added = text;
|
||||
u.m_addedSelection.m_start = setCoordinates(m_state.m_cursorPosition);
|
||||
insertText(text);
|
||||
|
||||
u.m_addedSelection.m_end = setCoordinates(m_state.m_cursorPosition);
|
||||
u.m_after = m_state;
|
||||
addUndo(u);
|
||||
}
|
||||
}
|
||||
refreshSearchResults();
|
||||
}
|
||||
|
||||
bool TextEditor::canUndo() {
|
||||
return !m_readOnly && m_undoIndex > 0;
|
||||
}
|
||||
|
||||
bool TextEditor::canRedo() const {
|
||||
return !m_readOnly && m_undoIndex < (i32) m_undoBuffer.size();
|
||||
}
|
||||
|
||||
void TextEditor::undo(i32 steps) {
|
||||
while (canUndo() && steps-- > 0)
|
||||
m_undoBuffer[--m_undoIndex].undo(this);
|
||||
refreshSearchResults();
|
||||
}
|
||||
|
||||
void TextEditor::redo(i32 steps) {
|
||||
while (canRedo() && steps-- > 0)
|
||||
m_undoBuffer[m_undoIndex++].redo(this);
|
||||
refreshSearchResults();
|
||||
}
|
||||
|
||||
std::string TextEditor::getText() const {
|
||||
auto start = setCoordinates(0, 0);
|
||||
auto end = setCoordinates(-1, -1);
|
||||
if (start == Invalid || end == Invalid)
|
||||
return "";
|
||||
return getText(Selection(start, end));
|
||||
}
|
||||
|
||||
std::vector<std::string> TextEditor::getTextLines() const {
|
||||
std::vector<std::string> result;
|
||||
|
||||
result.reserve(m_lines.size());
|
||||
|
||||
for (const auto &line: m_lines) {
|
||||
std::string text = line.m_chars;
|
||||
result.emplace_back(std::move(text));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string TextEditor::getSelectedText() const {
|
||||
return getText(m_state.m_selection);
|
||||
}
|
||||
|
||||
std::string TextEditor::getLineText(i32 line) const {
|
||||
auto sanitizedLine = setCoordinates(line, 0);
|
||||
auto endLine = setCoordinates(line, -1);
|
||||
if (sanitizedLine == Invalid || endLine == Invalid)
|
||||
return "";
|
||||
return getText(Selection(sanitizedLine, endLine));
|
||||
}
|
||||
|
||||
TextEditor::UndoRecord::UndoRecord(
|
||||
const std::string &added,
|
||||
const TextEditor::Selection addedSelection,
|
||||
const std::string &removed,
|
||||
const TextEditor::Selection removedSelection,
|
||||
TextEditor::EditorState &before,
|
||||
TextEditor::EditorState &after) : m_added(added), m_addedSelection(addedSelection), m_removed(removed), m_removedSelection(removedSelection), m_before(before), m_after(after) {}
|
||||
|
||||
void TextEditor::UndoRecord::undo(TextEditor *editor) {
|
||||
if (!m_added.empty()) {
|
||||
editor->deleteRange(m_addedSelection);
|
||||
editor->colorize();
|
||||
}
|
||||
|
||||
if (!m_removed.empty()) {
|
||||
auto start = m_removedSelection.m_start;
|
||||
editor->insertTextAt(start, m_removed.c_str());
|
||||
editor->colorize();
|
||||
}
|
||||
|
||||
editor->m_state = m_before;
|
||||
editor->ensureCursorVisible();
|
||||
}
|
||||
|
||||
void TextEditor::UndoRecord::redo(TextEditor *editor) {
|
||||
if (!m_removed.empty()) {
|
||||
editor->deleteRange(m_removedSelection);
|
||||
editor->colorize();
|
||||
}
|
||||
|
||||
if (!m_added.empty()) {
|
||||
auto start = m_addedSelection.m_start;
|
||||
editor->insertTextAt(start, m_added.c_str());
|
||||
editor->colorize();
|
||||
}
|
||||
|
||||
editor->m_state = m_after;
|
||||
editor->ensureCursorVisible();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
1184
plugins/ui/source/ui/text_editor/highlighter.cpp
Normal file
1184
plugins/ui/source/ui/text_editor/highlighter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
705
plugins/ui/source/ui/text_editor/navigate.cpp
Normal file
705
plugins/ui/source/ui/text_editor/navigate.cpp
Normal file
@@ -0,0 +1,705 @@
|
||||
#include "imgui.h"
|
||||
#include <ui/text_editor.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace hex::ui {
|
||||
bool isWordChar(char c) {
|
||||
auto asUChar = static_cast<u8>(c);
|
||||
return std::isalnum(asUChar) || c == '_' || asUChar > 0x7F;
|
||||
}
|
||||
|
||||
void TextEditor::jumpToLine(i32 line) {
|
||||
auto newPos = m_state.m_cursorPosition;
|
||||
if (line != -1) {
|
||||
newPos = setCoordinates(line, 0);
|
||||
}
|
||||
jumpToCoords(newPos);
|
||||
}
|
||||
|
||||
void TextEditor::jumpToCoords(const Coordinates &coords) {
|
||||
setSelection(Selection(coords, coords));
|
||||
setCursorPosition(coords);
|
||||
ensureCursorVisible();
|
||||
|
||||
setFocusAtCoords(coords);
|
||||
}
|
||||
|
||||
void TextEditor::moveToMatchedBracket(bool select) {
|
||||
resetCursorBlinkTime();
|
||||
if (m_matchedBracket.isNearABracket(this, m_state.m_cursorPosition)) {
|
||||
m_matchedBracket.findMatchingBracket(this);
|
||||
auto oldPos = m_matchedBracket.m_nearCursor;
|
||||
auto newPos = m_matchedBracket.m_matched;
|
||||
if (newPos != setCoordinates(-1, -1)) {
|
||||
if (select) {
|
||||
if (oldPos == m_interactiveSelection.m_start)
|
||||
m_interactiveSelection.m_start = newPos;
|
||||
else if (oldPos == m_interactiveSelection.m_end)
|
||||
m_interactiveSelection.m_end = newPos;
|
||||
else {
|
||||
m_interactiveSelection = Selection(newPos, oldPos);
|
||||
}
|
||||
} else
|
||||
m_interactiveSelection.m_start = m_interactiveSelection.m_end = newPos;
|
||||
|
||||
setSelection(m_interactiveSelection);
|
||||
setCursorPosition(newPos);
|
||||
ensureCursorVisible();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::moveUp(i32 amount, bool select) {
|
||||
resetCursorBlinkTime();
|
||||
auto oldPos = m_state.m_cursorPosition;
|
||||
if (amount < 0) {
|
||||
m_scrollYIncrement = -1.0;
|
||||
setScrollY();
|
||||
return;
|
||||
}
|
||||
m_state.m_cursorPosition.m_line = std::max(0, m_state.m_cursorPosition.m_line - amount);
|
||||
if (oldPos != m_state.m_cursorPosition) {
|
||||
if (select) {
|
||||
if (oldPos == m_interactiveSelection.m_start)
|
||||
m_interactiveSelection.m_start = m_state.m_cursorPosition;
|
||||
else if (oldPos == m_interactiveSelection.m_end)
|
||||
m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
else {
|
||||
m_interactiveSelection.m_start = m_state.m_cursorPosition;
|
||||
m_interactiveSelection.m_end = oldPos;
|
||||
}
|
||||
} else
|
||||
m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
setSelection(m_interactiveSelection);
|
||||
|
||||
ensureCursorVisible();
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::moveDown(i32 amount, bool select) {
|
||||
IM_ASSERT(m_state.m_cursorPosition.m_column >= 0);
|
||||
resetCursorBlinkTime();
|
||||
auto oldPos = m_state.m_cursorPosition;
|
||||
if (amount < 0) {
|
||||
m_scrollYIncrement = 1.0;
|
||||
setScrollY();
|
||||
return;
|
||||
}
|
||||
|
||||
m_state.m_cursorPosition.m_line = std::clamp(m_state.m_cursorPosition.m_line + amount, 0, (i32) m_lines.size() - 1);
|
||||
if (oldPos.m_line == (i64) (m_lines.size() - 1)) {
|
||||
m_topLine += amount;
|
||||
m_topLine = std::clamp(m_topLine, 0.0F, m_lines.size() - 1.0F);
|
||||
setTopLine();
|
||||
ensureCursorVisible();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_state.m_cursorPosition != oldPos) {
|
||||
if (select) {
|
||||
if (oldPos == m_interactiveSelection.m_end)
|
||||
m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
else if (oldPos == m_interactiveSelection.m_start)
|
||||
m_interactiveSelection.m_start = m_state.m_cursorPosition;
|
||||
else {
|
||||
m_interactiveSelection.m_start = oldPos;
|
||||
m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
}
|
||||
} else
|
||||
m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
setSelection(m_interactiveSelection);
|
||||
|
||||
ensureCursorVisible();
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::moveLeft(i32 amount, bool select, bool wordMode) {
|
||||
resetCursorBlinkTime();
|
||||
|
||||
auto oldPos = m_state.m_cursorPosition;
|
||||
|
||||
|
||||
if (isEmpty() || oldPos.m_line >= (i64)m_lines.size())
|
||||
return;
|
||||
|
||||
auto lindex = m_state.m_cursorPosition.m_line;
|
||||
auto lineMaxColumn = getLineMaxColumn(lindex);
|
||||
auto column = std::min(m_state.m_cursorPosition.m_column, lineMaxColumn);
|
||||
|
||||
while (amount-- > 0) {
|
||||
if (column == 0) {
|
||||
if (lindex == 0)
|
||||
m_state.m_cursorPosition = Coordinates(0, 0);
|
||||
else {
|
||||
lindex--;
|
||||
m_state.m_cursorPosition = setCoordinates(lindex, -1);
|
||||
}
|
||||
} else if (wordMode)
|
||||
m_state.m_cursorPosition = findPreviousWord(m_state.m_cursorPosition);
|
||||
else
|
||||
m_state.m_cursorPosition = Coordinates(lindex, column - 1);
|
||||
}
|
||||
|
||||
if (select) {
|
||||
if (oldPos == m_interactiveSelection.m_start)
|
||||
m_interactiveSelection.m_start = m_state.m_cursorPosition;
|
||||
else if (oldPos == m_interactiveSelection.m_end)
|
||||
m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
else {
|
||||
m_interactiveSelection.m_start = m_state.m_cursorPosition;
|
||||
m_interactiveSelection.m_end = oldPos;
|
||||
}
|
||||
} else
|
||||
m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
|
||||
setSelection(m_interactiveSelection);
|
||||
|
||||
ensureCursorVisible();
|
||||
}
|
||||
|
||||
void TextEditor::moveRight(i32 amount, bool select, bool wordMode) {
|
||||
resetCursorBlinkTime();
|
||||
|
||||
auto oldPos = m_state.m_cursorPosition;
|
||||
|
||||
if (isEmpty() || oldPos.m_line >= (i64) m_lines.size())
|
||||
return;
|
||||
|
||||
auto lindex = m_state.m_cursorPosition.m_line;
|
||||
auto lineMaxColumn = getLineMaxColumn(lindex);
|
||||
auto column = std::min(m_state.m_cursorPosition.m_column, lineMaxColumn);
|
||||
|
||||
while (amount-- > 0) {
|
||||
if (isEndOfLine(oldPos)) {
|
||||
if (!isEndOfFile(oldPos)) {
|
||||
lindex++;
|
||||
m_state.m_cursorPosition = Coordinates(lindex, 0);
|
||||
} else
|
||||
m_state.m_cursorPosition = setCoordinates(-1, -1);
|
||||
} else if (wordMode)
|
||||
m_state.m_cursorPosition = findNextWord(m_state.m_cursorPosition);
|
||||
else
|
||||
m_state.m_cursorPosition = Coordinates(lindex, column + 1);
|
||||
}
|
||||
|
||||
if (select) {
|
||||
if (oldPos == m_interactiveSelection.m_end) {
|
||||
m_interactiveSelection.m_end = Coordinates(m_state.m_cursorPosition);
|
||||
if (m_interactiveSelection.m_end == Invalid)
|
||||
return;
|
||||
} else if (oldPos == m_interactiveSelection.m_start)
|
||||
m_interactiveSelection.m_start = m_state.m_cursorPosition;
|
||||
else {
|
||||
m_interactiveSelection.m_start = oldPos;
|
||||
m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
}
|
||||
} else
|
||||
m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
|
||||
setSelection(m_interactiveSelection);
|
||||
|
||||
ensureCursorVisible();
|
||||
}
|
||||
|
||||
void TextEditor::moveTop(bool select) {
|
||||
resetCursorBlinkTime();
|
||||
auto oldPos = m_state.m_cursorPosition;
|
||||
setCursorPosition(setCoordinates(0, 0));
|
||||
|
||||
if (m_state.m_cursorPosition != oldPos) {
|
||||
if (select) {
|
||||
m_interactiveSelection = Selection(m_state.m_cursorPosition, oldPos);
|
||||
} else
|
||||
m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
setSelection(m_interactiveSelection);
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::TextEditor::moveBottom(bool select) {
|
||||
resetCursorBlinkTime();
|
||||
auto oldPos = getCursorPosition();
|
||||
auto newPos = setCoordinates(-1, -1);
|
||||
setCursorPosition(newPos);
|
||||
if (select) {
|
||||
m_interactiveSelection = Selection(oldPos, newPos);
|
||||
} else
|
||||
m_interactiveSelection.m_start = m_interactiveSelection.m_end = newPos;
|
||||
setSelection(m_interactiveSelection);
|
||||
}
|
||||
|
||||
void TextEditor::moveHome(bool select) {
|
||||
resetCursorBlinkTime();
|
||||
auto oldPos = m_state.m_cursorPosition;
|
||||
|
||||
auto &line = m_lines[oldPos.m_line];
|
||||
auto prefix = line.substr(0, oldPos.m_column);
|
||||
auto postfix = line.substr(oldPos.m_column);
|
||||
if (prefix.empty() && postfix.empty())
|
||||
return;
|
||||
i32 home;
|
||||
if (!prefix.empty()) {
|
||||
auto idx = prefix.find_first_not_of(" ");
|
||||
if (idx == std::string::npos) {
|
||||
auto postIdx = postfix.find_first_of(" ");
|
||||
if (postIdx == std::string::npos || postIdx == 0)
|
||||
home = 0;
|
||||
else {
|
||||
postIdx = postfix.find_first_not_of(" ");
|
||||
if (postIdx == std::string::npos)
|
||||
home = getLineMaxColumn(oldPos.m_line);
|
||||
else if (postIdx == 0)
|
||||
home = 0;
|
||||
else
|
||||
home = oldPos.m_column + postIdx;
|
||||
}
|
||||
} else
|
||||
home = idx;
|
||||
} else {
|
||||
auto postIdx = postfix.find_first_of(" ");
|
||||
if (postIdx == std::string::npos)
|
||||
home = 0;
|
||||
else {
|
||||
postIdx = postfix.find_first_not_of(" ");
|
||||
if (postIdx == std::string::npos)
|
||||
home = getLineMaxColumn(oldPos.m_line);
|
||||
else
|
||||
home = oldPos.m_column + postIdx;
|
||||
}
|
||||
}
|
||||
|
||||
setCursorPosition(Coordinates(m_state.m_cursorPosition.m_line, home));
|
||||
if (m_state.m_cursorPosition != oldPos) {
|
||||
if (select) {
|
||||
if (oldPos == m_interactiveSelection.m_start)
|
||||
m_interactiveSelection.m_start = m_state.m_cursorPosition;
|
||||
else if (oldPos == m_interactiveSelection.m_end)
|
||||
m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
else {
|
||||
m_interactiveSelection.m_start = m_state.m_cursorPosition;
|
||||
m_interactiveSelection.m_end = oldPos;
|
||||
}
|
||||
} else
|
||||
m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
setSelection(m_interactiveSelection);
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::moveEnd(bool select) {
|
||||
resetCursorBlinkTime();
|
||||
auto oldPos = m_state.m_cursorPosition;
|
||||
setCursorPosition(setCoordinates(m_state.m_cursorPosition.m_line, getLineMaxColumn(oldPos.m_line)));
|
||||
|
||||
if (m_state.m_cursorPosition != oldPos) {
|
||||
if (select) {
|
||||
if (oldPos == m_interactiveSelection.m_end)
|
||||
m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
else if (oldPos == m_interactiveSelection.m_start)
|
||||
m_interactiveSelection.m_start = m_state.m_cursorPosition;
|
||||
else {
|
||||
m_interactiveSelection.m_start = oldPos;
|
||||
m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
}
|
||||
} else
|
||||
m_interactiveSelection.m_start = m_interactiveSelection.m_end = m_state.m_cursorPosition;
|
||||
setSelection(m_interactiveSelection);
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::setScrollY() {
|
||||
if (!m_withinRender) {
|
||||
m_setScrollY = true;
|
||||
return;
|
||||
} else {
|
||||
m_setScrollY = false;
|
||||
auto scrollY = ImGui::GetScrollY();
|
||||
ImGui::SetScrollY(std::clamp(scrollY + m_scrollYIncrement, 0.0f, ImGui::GetScrollMaxY()));
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::setCursorPosition(const Coordinates &position) {
|
||||
if (m_state.m_cursorPosition != position) {
|
||||
m_state.m_cursorPosition = position;
|
||||
ensureCursorVisible();
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::setCursorPosition() {
|
||||
setCursorPosition(m_state.m_selection.m_end);
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::setCoordinates(i32 line, i32 column) const {
|
||||
if (isEmpty())
|
||||
return Coordinates(0, 0);
|
||||
|
||||
Coordinates result = Coordinates(0, 0);
|
||||
auto lineCount = (i32) m_lines.size();
|
||||
if (line < 0 && lineCount + line >= 0)
|
||||
result.m_line = lineCount + line;
|
||||
else
|
||||
result.m_line = std::clamp(line, 0, lineCount - 1);
|
||||
|
||||
auto maxColumn = getLineMaxColumn(result.m_line) + 1;
|
||||
if (column < 0 && maxColumn + column >= 0)
|
||||
result.m_column = maxColumn + column;
|
||||
else
|
||||
result.m_column = std::clamp(column, 0, maxColumn - 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::setCoordinates(const Coordinates &value) const {
|
||||
auto sanitized_value = setCoordinates(value.m_line, value.m_column);
|
||||
return sanitized_value;
|
||||
}
|
||||
|
||||
TextEditor::Selection TextEditor::setCoordinates(const Selection &value) const {
|
||||
auto start = setCoordinates(value.m_start);
|
||||
auto end = setCoordinates(value.m_end);
|
||||
if (start == Invalid || end == Invalid)
|
||||
return Selection(Invalid, Invalid);
|
||||
if (start > end) {
|
||||
std::swap(start, end);
|
||||
}
|
||||
return Selection(start, end);
|
||||
}
|
||||
|
||||
void TextEditor::advance(Coordinates &coordinates) const {
|
||||
if (isEndOfFile(coordinates))
|
||||
return;
|
||||
if (isEndOfLine(coordinates)) {
|
||||
++coordinates.m_line;
|
||||
coordinates.m_column = 0;
|
||||
return;
|
||||
}
|
||||
auto &line = m_lines[coordinates.m_line];
|
||||
i64 column = coordinates.m_column;
|
||||
std::string lineChar = line[column];
|
||||
auto incr = getStringCharacterCount(lineChar);
|
||||
coordinates.m_column += incr;
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::findWordStart(const Coordinates &from) const {
|
||||
Coordinates at = setCoordinates(from);
|
||||
if (at.m_line >= (i32) m_lines.size())
|
||||
return at;
|
||||
|
||||
auto &line = m_lines[at.m_line];
|
||||
auto charIndex = lineCoordinatesToIndex(at);
|
||||
|
||||
if (isWordChar(line.m_chars[charIndex])) {
|
||||
while (charIndex > 0 && isWordChar(line.m_chars[charIndex - 1]))
|
||||
--charIndex;
|
||||
} else if (ispunct(line.m_chars[charIndex])) {
|
||||
while (charIndex > 0 && ispunct(line.m_chars[charIndex - 1]))
|
||||
--charIndex;
|
||||
} else if (isspace(line.m_chars[charIndex])) {
|
||||
while (charIndex > 0 && isspace(line.m_chars[charIndex - 1]))
|
||||
--charIndex;
|
||||
}
|
||||
return getCharacterCoordinates(at.m_line, charIndex);
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::findWordEnd(const Coordinates &from) const {
|
||||
Coordinates at = from;
|
||||
if (at.m_line >= (i32) m_lines.size())
|
||||
return at;
|
||||
|
||||
auto &line = m_lines[at.m_line];
|
||||
auto charIndex = lineCoordinatesToIndex(at);
|
||||
|
||||
if (isWordChar(line.m_chars[charIndex])) {
|
||||
while (charIndex < (i32) line.m_chars.size() && isWordChar(line.m_chars[charIndex]))
|
||||
++charIndex;
|
||||
} else if (ispunct(line.m_chars[charIndex])) {
|
||||
while (charIndex < (i32) line.m_chars.size() && ispunct(line.m_chars[charIndex]))
|
||||
++charIndex;
|
||||
} else if (isspace(line.m_chars[charIndex])) {
|
||||
while (charIndex < (i32) line.m_chars.size() && isspace(line.m_chars[charIndex]))
|
||||
++charIndex;
|
||||
}
|
||||
return getCharacterCoordinates(at.m_line, charIndex);
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::findNextWord(const Coordinates &from) const {
|
||||
Coordinates at = from;
|
||||
if (at.m_line >= (i32) m_lines.size())
|
||||
return at;
|
||||
|
||||
auto &line = m_lines[at.m_line];
|
||||
auto charIndex = lineCoordinatesToIndex(at);
|
||||
|
||||
if (isspace(line.m_chars[charIndex])) {
|
||||
while (charIndex < (i32) line.m_chars.size() && isspace(line.m_chars[charIndex]))
|
||||
++charIndex;
|
||||
}
|
||||
if (isWordChar(line.m_chars[charIndex])) {
|
||||
while (charIndex < (i32) line.m_chars.size() && (isWordChar(line.m_chars[charIndex])))
|
||||
++charIndex;
|
||||
} else if (ispunct(line.m_chars[charIndex])) {
|
||||
while (charIndex < (i32) line.m_chars.size() && (ispunct(line.m_chars[charIndex])))
|
||||
++charIndex;
|
||||
}
|
||||
return getCharacterCoordinates(at.m_line, charIndex);
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::findPreviousWord(const Coordinates &from) const {
|
||||
Coordinates at = from;
|
||||
if (at.m_line >= (i32) m_lines.size())
|
||||
return at;
|
||||
|
||||
auto &line = m_lines[at.m_line];
|
||||
auto charIndex = lineCoordinatesToIndex(at);
|
||||
|
||||
if (isspace(line.m_chars[charIndex - 1])) {
|
||||
while (charIndex > 0 && isspace(line.m_chars[charIndex - 1]))
|
||||
--charIndex;
|
||||
}
|
||||
if (isWordChar(line.m_chars[charIndex - 1])) {
|
||||
while (charIndex > 0 && isWordChar(line.m_chars[charIndex - 1]))
|
||||
--charIndex;
|
||||
} else if (ispunct(line.m_chars[charIndex - 1])) {
|
||||
while (charIndex > 0 && ispunct(line.m_chars[charIndex - 1]))
|
||||
--charIndex;
|
||||
}
|
||||
return getCharacterCoordinates(at.m_line, charIndex);
|
||||
}
|
||||
|
||||
u32 TextEditor::skipSpaces(const Coordinates &from) {
|
||||
auto line = from.m_line;
|
||||
if (line >= (i64) m_lines.size())
|
||||
return 0;
|
||||
auto &lines = m_lines[line].m_chars;
|
||||
auto &colors = m_lines[line].m_colors;
|
||||
auto charIndex = lineCoordinatesToIndex(from);
|
||||
u32 s = 0;
|
||||
while (charIndex < (i32) lines.size() && lines[charIndex] == ' ' && colors[charIndex] == 0x00) {
|
||||
++s;
|
||||
++charIndex;
|
||||
}
|
||||
if (m_updateFocus)
|
||||
setFocus();
|
||||
return s;
|
||||
}
|
||||
|
||||
bool TextEditor::MatchedBracket::checkPosition(TextEditor *editor, const Coordinates &from) {
|
||||
auto lineIndex = from.m_line;
|
||||
auto line = editor->m_lines[lineIndex].m_chars;
|
||||
auto colors = editor->m_lines[lineIndex].m_colors;
|
||||
if (!line.empty() && colors.empty())
|
||||
return false;
|
||||
auto result = editor->lineCoordinatesToIndex(from);
|
||||
auto character = line[result];
|
||||
auto color = colors[result];
|
||||
if ((s_separators.find(character) != std::string::npos && (static_cast<PaletteIndex>(color) == PaletteIndex::Separator)) || (static_cast<PaletteIndex>(color) == PaletteIndex::WarningText) ||
|
||||
(s_operators.find(character) != std::string::npos && (static_cast<PaletteIndex>(color) == PaletteIndex::Operator)) || (static_cast<PaletteIndex>(color) == PaletteIndex::WarningText)) {
|
||||
if (m_nearCursor != editor->getCharacterCoordinates(lineIndex, result)) {
|
||||
m_nearCursor = editor->getCharacterCoordinates(lineIndex, result);
|
||||
m_changed = true;
|
||||
}
|
||||
m_active = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
i32 TextEditor::MatchedBracket::detectDirection(TextEditor *editor, const Coordinates &from) {
|
||||
std::string brackets = "()[]{}<>";
|
||||
i32 result = -2; // dont check either
|
||||
auto start = editor->setCoordinates(from);
|
||||
if (start == TextEditor::Invalid)
|
||||
return result;
|
||||
auto lineIndex = start.m_line;
|
||||
auto line = editor->m_lines[lineIndex].m_chars;
|
||||
auto charIndex = editor->lineCoordinatesToIndex(start);
|
||||
auto ch2 = line[charIndex];
|
||||
auto idx2 = brackets.find(ch2);
|
||||
if (charIndex == 0) {// no previous character
|
||||
if (idx2 == std::string::npos) // no brackets
|
||||
return -2;// dont check either
|
||||
else
|
||||
return 1; // check only current
|
||||
}// charIndex > 0
|
||||
auto ch1 = line[charIndex - 1];
|
||||
auto idx1 = brackets.find(ch1);
|
||||
if (idx1 == std::string::npos && idx2 == std::string::npos) // no brackets
|
||||
return -2;
|
||||
if (idx1 != std::string::npos && idx2 != std::string::npos) {
|
||||
if (idx1 % 2) // closing bracket + any bracket
|
||||
return -1; // check both and previous first
|
||||
else if (!(idx1 % 2) && !(idx2 % 2)) // opening bracket + opening bracket
|
||||
return 0; // check both and current first
|
||||
} else if (idx1 != std::string::npos) // only first bracket
|
||||
return 1; // check only previous
|
||||
else // only second bracket
|
||||
return 2; // check only current
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TextEditor::MatchedBracket::isNearABracket(TextEditor *editor, const Coordinates &from) {
|
||||
if (editor->isEmpty())
|
||||
return false;
|
||||
auto start = editor->setCoordinates(from);
|
||||
if (start == TextEditor::Invalid)
|
||||
return false;
|
||||
auto lineIndex = start.m_line;
|
||||
auto charIndex = editor->lineCoordinatesToIndex(start);
|
||||
auto direction1 = detectDirection(editor, start);
|
||||
auto charCoords = editor->getCharacterCoordinates(lineIndex, charIndex);
|
||||
i32 direction2 = 1;
|
||||
if (direction1 == -1 || direction1 == 1) {
|
||||
if (checkPosition(editor, editor->setCoordinates(charCoords.m_line, charCoords.m_column - 1)))
|
||||
return true;
|
||||
if (direction1 == -1)
|
||||
direction2 = 0;
|
||||
} else if (direction1 == 2 || direction1 == 0) {
|
||||
if (checkPosition(editor, charCoords))
|
||||
return true;
|
||||
if (direction1 == 0)
|
||||
direction2 = -1;
|
||||
}
|
||||
if (direction2 != 1) {
|
||||
if (checkPosition(editor, editor->setCoordinates(charCoords.m_line, charCoords.m_column + direction2)))
|
||||
return true;
|
||||
}
|
||||
u64 result;
|
||||
auto strLine = editor->m_lines[lineIndex].m_chars;
|
||||
if (charIndex == 0) {
|
||||
if (strLine[0] == ' ') {
|
||||
result = std::string::npos;
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
} else {
|
||||
result = strLine.find_last_not_of(' ', charIndex - 1);
|
||||
}
|
||||
if (result != std::string::npos) {
|
||||
auto resultCoords = editor->getCharacterCoordinates(lineIndex, result);
|
||||
if (checkPosition(editor, resultCoords))
|
||||
return true;
|
||||
}
|
||||
result = strLine.find_first_not_of(' ', charIndex);
|
||||
if (result != std::string::npos) {
|
||||
auto resultCoords = editor->getCharacterCoordinates(lineIndex, result);
|
||||
if (checkPosition(editor, resultCoords))
|
||||
return true;
|
||||
}
|
||||
if (isActive()) {
|
||||
editor->m_lines[m_nearCursor.m_line].m_colorized = false;
|
||||
editor->m_lines[m_matched.m_line].m_colorized = false;
|
||||
m_active = false;
|
||||
editor->colorize();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TextEditor::MatchedBracket::findMatchingBracket(TextEditor *editor) {
|
||||
auto from = editor->setCoordinates(m_nearCursor);
|
||||
if (from == TextEditor::Invalid) {
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
m_matched = from;
|
||||
auto lineIndex = from.m_line;
|
||||
auto maxLineIndex = editor->m_lines.size() - 1;
|
||||
auto charIndex = editor->lineCoordinatesToIndex(from);
|
||||
std::string line = editor->m_lines[lineIndex].m_chars;
|
||||
std::string colors = editor->m_lines[lineIndex].m_colors;
|
||||
if (!line.empty() && colors.empty()) {
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
std::string brackets = "()[]{}<>";
|
||||
char bracketChar = line[charIndex];
|
||||
char color1;
|
||||
auto idx = brackets.find_first_of(bracketChar);
|
||||
if (idx == std::string::npos) {
|
||||
if (m_active) {
|
||||
m_active = false;
|
||||
editor->colorize();
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto bracketChar2 = brackets[idx ^ 1];
|
||||
brackets = bracketChar;
|
||||
brackets += bracketChar2;
|
||||
i32 direction = 1 - 2 * (idx % 2);
|
||||
if (idx > 5)
|
||||
color1 = static_cast<char>(PaletteIndex::Operator);
|
||||
else
|
||||
color1 = static_cast<char>(PaletteIndex::Separator);
|
||||
char color = static_cast<char>(PaletteIndex::WarningText);
|
||||
i32 depth = 1;
|
||||
if (charIndex == (i64) (line.size() - 1) * (1 + direction) / 2) {
|
||||
if (lineIndex == (i64) maxLineIndex * (1 + direction) / 2) {
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
lineIndex += direction;
|
||||
line = editor->m_lines[lineIndex].m_chars;
|
||||
colors = editor->m_lines[lineIndex].m_colors;
|
||||
if (!line.empty() && colors.empty()) {
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
charIndex = (line.size() - 1) * (1 - direction) / 2 - direction;
|
||||
}
|
||||
for (i32 i = charIndex + direction;; i += direction) {
|
||||
if (direction == 1)
|
||||
idx = line.find_first_of(brackets, i);
|
||||
else
|
||||
idx = line.find_last_of(brackets, i);
|
||||
if (idx != std::string::npos) {
|
||||
if (line[idx] == bracketChar && (colors[idx] == color || colors[idx] == color1)) {
|
||||
++depth;
|
||||
i = idx;
|
||||
} else if ((line[idx] == bracketChar2 && (colors[idx] == color)) || colors[idx] == color1) {
|
||||
--depth;
|
||||
if (depth == 0) {
|
||||
if (m_matched != editor->getCharacterCoordinates(lineIndex, idx)) {
|
||||
m_matched = editor->getCharacterCoordinates(lineIndex, idx);
|
||||
m_changed = true;
|
||||
}
|
||||
m_active = true;
|
||||
break;
|
||||
}
|
||||
i = idx;
|
||||
} else {
|
||||
i = idx;
|
||||
}
|
||||
} else {
|
||||
if (direction == 1)
|
||||
i = line.size() - 1;
|
||||
else
|
||||
i = 0;
|
||||
}
|
||||
if ((i32) (direction * i) >= (i32) ((line.size() - 1) * (1 + direction) / 2)) {
|
||||
if (lineIndex == (i64) maxLineIndex * (1 + direction) / 2) {
|
||||
if (m_active) {
|
||||
m_active = false;
|
||||
m_changed = true;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
lineIndex += direction;
|
||||
line = editor->m_lines[lineIndex].m_chars;
|
||||
colors = editor->m_lines[lineIndex].m_colors;
|
||||
if (!line.empty() && colors.empty()) {
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
i = (line.size() - 1) * (1 - direction) / 2 - direction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanged()) {
|
||||
editor->m_lines[m_nearCursor.m_line].m_colorized = false;
|
||||
editor->m_lines[m_matched.m_line].m_colorized = false;
|
||||
|
||||
editor->colorize();
|
||||
m_changed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
579
plugins/ui/source/ui/text_editor/render.cpp
Normal file
579
plugins/ui/source/ui/text_editor/render.cpp
Normal file
@@ -0,0 +1,579 @@
|
||||
#include "imgui.h"
|
||||
#include <ui/text_editor.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace hex::ui {
|
||||
TextEditor::Palette s_paletteBase = TextEditor::getDarkPalette();
|
||||
|
||||
inline void TextUnformattedColoredAt(const ImVec2 &pos, const ImU32 &color, const char *text) {
|
||||
ImGui::SetCursorScreenPos(pos);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
ImGui::TextUnformatted(text);
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
void TextEditor::setTopMarginChanged(i32 newMargin) {
|
||||
m_newTopMargin = newMargin;
|
||||
m_topMarginChanged = true;
|
||||
}
|
||||
|
||||
void TextEditor::setFocusAtCoords(const Coordinates &coords) {
|
||||
m_focusAtCoords = coords;
|
||||
m_updateFocus = true;
|
||||
}
|
||||
|
||||
void TextEditor::clearErrorMarkers() {
|
||||
m_errorMarkers.clear();
|
||||
m_errorHoverBoxes.clear();
|
||||
}
|
||||
|
||||
void TextEditor::clearActionables() {
|
||||
clearErrorMarkers();
|
||||
clearGotoBoxes();
|
||||
clearCursorBoxes();
|
||||
}
|
||||
|
||||
ImVec2 TextEditor::underwaves(ImVec2 pos, u32 nChars, ImColor color, const ImVec2 &size_arg) {
|
||||
ImGui::GetStyle().AntiAliasedLines = false;
|
||||
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
||||
window->DC.CursorPos = pos;
|
||||
const ImVec2 label_size = ImGui::CalcTextSize("W", nullptr, true);
|
||||
ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x, label_size.y);
|
||||
float lineWidth = size.x / 3.0f + 0.5f;
|
||||
float halfLineW = lineWidth / 2.0f;
|
||||
|
||||
for (u32 i = 0; i < nChars; i++) {
|
||||
pos = window->DC.CursorPos;
|
||||
float lineY = pos.y + size.y;
|
||||
|
||||
ImVec2 pos1_1 = ImVec2(pos.x + 0 * lineWidth, lineY + halfLineW);
|
||||
ImVec2 pos1_2 = ImVec2(pos.x + 1 * lineWidth, lineY - halfLineW);
|
||||
ImVec2 pos2_1 = ImVec2(pos.x + 2 * lineWidth, lineY + halfLineW);
|
||||
ImVec2 pos2_2 = ImVec2(pos.x + 3 * lineWidth, lineY - halfLineW);
|
||||
|
||||
ImGui::GetWindowDrawList()->AddLine(pos1_1, pos1_2, ImU32(color), 0.4f);
|
||||
ImGui::GetWindowDrawList()->AddLine(pos1_2, pos2_1, ImU32(color), 0.4f);
|
||||
ImGui::GetWindowDrawList()->AddLine(pos2_1, pos2_2, ImU32(color), 0.4f);
|
||||
|
||||
window->DC.CursorPos = ImVec2(pos.x + size.x, pos.y);
|
||||
}
|
||||
auto ret = window->DC.CursorPos;
|
||||
ret.y += size.y;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void TextEditor::setTabSize(i32 value) {
|
||||
m_tabSize = std::max(0, std::min(32, value));
|
||||
}
|
||||
|
||||
float TextEditor::getPageSize() const {
|
||||
auto height = ImGui::GetCurrentWindow()->InnerClipRect.GetHeight();
|
||||
return height / m_charAdvance.y;
|
||||
}
|
||||
|
||||
bool TextEditor::isEndOfLine() const {
|
||||
return isEndOfLine(m_state.m_cursorPosition);
|
||||
}
|
||||
|
||||
bool TextEditor::isStartOfLine() const {
|
||||
return m_state.m_cursorPosition.m_column == 0;
|
||||
}
|
||||
|
||||
bool TextEditor::isEndOfLine(const Coordinates &coordinates) const {
|
||||
if (coordinates.m_line < (i32) m_lines.size())
|
||||
return coordinates.m_column >= getStringCharacterCount(m_lines[coordinates.m_line].m_chars);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextEditor::isEndOfFile(const Coordinates &coordinates) const {
|
||||
if (coordinates.m_line < (i32) m_lines.size())
|
||||
return coordinates.m_line >= (i32) m_lines.size() - 1 && isEndOfLine(coordinates);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextEditor::setTopLine() {
|
||||
if (!m_withinRender) {
|
||||
m_setTopLine = true;
|
||||
return;
|
||||
} else {
|
||||
m_setTopLine = false;
|
||||
ImGui::SetScrollY(m_topLine * m_charAdvance.y);
|
||||
ensureCursorVisible();
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::render(const char *title, const ImVec2 &size, bool border) {
|
||||
m_withinRender = true;
|
||||
|
||||
if (m_lines.capacity() < 2 * m_lines.size())
|
||||
m_lines.reserve(2 * m_lines.size());
|
||||
|
||||
auto scrollBg = ImGui::GetStyleColorVec4(ImGuiCol_ScrollbarBg);
|
||||
scrollBg.w = 0.0f;
|
||||
auto scrollBarSize = ImGui::GetStyle().ScrollbarSize;
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(m_palette[(i32) PaletteIndex::Background]));
|
||||
ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImGui::ColorConvertFloat4ToU32(scrollBg));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarRounding, 0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, scrollBarSize);
|
||||
|
||||
auto position = ImGui::GetCursorScreenPos();
|
||||
if (m_showLineNumbers) {
|
||||
std::string lineNumber = " " + std::to_string(m_lines.size()) + " ";
|
||||
m_lineNumberFieldWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, lineNumber.c_str(), nullptr, nullptr).x + m_leftMargin;
|
||||
ImGui::SetNextWindowPos(position);
|
||||
ImGui::SetCursorScreenPos(position);
|
||||
auto lineNoSize = ImVec2(m_lineNumberFieldWidth, size.y);
|
||||
if (!m_ignoreImGuiChild) {
|
||||
ImGui::BeginChild("##lineNumbers", lineNoSize, false, ImGuiWindowFlags_NoScrollbar);
|
||||
ImGui::EndChild();
|
||||
}
|
||||
} else {
|
||||
m_lineNumberFieldWidth = 0;
|
||||
}
|
||||
|
||||
ImVec2 textEditorSize = size;
|
||||
textEditorSize.x -= m_lineNumberFieldWidth;
|
||||
|
||||
bool scroll_x = m_longestLineLength * m_charAdvance.x >= textEditorSize.x;
|
||||
|
||||
bool scroll_y = m_lines.size() > 1;
|
||||
if (!border)
|
||||
textEditorSize.x -= scrollBarSize;
|
||||
ImGui::SetCursorScreenPos(ImVec2(position.x + m_lineNumberFieldWidth, position.y));
|
||||
ImGuiChildFlags childFlags = border ? ImGuiChildFlags_Borders : ImGuiChildFlags_None;
|
||||
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove;
|
||||
if (!m_ignoreImGuiChild)
|
||||
ImGui::BeginChild(title, textEditorSize, childFlags, windowFlags);
|
||||
auto window = ImGui::GetCurrentWindow();
|
||||
window->ScrollbarSizes = ImVec2(scrollBarSize * scroll_x, scrollBarSize * scroll_y);
|
||||
ImGui::GetCurrentWindowRead()->ScrollbarSizes = ImVec2(scrollBarSize * scroll_y, scrollBarSize * scroll_x);
|
||||
if (scroll_y) {
|
||||
ImGui::GetCurrentWindow()->ScrollbarY = true;
|
||||
ImGui::Scrollbar(ImGuiAxis_Y);
|
||||
ImGui::GetCurrentWindow()->ScrollbarY = false;
|
||||
}
|
||||
if (scroll_x) {
|
||||
ImGui::GetCurrentWindow()->ScrollbarX = true;
|
||||
ImGui::Scrollbar(ImGuiAxis_X);
|
||||
ImGui::GetCurrentWindow()->ScrollbarX = false;
|
||||
}
|
||||
|
||||
if (m_handleKeyboardInputs) {
|
||||
handleKeyboardInputs();
|
||||
}
|
||||
|
||||
if (m_handleMouseInputs)
|
||||
handleMouseInputs();
|
||||
|
||||
|
||||
colorizeInternal();
|
||||
renderText(title, position, textEditorSize);
|
||||
|
||||
if (!m_ignoreImGuiChild)
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::PopStyleVar(3);
|
||||
ImGui::PopStyleColor(2);
|
||||
|
||||
m_withinRender = false;
|
||||
ImGui::SetCursorScreenPos(ImVec2(position.x, position.y + size.y - 1));
|
||||
ImGui::Dummy({});
|
||||
}
|
||||
|
||||
void TextEditor::ensureCursorVisible() {
|
||||
if (!m_withinRender) {
|
||||
m_scrollToCursor = true;
|
||||
return;
|
||||
}
|
||||
|
||||
auto scrollBarSize = ImGui::GetStyle().ScrollbarSize;
|
||||
float scrollX = ImGui::GetScrollX();
|
||||
float scrollY = ImGui::GetScrollY();
|
||||
|
||||
auto windowPadding = ImGui::GetStyle().FramePadding * 2.0f;
|
||||
|
||||
auto height = ImGui::GetWindowHeight() - m_topMargin - scrollBarSize - m_charAdvance.y;
|
||||
auto width = ImGui::GetWindowWidth() - windowPadding.x - scrollBarSize;
|
||||
|
||||
auto top = (i32) rint((m_topMargin > scrollY ? m_topMargin - scrollY : scrollY) / m_charAdvance.y);
|
||||
auto bottom = top + (i32) rint(height / m_charAdvance.y);
|
||||
|
||||
auto left = (i32) rint(scrollX / m_charAdvance.x);
|
||||
auto right = left + (i32) rint(width / m_charAdvance.x);
|
||||
|
||||
auto pos = setCoordinates(m_state.m_cursorPosition);
|
||||
pos.m_column = (i32) rint(textDistanceToLineStart(pos) / m_charAdvance.x);
|
||||
|
||||
bool mScrollToCursorX = true;
|
||||
bool mScrollToCursorY = true;
|
||||
|
||||
if (pos.m_line >= top && pos.m_line <= bottom)
|
||||
mScrollToCursorY = false;
|
||||
if ((pos.m_column >= left) && (pos.m_column <= right))
|
||||
mScrollToCursorX = false;
|
||||
if (!mScrollToCursorX && !mScrollToCursorY && m_oldTopMargin == m_topMargin) {
|
||||
m_scrollToCursor = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mScrollToCursorY) {
|
||||
if (pos.m_line < top)
|
||||
ImGui::SetScrollY(std::max(0.0f, pos.m_line * m_charAdvance.y));
|
||||
if (pos.m_line > bottom)
|
||||
ImGui::SetScrollY(std::max(0.0f, pos.m_line * m_charAdvance.y - height));
|
||||
}
|
||||
if (mScrollToCursorX) {
|
||||
if (pos.m_column < left)
|
||||
ImGui::SetScrollX(std::max(0.0f, pos.m_column * m_charAdvance.x));
|
||||
if (pos.m_column > right)
|
||||
ImGui::SetScrollX(std::max(0.0f, pos.m_column * m_charAdvance.x - width));
|
||||
}
|
||||
m_oldTopMargin = m_topMargin;
|
||||
}
|
||||
|
||||
void TextEditor::resetCursorBlinkTime() {
|
||||
m_startTime = ImGui::GetTime() * 1000 - s_cursorBlinkOnTime;
|
||||
}
|
||||
|
||||
void TextEditor::renderText(const char *title, const ImVec2 &lineNumbersStartPos, const ImVec2 &textEditorSize) {
|
||||
|
||||
preRender();
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
s_cursorScreenPosition = ImGui::GetCursorScreenPos();
|
||||
ImVec2 position = lineNumbersStartPos;
|
||||
if (m_setScrollY)
|
||||
setScrollY();
|
||||
auto scrollY = ImGui::GetScrollY();
|
||||
if (m_setTopLine)
|
||||
setTopLine();
|
||||
else
|
||||
m_topLine = std::max<float>(0.0F, (scrollY - m_topMargin) / m_charAdvance.y);
|
||||
auto lineNo = m_topLine;
|
||||
|
||||
auto lineMax = std::clamp(lineNo + m_numberOfLinesDisplayed, 0.0F, m_lines.size() - 1.0F);
|
||||
|
||||
if (!m_lines.empty()) {
|
||||
bool focused = ImGui::IsWindowFocused();
|
||||
|
||||
while (lineNo <= lineMax) {
|
||||
|
||||
drawSelection(lineNo);
|
||||
|
||||
if (!m_ignoreImGuiChild)
|
||||
ImGui::EndChild();
|
||||
|
||||
if (m_showLineNumbers)
|
||||
drawLineNumbers(position, lineNo, textEditorSize, focused, drawList);
|
||||
|
||||
if (!m_ignoreImGuiChild)
|
||||
ImGui::BeginChild(title);
|
||||
|
||||
if (m_state.m_cursorPosition.m_line == lineNo && m_showCursor && focused)
|
||||
renderCursor(lineNo, drawList);
|
||||
|
||||
renderGotoButtons(lineNo);
|
||||
|
||||
// Render colorized text
|
||||
|
||||
auto &line = m_lines[lineNo];
|
||||
if (line.empty()) {
|
||||
ImGui::Dummy(m_charAdvance);
|
||||
lineNo = std::floor(lineNo + 1.0F);
|
||||
if (m_updateFocus)
|
||||
setFocus();
|
||||
continue;
|
||||
}
|
||||
auto colors = m_lines[lineNo].m_colors;
|
||||
u64 colorsSize = colors.size();
|
||||
u64 i = skipSpaces(setCoordinates(lineNo, 0));
|
||||
while (i < colorsSize) {
|
||||
char color = std::clamp(colors[i], (char) PaletteIndex::Default, (char) ((u8) PaletteIndex::Max - 1));
|
||||
u32 tokenLength = std::clamp((u64) (colors.find_first_not_of(color, i) - i),(u64) 1, colorsSize - i);
|
||||
if (m_updateFocus)
|
||||
setFocus();
|
||||
auto lineStart = setCoordinates(lineNo, i);
|
||||
|
||||
drawText(lineStart, i, tokenLength, color);
|
||||
|
||||
i += (tokenLength + skipSpaces(lineStart));
|
||||
}
|
||||
|
||||
lineNo = std::floor(lineNo + 1.0F);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_scrollToCursor)
|
||||
ensureCursorVisible();
|
||||
|
||||
postRender(title, position, lineNo);
|
||||
|
||||
}
|
||||
|
||||
void TextEditor::setFocus() {
|
||||
m_state.m_cursorPosition = m_focusAtCoords;
|
||||
resetCursorBlinkTime();
|
||||
ensureCursorVisible();
|
||||
if (!this->m_readOnly)
|
||||
ImGui::SetKeyboardFocusHere(0);
|
||||
m_updateFocus = false;
|
||||
}
|
||||
|
||||
void TextEditor::preRender() {
|
||||
m_charAdvance = calculateCharAdvance();
|
||||
|
||||
/* Update palette with the current alpha from style */
|
||||
for (i32 i = 0; i < (i32) PaletteIndex::Max; ++i) {
|
||||
auto color = ImGui::ColorConvertU32ToFloat4(s_paletteBase[i]);
|
||||
color.w *= ImGui::GetStyle().Alpha;
|
||||
m_palette[i] = ImGui::ColorConvertFloat4ToU32(color);
|
||||
}
|
||||
|
||||
m_numberOfLinesDisplayed = getPageSize();
|
||||
|
||||
if (m_scrollToTop) {
|
||||
m_scrollToTop = false;
|
||||
ImGui::SetScrollY(0.f);
|
||||
}
|
||||
|
||||
if (m_scrollToBottom && ImGui::GetScrollMaxY() >= ImGui::GetScrollY()) {
|
||||
m_scrollToBottom = false;
|
||||
ImGui::SetScrollY(ImGui::GetScrollMaxY());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TextEditor::drawSelection(float lineNo) {
|
||||
ImVec2 lineStartScreenPos = s_cursorScreenPosition + ImVec2(m_leftMargin, m_topMargin + std::floor(lineNo) * m_charAdvance.y);
|
||||
Selection lineCoords = Selection(setCoordinates(lineNo, 0), setCoordinates(lineNo, -1));
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
|
||||
if (m_state.m_selection.m_start <= lineCoords.m_end && m_state.m_selection.m_end > lineCoords.m_start) {
|
||||
float selectionStart = textDistanceToLineStart(std::max(m_state.m_selection.m_start, lineCoords.m_start));
|
||||
float selectionEnd = textDistanceToLineStart(std::min(m_state.m_selection.m_end, lineCoords.m_end)) + m_charAdvance.x * (m_state.m_selection.m_end.m_line > lineNo);
|
||||
|
||||
if (selectionStart < selectionEnd) {
|
||||
ImVec2 rectStart(lineStartScreenPos.x + selectionStart, lineStartScreenPos.y);
|
||||
ImVec2 rectEnd(lineStartScreenPos.x + selectionEnd, lineStartScreenPos.y + m_charAdvance.y);
|
||||
drawList->AddRectFilled(rectStart, rectEnd, m_palette[(i32) PaletteIndex::Selection]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::drawLineNumbers(ImVec2 position, float lineNo, const ImVec2 &contentSize, bool focused, ImDrawList *drawList) {
|
||||
ImVec2 lineStartScreenPos = s_cursorScreenPosition + ImVec2(m_leftMargin, m_topMargin + std::floor(lineNo) * m_charAdvance.y);
|
||||
ImVec2 lineNoStartScreenPos = ImVec2(position.x, m_topMargin + s_cursorScreenPosition.y + std::floor(lineNo) * m_charAdvance.y);
|
||||
auto start = ImVec2(lineNoStartScreenPos.x + m_lineNumberFieldWidth, lineStartScreenPos.y);
|
||||
i32 totalDigitCount = std::floor(std::log10(m_lines.size())) + 1;
|
||||
ImGui::SetCursorScreenPos(position);
|
||||
if (!m_ignoreImGuiChild)
|
||||
ImGui::BeginChild("##lineNumbers");
|
||||
|
||||
i32 padding = totalDigitCount - std::floor(std::log10(lineNo + 1)) - 1;
|
||||
std::string space = std::string(padding, ' ');
|
||||
std::string lineNoStr = space + std::to_string((i32) (lineNo + 1));
|
||||
ImGui::SetCursorScreenPos(ImVec2(position.x, lineStartScreenPos.y));
|
||||
if (ImGui::InvisibleButton(lineNoStr.c_str(), ImVec2(m_lineNumberFieldWidth, m_charAdvance.y))) {
|
||||
if (m_breakpoints.contains(lineNo + 1))
|
||||
m_breakpoints.erase(lineNo + 1);
|
||||
else
|
||||
m_breakpoints.insert(lineNo + 1);
|
||||
m_breakPointsChanged = true;
|
||||
auto cursorPosition = setCoordinates(lineNo, 0);
|
||||
if (cursorPosition == Invalid)
|
||||
return;
|
||||
|
||||
m_state.m_cursorPosition = cursorPosition;
|
||||
|
||||
jumpToCoords(m_state.m_cursorPosition);
|
||||
}
|
||||
// Draw breakpoints
|
||||
if (m_breakpoints.count(lineNo + 1) != 0) {
|
||||
auto end = ImVec2(lineNoStartScreenPos.x + contentSize.x + m_lineNumberFieldWidth, lineStartScreenPos.y + m_charAdvance.y);
|
||||
drawList->AddRectFilled(ImVec2(position.x, lineStartScreenPos.y), end, m_palette[(i32) PaletteIndex::Breakpoint]);
|
||||
|
||||
drawList->AddCircleFilled(start + ImVec2(0, m_charAdvance.y) / 2, m_charAdvance.y / 3, m_palette[(i32) PaletteIndex::Breakpoint]);
|
||||
drawList->AddCircle(start + ImVec2(0, m_charAdvance.y) / 2, m_charAdvance.y / 3, m_palette[(i32) PaletteIndex::Default]);
|
||||
drawList->AddText(ImVec2(lineNoStartScreenPos.x + m_leftMargin, lineStartScreenPos.y), m_palette[(i32) PaletteIndex::LineNumber], lineNoStr.c_str());
|
||||
}
|
||||
|
||||
if (m_state.m_cursorPosition.m_line == lineNo && m_showCursor) {
|
||||
|
||||
// Highlight the current line (where the cursor is)
|
||||
if (!hasSelection()) {
|
||||
auto end = ImVec2(lineNoStartScreenPos.x + contentSize.x + m_lineNumberFieldWidth, lineStartScreenPos.y + m_charAdvance.y);
|
||||
drawList->AddRectFilled(ImVec2(position.x, lineStartScreenPos.y), end, m_palette[(i32) (focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]);
|
||||
drawList->AddRect(ImVec2(position.x, lineStartScreenPos.y), end, m_palette[(i32) PaletteIndex::CurrentLineEdge], 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
TextUnformattedColoredAt(ImVec2(m_leftMargin + lineNoStartScreenPos.x, lineStartScreenPos.y), m_palette[(i32) PaletteIndex::LineNumber], lineNoStr.c_str());
|
||||
|
||||
if (!m_ignoreImGuiChild)
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void TextEditor::renderCursor(float lineNo, ImDrawList *drawList) {
|
||||
ImVec2 lineStartScreenPos = s_cursorScreenPosition + ImVec2(m_leftMargin, m_topMargin + std::floor(lineNo) * m_charAdvance.y);
|
||||
auto timeEnd = ImGui::GetTime() * 1000;
|
||||
auto elapsed = timeEnd - m_startTime;
|
||||
if (elapsed > s_cursorBlinkOnTime) {
|
||||
float width = 1.0f;
|
||||
u64 charIndex = lineCoordinatesToIndex(m_state.m_cursorPosition);
|
||||
float cx = textDistanceToLineStart(m_state.m_cursorPosition);
|
||||
auto &line = m_lines[std::floor(lineNo)];
|
||||
if (m_overwrite && charIndex < line.size()) {
|
||||
char c = line[charIndex];
|
||||
std::string s(1, c);
|
||||
width = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, s.c_str()).x;
|
||||
}
|
||||
ImVec2 rectStart(lineStartScreenPos.x + cx, lineStartScreenPos.y);
|
||||
ImVec2 rectEnd(lineStartScreenPos.x + cx + width, lineStartScreenPos.y + m_charAdvance.y);
|
||||
drawList->AddRectFilled(rectStart, rectEnd, m_palette[(i32) PaletteIndex::Cursor]);
|
||||
if (elapsed > s_cursorBlinkInterval)
|
||||
m_startTime = timeEnd;
|
||||
if (m_matchedBracket.isNearABracket(this, m_state.m_cursorPosition))
|
||||
m_matchedBracket.findMatchingBracket(this);
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::renderGotoButtons(float lineNo) {
|
||||
ImVec2 lineStartScreenPos = s_cursorScreenPosition + ImVec2(m_leftMargin, m_topMargin + std::floor(lineNo) * m_charAdvance.y);
|
||||
auto lineText = getLineText(lineNo);
|
||||
Coordinates gotoKey = setCoordinates(lineNo + 1, 0);
|
||||
if (gotoKey != Invalid) {
|
||||
std::string errorLineColumn;
|
||||
bool found = false;
|
||||
for (auto text: m_clickableText) {
|
||||
if (lineText.find(text) == 0) {
|
||||
errorLineColumn = lineText.substr(text.size());
|
||||
if (!errorLineColumn.empty()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
i32 currLine = 0, currColumn = 0;
|
||||
if (auto idx = errorLineColumn.find(":"); idx != std::string::npos) {
|
||||
auto errorLine = errorLineColumn.substr(0, idx);
|
||||
if (!errorLine.empty())
|
||||
currLine = std::stoi(errorLine) - 1;
|
||||
auto errorColumn = errorLineColumn.substr(idx + 1);
|
||||
if (!errorColumn.empty())
|
||||
currColumn = std::stoi(errorColumn) - 1;
|
||||
}
|
||||
TextEditor::Coordinates errorPos = GetSourceCodeEditor()->setCoordinates(currLine, currColumn);
|
||||
if (errorPos != Invalid) {
|
||||
ImVec2 errorStart = ImVec2(lineStartScreenPos.x, lineStartScreenPos.y);
|
||||
auto lineEnd = setCoordinates(lineNo, -1);
|
||||
if (lineEnd != Invalid) {
|
||||
ImVec2 errorEnd = ImVec2(lineStartScreenPos.x + textDistanceToLineStart(lineEnd), lineStartScreenPos.y + m_charAdvance.y);
|
||||
ErrorGotoBox box = ErrorGotoBox(ImRect({errorStart, errorEnd}), errorPos, GetSourceCodeEditor());
|
||||
m_errorGotoBoxes[gotoKey] = box;
|
||||
CursorChangeBox cursorBox = CursorChangeBox(ImRect({errorStart, errorEnd}));
|
||||
m_cursorBoxes[gotoKey] = cursorBox;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_cursorBoxes.find(gotoKey) != m_cursorBoxes.end()) {
|
||||
auto box = m_cursorBoxes[gotoKey];
|
||||
if (box.trigger()) box.callback();
|
||||
}
|
||||
|
||||
if (m_errorGotoBoxes.find(gotoKey) != m_errorGotoBoxes.end()) {
|
||||
auto box = m_errorGotoBoxes[gotoKey];
|
||||
if (box.trigger()) box.callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::drawText(Coordinates &lineStart, u64 i, u32 tokenLength, char color) {
|
||||
auto &line = m_lines[lineStart.m_line];
|
||||
ImVec2 lineStartScreenPos = s_cursorScreenPosition + ImVec2(m_leftMargin, m_topMargin + std::floor(lineStart.m_line) * m_charAdvance.y);
|
||||
auto textStart = textDistanceToLineStart(lineStart);
|
||||
auto begin = lineStartScreenPos + ImVec2(textStart, 0);
|
||||
|
||||
TextUnformattedColoredAt(begin, m_palette[(i32) color], line.substr(i, tokenLength).c_str());
|
||||
|
||||
ErrorMarkers::iterator errorIt;
|
||||
auto key = lineStart + Coordinates(1, 1);
|
||||
if (errorIt = m_errorMarkers.find(key); errorIt != m_errorMarkers.end()) {
|
||||
auto errorMessage = errorIt->second.second;
|
||||
auto errorLength = errorIt->second.first;
|
||||
if (errorLength == 0)
|
||||
errorLength = line.size() - i - 1;
|
||||
|
||||
auto end = underwaves(begin, errorLength, m_palette[(i32) PaletteIndex::ErrorMarker]);
|
||||
ErrorHoverBox box = ErrorHoverBox(ImRect({begin, end}), key, errorMessage.c_str());
|
||||
m_errorHoverBoxes[key] = box;
|
||||
}
|
||||
if (m_errorHoverBoxes.find(key) != m_errorHoverBoxes.end()) {
|
||||
auto box = m_errorHoverBoxes[key];
|
||||
if (box.trigger()) box.callback();
|
||||
}
|
||||
lineStart = lineStart + Coordinates(0, tokenLength);
|
||||
}
|
||||
|
||||
void TextEditor::postRender(const char *title, ImVec2 position, float lineNo) {
|
||||
ImVec2 lineStartScreenPos = ImVec2(s_cursorScreenPosition.x + m_leftMargin, m_topMargin + s_cursorScreenPosition.y + std::floor(lineNo) * m_charAdvance.y);
|
||||
float globalLineMax = m_lines.size();
|
||||
auto lineMax = std::clamp(lineNo + m_numberOfLinesDisplayed, 0.0F, globalLineMax - 1.0F);
|
||||
if (!m_ignoreImGuiChild)
|
||||
ImGui::EndChild();
|
||||
|
||||
if (m_showLineNumbers && !m_ignoreImGuiChild) {
|
||||
ImGui::BeginChild("##lineNumbers");
|
||||
ImGui::SetCursorScreenPos(ImVec2(position.x, lineStartScreenPos.y));
|
||||
ImGui::Dummy(ImVec2(m_lineNumberFieldWidth, (globalLineMax - lineMax - 1) * m_charAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight() - m_charAdvance.y));
|
||||
ImGui::EndChild();
|
||||
}
|
||||
if (!m_ignoreImGuiChild)
|
||||
ImGui::BeginChild(title);
|
||||
|
||||
ImGui::SetCursorScreenPos(lineStartScreenPos);
|
||||
if (m_showLineNumbers)
|
||||
ImGui::Dummy(ImVec2(m_longestLineLength * m_charAdvance.x + m_charAdvance.x, (globalLineMax - lineMax - 2.0F) * m_charAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight()));
|
||||
else
|
||||
ImGui::Dummy(ImVec2(m_longestLineLength * m_charAdvance.x + m_charAdvance.x, (globalLineMax - lineMax - 3.0F) * m_charAdvance.y + ImGui::GetCurrentWindow()->InnerClipRect.GetHeight() - 1.0f));
|
||||
|
||||
|
||||
if (m_topMarginChanged) {
|
||||
m_topMarginChanged = false;
|
||||
auto window = ImGui::GetCurrentWindow();
|
||||
auto maxScroll = window->ScrollMax.y;
|
||||
if (maxScroll > 0) {
|
||||
float pixelCount;
|
||||
if (m_newTopMargin > m_topMargin) {
|
||||
pixelCount = m_newTopMargin - m_topMargin;
|
||||
} else if (m_newTopMargin > 0) {
|
||||
pixelCount = m_topMargin - m_newTopMargin;
|
||||
} else {
|
||||
pixelCount = m_topMargin;
|
||||
}
|
||||
auto oldScrollY = ImGui::GetScrollY();
|
||||
|
||||
if (m_newTopMargin > m_topMargin)
|
||||
m_shiftedScrollY = oldScrollY + pixelCount;
|
||||
else
|
||||
m_shiftedScrollY = oldScrollY - pixelCount;
|
||||
ImGui::SetScrollY(m_shiftedScrollY);
|
||||
m_topMargin = m_newTopMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImVec2 TextEditor::calculateCharAdvance() const {
|
||||
/* Compute mCharAdvance regarding scaled font size (Ctrl + mouse wheel)*/
|
||||
const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x;
|
||||
return ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * m_lineSpacing);
|
||||
}
|
||||
|
||||
float TextEditor::textDistanceToLineStart(const Coordinates &aFrom) const {
|
||||
auto &line = m_lines[aFrom.m_line];
|
||||
i32 colIndex = lineCoordinatesToIndex(aFrom);
|
||||
auto substr = line.m_chars.substr(0, colIndex);
|
||||
return ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, substr.c_str(), nullptr, nullptr).x;
|
||||
}
|
||||
}
|
||||
841
plugins/ui/source/ui/text_editor/support.cpp
Normal file
841
plugins/ui/source/ui/text_editor/support.cpp
Normal file
@@ -0,0 +1,841 @@
|
||||
#include <ui/text_editor.hpp>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
namespace hex::ui {
|
||||
bool TextEditor::Coordinates::operator==(const Coordinates &o) const {
|
||||
return
|
||||
m_line == o.m_line &&
|
||||
m_column == o.m_column;
|
||||
}
|
||||
|
||||
bool TextEditor::Coordinates::operator!=(const Coordinates &o) const {
|
||||
return
|
||||
m_line != o.m_line ||
|
||||
m_column != o.m_column;
|
||||
}
|
||||
|
||||
bool TextEditor::Coordinates::operator<(const Coordinates &o) const {
|
||||
if (m_line != o.m_line)
|
||||
return m_line < o.m_line;
|
||||
return m_column < o.m_column;
|
||||
}
|
||||
|
||||
bool TextEditor::Coordinates::operator>(const Coordinates &o) const {
|
||||
if (m_line != o.m_line)
|
||||
return m_line > o.m_line;
|
||||
return m_column > o.m_column;
|
||||
}
|
||||
|
||||
bool TextEditor::Coordinates::operator<=(const Coordinates &o) const {
|
||||
if (m_line != o.m_line)
|
||||
return m_line < o.m_line;
|
||||
return m_column <= o.m_column;
|
||||
}
|
||||
|
||||
bool TextEditor::Coordinates::operator>=(const Coordinates &o) const {
|
||||
if (m_line != o.m_line)
|
||||
return m_line > o.m_line;
|
||||
return m_column >= o.m_column;
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::Coordinates::operator+(const Coordinates &o) const {
|
||||
return Coordinates(m_line + o.m_line, m_column + o.m_column);
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::Coordinates::operator-(const Coordinates &o) const {
|
||||
return Coordinates(m_line - o.m_line, m_column - o.m_column);
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::Selection::getSelectedLines() {
|
||||
return Coordinates(m_start.m_line, m_end.m_line);
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::Selection::getSelectedColumns() {
|
||||
if (isSingleLine())
|
||||
return Coordinates(m_start.m_column, m_end.m_column - m_start.m_column);
|
||||
return Coordinates(m_start.m_column, m_end.m_column);
|
||||
}
|
||||
|
||||
bool TextEditor::Selection::isSingleLine() {
|
||||
return m_start.m_line == m_end.m_line;
|
||||
}
|
||||
|
||||
bool TextEditor::Selection::contains(Coordinates coordinates, int8_t endsInclusive) {
|
||||
bool result = true;
|
||||
if (endsInclusive & 2)
|
||||
result &= m_start <= coordinates;
|
||||
else
|
||||
result &= m_start < coordinates;
|
||||
|
||||
if (!result)
|
||||
return false;
|
||||
if (endsInclusive & 1)
|
||||
result &= coordinates <= m_end;
|
||||
else
|
||||
result &= coordinates < m_end;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char TextEditor::Line::LineIterator::operator*() {
|
||||
return *m_charsIter;
|
||||
}
|
||||
|
||||
TextEditor::Line::LineIterator TextEditor::Line::LineIterator::operator++() {
|
||||
LineIterator iter = *this;
|
||||
++iter.m_charsIter;
|
||||
++iter.m_colorsIter;
|
||||
++iter.m_flagsIter;
|
||||
return iter;
|
||||
}
|
||||
|
||||
TextEditor::Line::LineIterator TextEditor::Line::LineIterator::operator=(const LineIterator &other) {
|
||||
m_charsIter = other.m_charsIter;
|
||||
m_colorsIter = other.m_colorsIter;
|
||||
m_flagsIter = other.m_flagsIter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool TextEditor::Line::LineIterator::operator!=(const LineIterator &other) const {
|
||||
return m_charsIter != other.m_charsIter || m_colorsIter != other.m_colorsIter ||
|
||||
m_flagsIter != other.m_flagsIter;
|
||||
}
|
||||
|
||||
bool TextEditor::Line::LineIterator::operator==(const LineIterator &other) const {
|
||||
return m_charsIter == other.m_charsIter && m_colorsIter == other.m_colorsIter &&
|
||||
m_flagsIter == other.m_flagsIter;
|
||||
}
|
||||
|
||||
TextEditor::Line::LineIterator TextEditor::Line::LineIterator::operator+(i32 n) {
|
||||
LineIterator iter = *this;
|
||||
iter.m_charsIter += n;
|
||||
iter.m_colorsIter += n;
|
||||
iter.m_flagsIter += n;
|
||||
return iter;
|
||||
}
|
||||
|
||||
i32 TextEditor::Line::LineIterator::operator-(LineIterator l) {
|
||||
return m_charsIter - l.m_charsIter;
|
||||
}
|
||||
|
||||
TextEditor::Line::LineIterator TextEditor::Line::begin() const {
|
||||
LineIterator iter;
|
||||
iter.m_charsIter = m_chars.begin();
|
||||
iter.m_colorsIter = m_colors.begin();
|
||||
iter.m_flagsIter = m_flags.begin();
|
||||
return iter;
|
||||
}
|
||||
|
||||
TextEditor::Line::LineIterator TextEditor::Line::end() const {
|
||||
LineIterator iter;
|
||||
iter.m_charsIter = m_chars.end();
|
||||
iter.m_colorsIter = m_colors.end();
|
||||
iter.m_flagsIter = m_flags.end();
|
||||
return iter;
|
||||
}
|
||||
|
||||
TextEditor::Line::LineIterator TextEditor::Line::begin() {
|
||||
LineIterator iter;
|
||||
iter.m_charsIter = m_chars.begin();
|
||||
iter.m_colorsIter = m_colors.begin();
|
||||
iter.m_flagsIter = m_flags.begin();
|
||||
return iter;
|
||||
}
|
||||
|
||||
TextEditor::Line::LineIterator TextEditor::Line::end() {
|
||||
LineIterator iter;
|
||||
iter.m_charsIter = m_chars.end();
|
||||
iter.m_colorsIter = m_colors.end();
|
||||
iter.m_flagsIter = m_flags.end();
|
||||
return iter;
|
||||
}
|
||||
|
||||
TextEditor::Line &TextEditor::Line::operator=(const Line &line) {
|
||||
m_chars = line.m_chars;
|
||||
m_colors = line.m_colors;
|
||||
m_flags = line.m_flags;
|
||||
m_colorized = line.m_colorized;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TextEditor::Line &TextEditor::Line::operator=(Line &&line) noexcept {
|
||||
m_chars = std::move(line.m_chars);
|
||||
m_colors = std::move(line.m_colors);
|
||||
m_flags = std::move(line.m_flags);
|
||||
m_colorized = line.m_colorized;
|
||||
return *this;
|
||||
}
|
||||
|
||||
u64 TextEditor::Line::size() const {
|
||||
return m_chars.size();
|
||||
}
|
||||
|
||||
char TextEditor::Line::front(LinePart part) const {
|
||||
if (part == LinePart::Chars && !m_chars.empty())
|
||||
return m_chars.front();
|
||||
if (part == LinePart::Colors && !m_colors.empty())
|
||||
return m_colors.front();
|
||||
if (part == LinePart::Flags && !m_flags.empty())
|
||||
return m_flags.front();
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
std::string TextEditor::Line::frontUtf8(LinePart part) const {
|
||||
if (part == LinePart::Chars && !m_chars.empty())
|
||||
return m_chars.substr(0, TextEditor::utf8CharLength(m_chars[0]));
|
||||
if (part == LinePart::Colors && !m_colors.empty())
|
||||
return m_colors.substr(0, TextEditor::utf8CharLength(m_chars[0]));
|
||||
if (part == LinePart::Flags && !m_flags.empty())
|
||||
return m_flags.substr(0, TextEditor::utf8CharLength(m_chars[0]));
|
||||
return "";
|
||||
}
|
||||
|
||||
void TextEditor::Line::push_back(char c) {
|
||||
m_chars.push_back(c);
|
||||
m_colors.push_back(0x00);
|
||||
m_flags.push_back(0x00);
|
||||
m_colorized = false;
|
||||
}
|
||||
|
||||
bool TextEditor::Line::empty() const {
|
||||
return m_chars.empty();
|
||||
}
|
||||
|
||||
std::string TextEditor::Line::substr(u64 start, u64 length, LinePart part) const {
|
||||
if (start >= m_chars.size() || m_colors.size() != m_chars.size() || m_flags.size() != m_chars.size())
|
||||
return "";
|
||||
if (length == (u64) -1 || start + length >= m_chars.size())
|
||||
length = m_chars.size() - start;
|
||||
if (length == 0)
|
||||
return "";
|
||||
|
||||
if (part == LinePart::Chars)
|
||||
return m_chars.substr(start, length);
|
||||
if (part == LinePart::Colors)
|
||||
return m_colors.substr(start, length);
|
||||
if (part == LinePart::Flags)
|
||||
return m_flags.substr(start, length);
|
||||
if (part == LinePart::Utf8) {
|
||||
u64 utf8Start = 0;
|
||||
for (u64 utf8Index = 0; utf8Index < start; ++utf8Index) {
|
||||
utf8Start += TextEditor::utf8CharLength(m_chars[utf8Start]);
|
||||
}
|
||||
u64 utf8Length = 0;
|
||||
for (u64 utf8Index = 0; utf8Index < length; ++utf8Index) {
|
||||
utf8Length += TextEditor::utf8CharLength(m_chars[utf8Start + utf8Length]);
|
||||
}
|
||||
return m_chars.substr(utf8Start, utf8Length);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
char TextEditor::Line::operator[](u64 index) const {
|
||||
index = std::clamp(index, (u64) 0, (u64) (m_chars.size() - 1));
|
||||
return m_chars[index];
|
||||
}
|
||||
|
||||
// C++ can't overload functions based on return type, so use any type other
|
||||
// than u64 to avoid ambiguity.
|
||||
std::string TextEditor::Line::operator[](i64 column) const {
|
||||
u64 utf8Length = TextEditor::getStringCharacterCount(m_chars);
|
||||
u64 index = static_cast<u64>(column);
|
||||
index = std::clamp(index, (u64) 0, utf8Length - 1);
|
||||
u64 utf8Start = 0;
|
||||
for (u64 utf8Index = 0; utf8Index < index; ++utf8Index) {
|
||||
utf8Start += TextEditor::utf8CharLength(m_chars[utf8Start]);
|
||||
}
|
||||
u64 utf8CharLen = TextEditor::utf8CharLength(m_chars[utf8Start]);
|
||||
if (utf8Start + utf8CharLen > m_chars.size())
|
||||
utf8CharLen = m_chars.size() - utf8Start;
|
||||
return m_chars.substr(utf8Start, utf8CharLen);
|
||||
}
|
||||
|
||||
void TextEditor::Line::setNeedsUpdate(bool needsUpdate) {
|
||||
m_colorized = m_colorized && !needsUpdate;
|
||||
}
|
||||
|
||||
void TextEditor::Line::append(const char *text) {
|
||||
append(std::string(text));
|
||||
}
|
||||
|
||||
void TextEditor::Line::append(const char text) {
|
||||
append(std::string(1, text));
|
||||
}
|
||||
|
||||
void TextEditor::Line::append(const std::string &text) {
|
||||
Line line(text);
|
||||
append(line);
|
||||
}
|
||||
|
||||
void TextEditor::Line::append(const Line &line) {
|
||||
append(line.begin(), line.end());
|
||||
}
|
||||
|
||||
void TextEditor::Line::append(LineIterator begin, LineIterator end) {
|
||||
if (begin.m_charsIter < end.m_charsIter)
|
||||
m_chars.append(begin.m_charsIter, end.m_charsIter);
|
||||
if (begin.m_colorsIter < end.m_colorsIter)
|
||||
m_colors.append(begin.m_colorsIter, end.m_colorsIter);
|
||||
if (begin.m_flagsIter < end.m_flagsIter)
|
||||
m_flags.append(begin.m_flagsIter, end.m_flagsIter);
|
||||
m_colorized = false;
|
||||
}
|
||||
|
||||
void TextEditor::Line::insert(LineIterator iter, const std::string &text) {
|
||||
insert(iter, text.begin(), text.end());
|
||||
}
|
||||
|
||||
void TextEditor::Line::insert(LineIterator iter, const char text) {
|
||||
insert(iter, std::string(1, text));
|
||||
}
|
||||
|
||||
void TextEditor::Line::insert(LineIterator iter, strConstIter beginString, strConstIter endString) {
|
||||
Line line(std::string(beginString, endString));
|
||||
insert(iter, line);
|
||||
}
|
||||
|
||||
void TextEditor::Line::insert(LineIterator iter, const Line &line) {
|
||||
insert(iter, line.begin(), line.end());
|
||||
}
|
||||
|
||||
void TextEditor::Line::insert(LineIterator iter, LineIterator beginLine, LineIterator endLine) {
|
||||
if (iter == end())
|
||||
append(beginLine, endLine);
|
||||
else {
|
||||
m_chars.insert(iter.m_charsIter, beginLine.m_charsIter, endLine.m_charsIter);
|
||||
m_colors.insert(iter.m_colorsIter, beginLine.m_colorsIter, endLine.m_colorsIter);
|
||||
m_flags.insert(iter.m_flagsIter, beginLine.m_flagsIter, endLine.m_flagsIter);
|
||||
m_colorized = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::Line::erase(LineIterator begin) {
|
||||
m_chars.erase(begin.m_charsIter);
|
||||
m_colors.erase(begin.m_colorsIter);
|
||||
m_flags.erase(begin.m_flagsIter);
|
||||
m_colorized = false;
|
||||
}
|
||||
|
||||
void TextEditor::Line::erase(LineIterator begin, u64 count) {
|
||||
if (count == (u64) -1)
|
||||
count = m_chars.size() - (begin.m_charsIter - m_chars.begin());
|
||||
m_chars.erase(begin.m_charsIter, begin.m_charsIter + count);
|
||||
m_colors.erase(begin.m_colorsIter, begin.m_colorsIter + count);
|
||||
m_flags.erase(begin.m_flagsIter, begin.m_flagsIter + count);
|
||||
m_colorized = false;
|
||||
}
|
||||
|
||||
void TextEditor::Line::erase(u64 start, u64 length) {
|
||||
if (length == (u64) -1 || start + length >= m_chars.size())
|
||||
length = m_chars.size() - start;
|
||||
u64 utf8Start = 0;
|
||||
for (u64 utf8Index = 0; utf8Index < start; ++utf8Index) {
|
||||
utf8Start += TextEditor::utf8CharLength(m_chars[utf8Start]);
|
||||
}
|
||||
u64 utf8Length = 0;
|
||||
for (u64 utf8Index = 0; utf8Index < length; ++utf8Index) {
|
||||
utf8Length += TextEditor::utf8CharLength(m_chars[utf8Start + utf8Length]);
|
||||
}
|
||||
utf8Length = std::min(utf8Length, (u64) (m_chars.size() - utf8Start));
|
||||
erase(begin() + utf8Start, utf8Length);
|
||||
}
|
||||
|
||||
void TextEditor::Line::clear() {
|
||||
m_chars.clear();
|
||||
m_colors.clear();
|
||||
m_flags.clear();
|
||||
m_colorized = false;
|
||||
}
|
||||
|
||||
void TextEditor::Line::setLine(const std::string &text) {
|
||||
m_chars = text;
|
||||
m_colors = std::string(text.size(), 0x00);
|
||||
m_flags = std::string(text.size(), 0x00);
|
||||
m_colorized = false;
|
||||
}
|
||||
|
||||
void TextEditor::Line::setLine(const Line &text) {
|
||||
m_chars = text.m_chars;
|
||||
m_colors = text.m_colors;
|
||||
m_flags = text.m_flags;
|
||||
m_colorized = text.m_colorized;
|
||||
}
|
||||
|
||||
bool TextEditor::Line::needsUpdate() const {
|
||||
return !m_colorized;
|
||||
}
|
||||
|
||||
TextEditor *TextEditor::GetSourceCodeEditor() {
|
||||
if (m_sourceCodeEditor != nullptr)
|
||||
return m_sourceCodeEditor;
|
||||
return this;
|
||||
}
|
||||
|
||||
bool TextEditor::isEmpty() const {
|
||||
if (m_lines.empty())
|
||||
return true;
|
||||
if (m_lines.size() == 1) {
|
||||
if (m_lines[0].empty())
|
||||
return true;
|
||||
if (m_lines[0].size() == 1 && m_lines[0].front() == '\n')
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TextEditor::setSelection(const Selection &selection) {
|
||||
m_state.m_selection = setCoordinates(selection);
|
||||
}
|
||||
|
||||
TextEditor::Selection TextEditor::getSelection() const {
|
||||
return m_state.m_selection;
|
||||
}
|
||||
|
||||
void TextEditor::selectWordUnderCursor() {
|
||||
auto wordStart = findWordStart(getCursorPosition());
|
||||
setSelection(Selection(wordStart, findWordEnd(wordStart)));
|
||||
}
|
||||
|
||||
void TextEditor::selectAll() {
|
||||
setSelection(Selection(setCoordinates(0, 0), setCoordinates(-1, -1)));
|
||||
}
|
||||
|
||||
bool TextEditor::hasSelection() const {
|
||||
return !isEmpty() && m_state.m_selection.m_end > m_state.m_selection.m_start;
|
||||
}
|
||||
|
||||
void TextEditor::addUndo(UndoRecord &value) {
|
||||
if (m_readOnly)
|
||||
return;
|
||||
|
||||
m_undoBuffer.resize((u64) (m_undoIndex + 1));
|
||||
m_undoBuffer.back() = value;
|
||||
++m_undoIndex;
|
||||
}
|
||||
|
||||
TextEditor::PaletteIndex TextEditor::getColorIndexFromFlags(Line::Flags flags) {
|
||||
if (flags.m_bits.globalDocComment)
|
||||
return PaletteIndex::GlobalDocComment;
|
||||
if (flags.m_bits.blockDocComment)
|
||||
return PaletteIndex::DocBlockComment;
|
||||
if (flags.m_bits.docComment)
|
||||
return PaletteIndex::DocComment;
|
||||
if (flags.m_bits.blockComment)
|
||||
return PaletteIndex::BlockComment;
|
||||
if (flags.m_bits.comment)
|
||||
return PaletteIndex::Comment;
|
||||
if (flags.m_bits.deactivated)
|
||||
return PaletteIndex::PreprocessorDeactivated;
|
||||
if (flags.m_bits.preprocessor)
|
||||
return PaletteIndex::Directive;
|
||||
return PaletteIndex::Default;
|
||||
}
|
||||
|
||||
void TextEditor::handleKeyboardInputs() {
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
|
||||
// command => Ctrl
|
||||
// control => Super
|
||||
// option => Alt
|
||||
auto ctrl = io.KeyCtrl;
|
||||
auto alt = io.KeyAlt;
|
||||
auto shift = io.KeyShift;
|
||||
|
||||
if (ImGui::IsWindowFocused()) {
|
||||
if (ImGui::IsWindowHovered())
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput);
|
||||
|
||||
io.WantCaptureKeyboard = true;
|
||||
io.WantTextInput = true;
|
||||
|
||||
if (!m_readOnly && !ctrl && !shift && !alt && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter)))
|
||||
enterCharacter('\n', false);
|
||||
else if (!m_readOnly && !ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Tab))
|
||||
enterCharacter('\t', shift);
|
||||
|
||||
if (!m_readOnly && !io.InputQueueCharacters.empty()) {
|
||||
for (i32 i = 0; i < io.InputQueueCharacters.Size; i++) {
|
||||
auto c = io.InputQueueCharacters[i];
|
||||
if (c != 0 && (c == '\n' || c >= 32)) {
|
||||
enterCharacter(c, shift);
|
||||
}
|
||||
}
|
||||
io.InputQueueCharacters.resize(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::handleMouseInputs() {
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
auto shift = io.KeyShift;
|
||||
auto ctrl = io.ConfigMacOSXBehaviors ? io.KeyAlt : io.KeyCtrl;
|
||||
auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
|
||||
|
||||
if (ImGui::IsWindowHovered()) {
|
||||
if (!alt) {
|
||||
auto click = ImGui::IsMouseClicked(0);
|
||||
auto doubleClick = ImGui::IsMouseDoubleClicked(0);
|
||||
auto rightClick = ImGui::IsMouseClicked(1);
|
||||
auto t = ImGui::GetTime();
|
||||
auto tripleClick = click && !doubleClick && (m_lastClick != -1.0f && (t - m_lastClick) < io.MouseDoubleClickTime);
|
||||
bool resetBlinking = false;
|
||||
/*
|
||||
Left mouse button triple click
|
||||
*/
|
||||
|
||||
if (tripleClick) {
|
||||
if (!ctrl) {
|
||||
m_state.m_cursorPosition = screenPosToCoordinates(ImGui::GetMousePos());
|
||||
auto line = m_state.m_cursorPosition.m_line;
|
||||
m_state.m_selection.m_start = setCoordinates(line, 0);
|
||||
m_state.m_selection.m_end = setCoordinates(line, getLineMaxColumn(line));
|
||||
}
|
||||
|
||||
m_lastClick = -1.0f;
|
||||
resetBlinking = true;
|
||||
}
|
||||
|
||||
/*
|
||||
Left mouse button double click
|
||||
*/
|
||||
|
||||
else if (doubleClick) {
|
||||
if (!ctrl) {
|
||||
m_state.m_cursorPosition = screenPosToCoordinates(ImGui::GetMousePos());
|
||||
m_state.m_selection.m_start = findWordStart(m_state.m_cursorPosition);
|
||||
m_state.m_selection.m_end = findWordEnd(m_state.m_cursorPosition);
|
||||
}
|
||||
|
||||
m_lastClick = (float) ImGui::GetTime();
|
||||
resetBlinking = true;
|
||||
}
|
||||
|
||||
/*
|
||||
Left mouse button click
|
||||
*/
|
||||
else if (click) {
|
||||
if (ctrl) {
|
||||
m_state.m_cursorPosition = m_interactiveSelection.m_start = m_interactiveSelection.m_end = screenPosToCoordinates(ImGui::GetMousePos());
|
||||
selectWordUnderCursor();
|
||||
} else if (shift) {
|
||||
m_interactiveSelection.m_end = screenPosToCoordinates(ImGui::GetMousePos());
|
||||
m_state.m_cursorPosition = m_interactiveSelection.m_end;
|
||||
setSelection(m_interactiveSelection);
|
||||
} else {
|
||||
m_state.m_cursorPosition = m_interactiveSelection.m_start = m_interactiveSelection.m_end = screenPosToCoordinates(ImGui::GetMousePos());
|
||||
setSelection(m_interactiveSelection);
|
||||
}
|
||||
resetCursorBlinkTime();
|
||||
|
||||
ensureCursorVisible();
|
||||
m_lastClick = (float) ImGui::GetTime();
|
||||
} else if (rightClick) {
|
||||
auto cursorPosition = screenPosToCoordinates(ImGui::GetMousePos());
|
||||
|
||||
if (!hasSelection() || m_state.m_selection.m_start > cursorPosition || cursorPosition > m_state.m_selection.m_end) {
|
||||
m_state.m_cursorPosition = m_interactiveSelection.m_start = m_interactiveSelection.m_end = cursorPosition;
|
||||
setSelection(m_interactiveSelection);
|
||||
}
|
||||
resetCursorBlinkTime();
|
||||
m_raiseContextMenu = true;
|
||||
ImGui::SetWindowFocus();
|
||||
}
|
||||
// Mouse left button dragging (=> update selection)
|
||||
else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0)) {
|
||||
io.WantCaptureMouse = true;
|
||||
m_state.m_cursorPosition = m_interactiveSelection.m_end = screenPosToCoordinates(ImGui::GetMousePos());
|
||||
setSelection(m_interactiveSelection);
|
||||
resetBlinking = true;
|
||||
}
|
||||
if (resetBlinking)
|
||||
resetCursorBlinkTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the index here is array index so zero based
|
||||
void TextEditor::FindReplaceHandler::selectFound(TextEditor *editor, i32 found) {
|
||||
if (found < 0 || found >= (i64) m_matches.size())
|
||||
return;
|
||||
editor->setSelection(m_matches[found].m_selection);
|
||||
editor->setCursorPosition();
|
||||
}
|
||||
|
||||
// The returned index is shown in the form
|
||||
// 'index of count' so 1 based
|
||||
u32 TextEditor::FindReplaceHandler::findMatch(TextEditor *editor, bool isNext) {
|
||||
|
||||
if (editor->m_textChanged || m_optionsChanged) {
|
||||
std::string findWord = getFindWord();
|
||||
if (findWord.empty())
|
||||
return 0;
|
||||
resetMatches();
|
||||
findAllMatches(editor, findWord);
|
||||
}
|
||||
Coordinates targetPos = editor->m_state.m_cursorPosition;
|
||||
if (editor->hasSelection())
|
||||
targetPos = isNext ? editor->m_state.m_selection.m_end : editor->m_state.m_selection.m_start;
|
||||
|
||||
auto count = m_matches.size();
|
||||
|
||||
if (count == 0) {
|
||||
editor->setCursorPosition(targetPos);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isNext) {
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
if (targetPos <= m_matches[i].m_selection.m_start) {
|
||||
selectFound(editor, i);
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
selectFound(editor, 0);
|
||||
return 1;
|
||||
} else {
|
||||
for (i32 i = count - 1; i >= 0; i--) {
|
||||
if (targetPos >= m_matches[i].m_selection.m_end) {
|
||||
selectFound(editor, i);
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
selectFound(editor, count - 1);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
// returns 1 based index
|
||||
u32 TextEditor::FindReplaceHandler::findPosition(TextEditor *editor, Coordinates pos, bool isNext) {
|
||||
if (editor->m_textChanged || m_optionsChanged) {
|
||||
std::string findWord = getFindWord();
|
||||
if (findWord.empty())
|
||||
return 0;
|
||||
resetMatches();
|
||||
findAllMatches(editor, findWord);
|
||||
}
|
||||
|
||||
i32 count = m_matches.size();
|
||||
if (count == 0)
|
||||
return 0;
|
||||
if (isNext) {
|
||||
for (i32 i = 0; i < count; i++) {
|
||||
auto interval = Selection(m_matches[i==0 ? count-1 : i - 1].m_selection.m_end,m_matches[i].m_selection.m_end);
|
||||
if (interval.contains(pos))
|
||||
return i + 1;
|
||||
}
|
||||
} else {
|
||||
for (i32 i = 0; i < count; i++) {
|
||||
auto interval = Selection(m_matches[i == 0 ? count - 1 : i - 1].m_selection.m_start, m_matches[i].m_selection.m_start);
|
||||
if (interval.contains(pos, 2))
|
||||
return i == 0 ? count : i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create a string that escapes special characters
|
||||
// and separate word from non word
|
||||
std::string make_wholeWord(const std::string &s) {
|
||||
static const char metacharacters[] = R"(\.^$-+()[]{}|?*)";
|
||||
std::string out;
|
||||
out.reserve(s.size());
|
||||
if (s[0] == '#')
|
||||
out.push_back('#');
|
||||
out.push_back('\\');
|
||||
out.push_back('b');
|
||||
for (auto ch: s) {
|
||||
if (strchr(metacharacters, ch))
|
||||
out.push_back('\\');
|
||||
out.push_back(ch);
|
||||
}
|
||||
out.push_back('\\');
|
||||
out.push_back('b');
|
||||
return out;
|
||||
}
|
||||
|
||||
// Performs actual search to fill mMatches
|
||||
bool TextEditor::FindReplaceHandler::findNext(TextEditor *editor) {
|
||||
Coordinates curPos = m_matches.empty() ? editor->m_state.m_cursorPosition : editor->lineCoordsToIndexCoords(m_matches.back().m_cursorPosition);
|
||||
|
||||
u64 matchLength = getStringCharacterCount(m_findWord);
|
||||
u64 matchBytes = m_findWord.size();
|
||||
u64 byteIndex = 0;
|
||||
|
||||
for (i64 ln = 0; ln < curPos.m_line; ln++)
|
||||
byteIndex += editor->getLineByteCount(ln) + 1;
|
||||
byteIndex += curPos.m_column;
|
||||
|
||||
std::string wordLower = m_findWord;
|
||||
if (!getMatchCase())
|
||||
std::transform(wordLower.begin(), wordLower.end(), wordLower.begin(), ::tolower);
|
||||
|
||||
std::string textSrc = editor->getText();
|
||||
if (!getMatchCase())
|
||||
std::transform(textSrc.begin(), textSrc.end(), textSrc.begin(), ::tolower);
|
||||
|
||||
u64 textLoc;
|
||||
// TODO: use regexp find iterator in all cases
|
||||
// to find all matches for FindAllMatches.
|
||||
// That should make things faster (no need
|
||||
// to call FindNext many times) and remove
|
||||
// clunky match case code
|
||||
if (getWholeWord() || getFindRegEx()) {
|
||||
std::regex regularExpression;
|
||||
if (getFindRegEx()) {
|
||||
try {
|
||||
regularExpression.assign(wordLower);
|
||||
} catch (const std::regex_error &e) {
|
||||
hex::log::error("Error in regular expression: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
regularExpression.assign(make_wholeWord(wordLower));
|
||||
} catch (const std::regex_error &e) {
|
||||
hex::log::error("Error in regular expression: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
u64 pos = 0;
|
||||
std::sregex_iterator iter = std::sregex_iterator(textSrc.begin(), textSrc.end(), regularExpression);
|
||||
std::sregex_iterator end;
|
||||
if (!iter->ready())
|
||||
return false;
|
||||
u64 firstLoc = iter->position();
|
||||
u64 firstLength = iter->length();
|
||||
|
||||
if (firstLoc > byteIndex) {
|
||||
pos = firstLoc;
|
||||
matchLength = firstLength;
|
||||
} else {
|
||||
|
||||
while (iter != end) {
|
||||
iter++;
|
||||
if (((pos = iter->position()) > byteIndex) && ((matchLength = iter->length()) > 0))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (iter == end)
|
||||
return false;
|
||||
|
||||
textLoc = pos;
|
||||
} else {
|
||||
// non regex search
|
||||
textLoc = textSrc.find(wordLower, byteIndex);
|
||||
if (textLoc == std::string::npos)
|
||||
return false;
|
||||
}
|
||||
if (textLoc == std::string::npos)
|
||||
return false;
|
||||
TextEditor::EditorState state;
|
||||
state.m_selection = Selection(TextEditor::stringIndexToCoordinates(textLoc, textSrc), TextEditor::stringIndexToCoordinates(textLoc + matchBytes, textSrc));
|
||||
state.m_cursorPosition = state.m_selection.m_end;
|
||||
m_matches.push_back(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextEditor::FindReplaceHandler::findAllMatches(TextEditor *editor, std::string findWord) {
|
||||
|
||||
if (findWord.empty()) {
|
||||
editor->ensureCursorVisible();
|
||||
m_findWord = "";
|
||||
m_matches.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (findWord == m_findWord && !editor->m_textChanged && !m_optionsChanged)
|
||||
return;
|
||||
|
||||
if (m_optionsChanged)
|
||||
m_optionsChanged = false;
|
||||
|
||||
m_matches.clear();
|
||||
m_findWord = findWord;
|
||||
auto startingPos = editor->m_state.m_cursorPosition;
|
||||
auto saveState = editor->m_state;
|
||||
Coordinates begin = editor->setCoordinates(0, 0);
|
||||
editor->m_state.m_cursorPosition = begin;
|
||||
|
||||
if (!findNext(editor)) {
|
||||
editor->m_state = saveState;
|
||||
editor->ensureCursorVisible();
|
||||
return;
|
||||
}
|
||||
TextEditor::EditorState state = m_matches.back();
|
||||
|
||||
while (state.m_cursorPosition < startingPos) {
|
||||
if (!findNext(editor)) {
|
||||
editor->m_state = saveState;
|
||||
editor->ensureCursorVisible();
|
||||
return;
|
||||
}
|
||||
state = m_matches.back();
|
||||
}
|
||||
|
||||
while (findNext(editor));
|
||||
|
||||
editor->m_state = saveState;
|
||||
editor->ensureCursorVisible();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
bool TextEditor::FindReplaceHandler::replace(TextEditor *editor, bool right) {
|
||||
if (m_matches.empty() || m_findWord == m_replaceWord || m_findWord.empty())
|
||||
return false;
|
||||
|
||||
|
||||
auto state = editor->m_state;
|
||||
if (editor->m_state.m_selection.contains(editor->m_state.m_cursorPosition)) {
|
||||
editor->m_state.m_cursorPosition = editor->m_state.m_selection.m_start;
|
||||
if (editor->isStartOfLine()) {
|
||||
editor->m_state.m_cursorPosition.m_line--;
|
||||
editor->m_state.m_cursorPosition.m_column = editor->getLineMaxColumn(editor->m_state.m_cursorPosition.m_line);
|
||||
} else
|
||||
editor->m_state.m_cursorPosition.m_column--;
|
||||
}
|
||||
auto matchIndex = findMatch(editor, right);
|
||||
if (matchIndex != 0) {
|
||||
UndoRecord u;
|
||||
u.m_before = editor->m_state;
|
||||
u.m_removed = editor->getSelectedText();
|
||||
u.m_removedSelection = editor->m_state.m_selection;
|
||||
editor->deleteSelection();
|
||||
if (getFindRegEx()) {
|
||||
std::string replacedText = std::regex_replace(editor->getText(), std::regex(m_findWord), m_replaceWord, std::regex_constants::format_first_only | std::regex_constants::format_no_copy);
|
||||
u.m_added = replacedText;
|
||||
} else
|
||||
u.m_added = m_replaceWord;
|
||||
|
||||
u.m_addedSelection.m_start = editor->setCoordinates(editor->m_state.m_cursorPosition);
|
||||
editor->insertText(u.m_added);
|
||||
|
||||
editor->setCursorPosition(editor->m_state.m_selection.m_end);
|
||||
|
||||
u.m_addedSelection.m_end = editor->setCoordinates(editor->m_state.m_cursorPosition);
|
||||
|
||||
editor->ensureCursorVisible();
|
||||
ImGui::SetKeyboardFocusHere(0);
|
||||
|
||||
u.m_after = editor->m_state;
|
||||
editor->addUndo(u);
|
||||
editor->m_textChanged = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
editor->m_state = state;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextEditor::FindReplaceHandler::replaceAll(TextEditor *editor) {
|
||||
u32 count = m_matches.size();
|
||||
|
||||
for (u32 i = 0; i < count; i++)
|
||||
replace(editor, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
168
plugins/ui/source/ui/text_editor/utf8.cpp
Normal file
168
plugins/ui/source/ui/text_editor/utf8.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#include <ui/text_editor.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
namespace hex::ui {
|
||||
// https://en.wikipedia.org/wiki/UTF-8
|
||||
// We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code)
|
||||
i32 TextEditor::utf8CharLength(u8 c) {
|
||||
if ((c & 0xFE) == 0xFC)
|
||||
return 6;
|
||||
if ((c & 0xFC) == 0xF8)
|
||||
return 5;
|
||||
if ((c & 0xF8) == 0xF0)
|
||||
return 4;
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
return 3;
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
i32 TextEditor::getStringCharacterCount(const std::string &str) {
|
||||
if (str.empty())
|
||||
return 0;
|
||||
i32 count = 0;
|
||||
for (u32 idx = 0; idx < str.size(); count++)
|
||||
idx += TextEditor::utf8CharLength(str[idx]);
|
||||
return count;
|
||||
}
|
||||
|
||||
// "Borrowed" from ImGui source
|
||||
void TextEditor::imTextCharToUtf8(std::string &buffer, u32 c) {
|
||||
if (c < 0x80) {
|
||||
buffer += (char) c;
|
||||
return;
|
||||
}
|
||||
if (c < 0x800) {
|
||||
buffer += (char) (0xc0 + (c >> 6));
|
||||
buffer += (char) (0x80 + (c & 0x3f));
|
||||
return;
|
||||
}
|
||||
if (c >= 0xdc00 && c < 0xe000)
|
||||
return;
|
||||
if (c >= 0xd800 && c < 0xdc00) {
|
||||
buffer += (char) (0xf0 + (c >> 18));
|
||||
buffer += (char) (0x80 + ((c >> 12) & 0x3f));
|
||||
buffer += (char) (0x80 + ((c >> 6) & 0x3f));
|
||||
buffer += (char) (0x80 + ((c) & 0x3f));
|
||||
return;
|
||||
}
|
||||
// else if (c < 0x10000)
|
||||
{
|
||||
buffer += (char) (0xe0 + (c >> 12));
|
||||
buffer += (char) (0x80 + ((c >> 6) & 0x3f));
|
||||
buffer += (char) (0x80 + ((c) & 0x3f));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
i32 TextEditor::imTextCharToUtf8(char *buffer, i32 buf_size, u32 c) {
|
||||
std::string input;
|
||||
imTextCharToUtf8(input, c);
|
||||
auto size = std::min((i32)input.size(),buf_size - 1);
|
||||
i32 i = 0;
|
||||
for (; i < size; i++)
|
||||
buffer[i] = input[i];
|
||||
buffer[i] = 0;
|
||||
return size;
|
||||
}
|
||||
|
||||
static i32 utf8CharCount(const std::string &line, i32 start, i32 numChars) {
|
||||
if (line.empty())
|
||||
return 0;
|
||||
|
||||
i32 index = 0;
|
||||
for (i32 column = 0; start + index < (i32) line.size() && column < numChars; ++column)
|
||||
index += TextEditor::utf8CharLength(line[start + index]);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::screenPosToCoordinates(const ImVec2 &position) const {
|
||||
ImVec2 local = position - ImGui::GetCursorScreenPos();
|
||||
i32 lineNo = std::max(0, (i32) floor(local.y / m_charAdvance.y));
|
||||
if (local.x < (m_leftMargin - 2) || lineNo >= (i32) m_lines.size() || m_lines[lineNo].empty())
|
||||
return setCoordinates(std::min(lineNo, (i32) m_lines.size() - 1), 0);
|
||||
std::string line = m_lines[lineNo].m_chars;
|
||||
local.x -= (m_leftMargin - 5);
|
||||
i32 count = 0;
|
||||
u64 length;
|
||||
i32 increase;
|
||||
do {
|
||||
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;
|
||||
} while (length < local.x && count < (i32) line.size() + increase);
|
||||
|
||||
auto result = getCharacterCoordinates(lineNo, count - increase);
|
||||
result = setCoordinates(result);
|
||||
if (result == Invalid)
|
||||
return Coordinates(0, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::lineCoordsToIndexCoords(const Coordinates &coordinates) const {
|
||||
if (coordinates.m_line >= (i64) m_lines.size())
|
||||
return Invalid;
|
||||
|
||||
const auto &line = m_lines[coordinates.m_line];
|
||||
return Coordinates(coordinates.m_line,utf8CharCount(line.m_chars, 0, coordinates.m_column));
|
||||
}
|
||||
|
||||
i32 TextEditor::lineCoordinatesToIndex(const Coordinates &coordinates) const {
|
||||
if (coordinates.m_line >= (i64) m_lines.size())
|
||||
return -1;
|
||||
|
||||
const auto &line = m_lines[coordinates.m_line];
|
||||
return utf8CharCount(line.m_chars, 0, coordinates.m_column);
|
||||
}
|
||||
|
||||
i32 TextEditor::Line::getCharacterColumn(i32 index) const {
|
||||
i32 col = 0;
|
||||
i32 i = 0;
|
||||
while (i < index && i < (i32) size()) {
|
||||
auto c = m_chars[i];
|
||||
i += TextEditor::utf8CharLength(c);
|
||||
col++;
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::getCharacterCoordinates(i32 lineIndex, i32 strIndex) const {
|
||||
if (lineIndex < 0 || lineIndex >= (i32) m_lines.size())
|
||||
return Coordinates(0, 0);
|
||||
auto &line = m_lines[lineIndex];
|
||||
return setCoordinates(lineIndex, line.getCharacterColumn(strIndex));
|
||||
}
|
||||
|
||||
u64 TextEditor::getLineByteCount(i32 lineIndex) const {
|
||||
if (lineIndex >= (i64) m_lines.size() || lineIndex < 0)
|
||||
return 0;
|
||||
|
||||
auto &line = m_lines[lineIndex];
|
||||
return line.size();
|
||||
}
|
||||
|
||||
i32 TextEditor::getLineCharacterCount(i32 line) const {
|
||||
return getLineMaxColumn(line);
|
||||
}
|
||||
|
||||
i32 TextEditor::getLineMaxColumn(i32 line) const {
|
||||
if (line >= (i64) m_lines.size() || line < 0)
|
||||
return 0;
|
||||
return getStringCharacterCount(m_lines[line].m_chars);
|
||||
}
|
||||
|
||||
TextEditor::Coordinates TextEditor::stringIndexToCoordinates(i32 strIndex, const std::string &input) {
|
||||
if (strIndex < 0 || strIndex > (i32) input.size())
|
||||
return TextEditor::Coordinates(0, 0);
|
||||
std::string str = input.substr(0, strIndex);
|
||||
auto line = std::count(str.begin(), str.end(), '\n');
|
||||
auto index = str.find_last_of('\n');
|
||||
str = str.substr(index + 1);
|
||||
auto col = TextEditor::getStringCharacterCount(str);
|
||||
|
||||
return TextEditor::Coordinates(line, col);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user