mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-03-30 13:05:25 -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:
3
lib/third_party/imgui/CMakeLists.txt
vendored
3
lib/third_party/imgui/CMakeLists.txt
vendored
@@ -11,10 +11,9 @@ add_subdirectory(implot)
|
||||
add_subdirectory(implot3d)
|
||||
add_subdirectory(imnodes)
|
||||
add_subdirectory(backend)
|
||||
add_subdirectory(ColorTextEditor)
|
||||
add_subdirectory(imgui_test_engine)
|
||||
|
||||
set(IMGUI_LIBRARIES imgui_imgui imgui_cimgui imgui_implot imgui_implot3d imgui_imnodes imgui_backend imgui_color_text_editor)
|
||||
set(IMGUI_LIBRARIES imgui_imgui imgui_cimgui imgui_implot imgui_implot3d imgui_imnodes imgui_backend)
|
||||
set(IMGUI_LIBRARIES ${IMGUI_LIBRARIES} PARENT_SCOPE)
|
||||
|
||||
if (NOT IMHEX_EXTERNAL_PLUGIN_BUILD)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
# https://github.com/BalazsJako/ImGuiColorTextEdit
|
||||
project(imgui_color_text_editor)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
|
||||
if (NOT IMHEX_EXTERNAL_PLUGIN_BUILD)
|
||||
add_library(imgui_color_text_editor OBJECT
|
||||
source/TextEditor.cpp
|
||||
)
|
||||
|
||||
target_include_directories(imgui_color_text_editor PUBLIC
|
||||
include
|
||||
)
|
||||
|
||||
target_link_libraries(imgui_color_text_editor PRIVATE imgui_includes)
|
||||
endif()
|
||||
|
||||
target_include_directories(imgui_all_includes INTERFACE include)
|
||||
|
||||
33
lib/third_party/imgui/ColorTextEditor/README.md
vendored
33
lib/third_party/imgui/ColorTextEditor/README.md
vendored
@@ -1,33 +0,0 @@
|
||||
# ImGuiColorTextEdit
|
||||
Syntax highlighting text editor for ImGui
|
||||
|
||||

|
||||
|
||||
Demo project: https://github.com/BalazsJako/ColorTextEditorDemo
|
||||
|
||||
This started as my attempt to write a relatively simple widget which provides text editing functionality with syntax highlighting. Now there are other contributors who provide valuable additions.
|
||||
|
||||
While it relies on Omar Cornut's https://github.com/ocornut/imgui, it does not follow the "pure" one widget - one function approach. Since the editor has to maintain a relatively complex and large internal state, it did not seem to be practical to try and enforce fully immediate mode. It stores its internal state in an object instance which is reused across frames.
|
||||
|
||||
The code is (still) work in progress, please report if you find any issues.
|
||||
|
||||
# Main features
|
||||
- approximates typical code editor look and feel (essential mouse/keyboard commands work - I mean, the commands _I_ normally use :))
|
||||
- undo/redo
|
||||
- UTF-8 support
|
||||
- works with both fixed and variable-width fonts
|
||||
- extensible syntax highlighting for multiple languages
|
||||
- identifier declarations: a small piece of description can be associated with an identifier. The editor displays it in a tooltip when the mouse cursor is hovered over the identifier
|
||||
- error markers: the user can specify a list of error messages together the line of occurence, the editor will highligh the lines with red backround and display error message in a tooltip when the mouse cursor is hovered over the line
|
||||
- large files: there is no explicit limit set on file size or number of lines (below 2GB, performance is not affected when large files are loaded (except syntax coloring, see below)
|
||||
- color palette support: you can switch between different color palettes, or even define your own
|
||||
- whitespace indicators (TAB, space)
|
||||
|
||||
# Known issues
|
||||
- syntax highligthing of most languages - except C/C++ - is based on std::regex, which is diasppointingly slow. Because of that, the highlighting process is amortized between multiple frames. C/C++ has a hand-written tokenizer which is much faster.
|
||||
|
||||
Please post your screenshots if you find this little piece of software useful. :)
|
||||
|
||||
# Contribute
|
||||
|
||||
If you want to contribute, please refer to CONTRIBUTE file.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -13,12 +13,10 @@
|
||||
#include <iostream>
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
namespace hex::ui {
|
||||
using strConstIter = std::string::const_iterator;
|
||||
int32_t utf8CharLength(uint8_t c);
|
||||
int32_t getStringCharacterCount(const std::string& str);
|
||||
|
||||
class TextEditor {
|
||||
|
||||
@@ -29,54 +27,18 @@ namespace hex::ui {
|
||||
// information about the size of the array. Currently positive coordinates are always bigger
|
||||
// than negatives even if that gives a wrong result.
|
||||
struct Coordinates {
|
||||
int32_t m_line, m_column;
|
||||
i32 m_line, m_column;
|
||||
|
||||
Coordinates() : m_line(0), m_column(0) {}
|
||||
Coordinates(int32_t line, int32_t column) : m_line(line), m_column(column) {}
|
||||
|
||||
bool operator==(const Coordinates &o) const {
|
||||
return
|
||||
m_line == o.m_line &&
|
||||
m_column == o.m_column;
|
||||
}
|
||||
|
||||
bool operator!=(const Coordinates &o) const {
|
||||
return
|
||||
m_line != o.m_line ||
|
||||
m_column != o.m_column;
|
||||
}
|
||||
|
||||
bool operator<(const Coordinates &o) const {
|
||||
if (m_line != o.m_line)
|
||||
return m_line < o.m_line;
|
||||
return m_column < o.m_column;
|
||||
}
|
||||
|
||||
bool operator>(const Coordinates &o) const {
|
||||
if (m_line != o.m_line)
|
||||
return m_line > o.m_line;
|
||||
return m_column > o.m_column;
|
||||
}
|
||||
|
||||
bool operator<=(const Coordinates &o) const {
|
||||
if (m_line != o.m_line)
|
||||
return m_line < o.m_line;
|
||||
return m_column <= o.m_column;
|
||||
}
|
||||
|
||||
bool operator>=(const Coordinates &o) const {
|
||||
if (m_line != o.m_line)
|
||||
return m_line > o.m_line;
|
||||
return m_column >= o.m_column;
|
||||
}
|
||||
|
||||
Coordinates operator+(const Coordinates &o) const {
|
||||
return Coordinates(m_line + o.m_line, m_column + o.m_column);
|
||||
}
|
||||
|
||||
Coordinates operator-(const Coordinates &o) const {
|
||||
return Coordinates(m_line - o.m_line, m_column - o.m_column);
|
||||
}
|
||||
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);
|
||||
@@ -93,19 +55,10 @@ namespace hex::ui {
|
||||
}
|
||||
}
|
||||
|
||||
Coordinates getSelectedLines() {
|
||||
return Coordinates(m_start.m_line, m_end.m_line);
|
||||
}
|
||||
|
||||
Coordinates 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 isSingleLine() {
|
||||
return m_start.m_line == m_end.m_line;
|
||||
}
|
||||
Coordinates getSelectedLines();
|
||||
Coordinates getSelectedColumns();
|
||||
bool isSingleLine();
|
||||
bool contains(Coordinates coordinates, int8_t endsInclusive=1);
|
||||
};
|
||||
|
||||
struct EditorState {
|
||||
@@ -119,7 +72,7 @@ namespace hex::ui {
|
||||
typedef std::vector<EditorState> Matches;
|
||||
Matches &getMatches() { return m_matches; }
|
||||
bool findNext(TextEditor *editor);
|
||||
uint32_t findMatch(TextEditor *editor, bool isNex);
|
||||
u32 findMatch(TextEditor *editor, bool isNex);
|
||||
bool replace(TextEditor *editor, bool right);
|
||||
bool replaceAll(TextEditor *editor);
|
||||
std::string &getFindWord() { return m_findWord; }
|
||||
@@ -133,9 +86,9 @@ namespace hex::ui {
|
||||
|
||||
std::string &getReplaceWord() { return m_replaceWord; }
|
||||
void setReplaceWord(const std::string &replaceWord) { m_replaceWord = replaceWord; }
|
||||
void selectFound(TextEditor *editor, int32_t found);
|
||||
void selectFound(TextEditor *editor, i32 found);
|
||||
void findAllMatches(TextEditor *editor, std::string findWord);
|
||||
uint32_t findPosition(TextEditor *editor, Coordinates pos, bool isNext);
|
||||
u32 findPosition(TextEditor *editor, Coordinates pos, bool isNext);
|
||||
bool getMatchCase() const { return m_matchCase; }
|
||||
|
||||
void setMatchCase(TextEditor *editor, bool matchCase) {
|
||||
@@ -239,9 +192,9 @@ namespace hex::ui {
|
||||
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<uint32_t, std::string>>;
|
||||
using Breakpoints = std::unordered_set<uint32_t>;
|
||||
using Palette = std::array<ImU32, (uint64_t) PaletteIndex::Max>;
|
||||
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 {
|
||||
@@ -348,63 +301,24 @@ namespace hex::ui {
|
||||
|
||||
LineIterator() = default;
|
||||
|
||||
char operator*() {
|
||||
return *m_charsIter;
|
||||
}
|
||||
|
||||
LineIterator operator++() {
|
||||
LineIterator iter = *this;
|
||||
++iter.m_charsIter;
|
||||
++iter.m_colorsIter;
|
||||
++iter.m_flagsIter;
|
||||
return iter;
|
||||
}
|
||||
|
||||
LineIterator operator=(const LineIterator &other) {
|
||||
m_charsIter = other.m_charsIter;
|
||||
m_colorsIter = other.m_colorsIter;
|
||||
m_flagsIter = other.m_flagsIter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator!=(const LineIterator &other) const {
|
||||
return m_charsIter != other.m_charsIter || m_colorsIter != other.m_colorsIter ||
|
||||
m_flagsIter != other.m_flagsIter;
|
||||
}
|
||||
|
||||
bool operator==(const LineIterator &other) const {
|
||||
return m_charsIter == other.m_charsIter && m_colorsIter == other.m_colorsIter &&
|
||||
m_flagsIter == other.m_flagsIter;
|
||||
}
|
||||
|
||||
LineIterator operator+(int32_t n) {
|
||||
LineIterator iter = *this;
|
||||
iter.m_charsIter += n;
|
||||
iter.m_colorsIter += n;
|
||||
iter.m_flagsIter += n;
|
||||
return iter;
|
||||
}
|
||||
|
||||
int32_t operator-(LineIterator l) {
|
||||
return m_charsIter - l.m_charsIter;
|
||||
}
|
||||
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);
|
||||
};
|
||||
|
||||
LineIterator begin() const {
|
||||
LineIterator iter;
|
||||
iter.m_charsIter = m_chars.begin();
|
||||
iter.m_colorsIter = m_colors.begin();
|
||||
iter.m_flagsIter = m_flags.begin();
|
||||
return iter;
|
||||
}
|
||||
enum class LinePart {
|
||||
Chars,
|
||||
Utf8,
|
||||
Colors,
|
||||
Flags
|
||||
};
|
||||
|
||||
LineIterator end() const {
|
||||
LineIterator iter;
|
||||
iter.m_charsIter = m_chars.end();
|
||||
iter.m_colorsIter = m_colors.end();
|
||||
iter.m_flagsIter = m_flags.end();
|
||||
return iter;
|
||||
}
|
||||
LineIterator begin() const;
|
||||
LineIterator end() const;
|
||||
|
||||
std::string m_chars;
|
||||
std::string m_colors;
|
||||
@@ -416,246 +330,39 @@ namespace hex::ui {
|
||||
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) {}
|
||||
|
||||
int32_t getCharacterColumn(int32_t index) const;
|
||||
LineIterator begin() {
|
||||
LineIterator iter;
|
||||
iter.m_charsIter = m_chars.begin();
|
||||
iter.m_colorsIter = m_colors.begin();
|
||||
iter.m_flagsIter = m_flags.begin();
|
||||
return iter;
|
||||
}
|
||||
|
||||
LineIterator end() {
|
||||
LineIterator iter;
|
||||
iter.m_charsIter = m_chars.end();
|
||||
iter.m_colorsIter = m_colors.end();
|
||||
iter.m_flagsIter = m_flags.end();
|
||||
return iter;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint64_t size() const {
|
||||
return m_chars.size();
|
||||
}
|
||||
|
||||
enum class LinePart {
|
||||
Chars,
|
||||
Utf8,
|
||||
Colors,
|
||||
Flags
|
||||
};
|
||||
|
||||
char front(LinePart part = LinePart::Chars) 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 frontUtf8(LinePart part = LinePart::Chars) const {
|
||||
if (part == LinePart::Chars && !m_chars.empty())
|
||||
return m_chars.substr(0, utf8CharLength(m_chars[0]));
|
||||
if (part == LinePart::Colors && !m_colors.empty())
|
||||
return m_colors.substr(0, utf8CharLength(m_chars[0]));
|
||||
if (part == LinePart::Flags && !m_flags.empty())
|
||||
return m_flags.substr(0, utf8CharLength(m_chars[0]));
|
||||
return "";
|
||||
}
|
||||
|
||||
void push_back(char c) {
|
||||
m_chars.push_back(c);
|
||||
m_colors.push_back(0x00);
|
||||
m_flags.push_back(0x00);
|
||||
m_colorized = false;
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return m_chars.empty();
|
||||
}
|
||||
|
||||
std::string substr(uint64_t start, uint64_t length = (uint64_t) -1, LinePart part = LinePart::Chars) const {
|
||||
if (start >= m_chars.size() || m_colors.size() != m_chars.size() || m_flags.size() != m_chars.size())
|
||||
return "";
|
||||
if (length == (uint64_t) -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) {
|
||||
uint64_t utf8Start = 0;
|
||||
for (uint64_t utf8Index = 0; utf8Index < start; ++utf8Index) {
|
||||
utf8Start += utf8CharLength(m_chars[utf8Start]);
|
||||
}
|
||||
uint64_t utf8Length = 0;
|
||||
for (uint64_t utf8Index = 0; utf8Index < length; ++utf8Index) {
|
||||
utf8Length += utf8CharLength(m_chars[utf8Start + utf8Length]);
|
||||
}
|
||||
return m_chars.substr(utf8Start, utf8Length);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
char operator[](uint64_t index) const {
|
||||
index = std::clamp(index, (uint64_t) 0, (uint64_t) (m_chars.size() - 1));
|
||||
return m_chars[index];
|
||||
}
|
||||
|
||||
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 uint64_t to avoid ambiguity.
|
||||
template<class T>
|
||||
std::string operator[](T column) const {
|
||||
uint64_t utf8Length = getStringCharacterCount(m_chars);
|
||||
uint64_t index = static_cast<uint64_t>(column);
|
||||
index = std::clamp(index, (uint64_t) 0, utf8Length - 1);
|
||||
uint64_t utf8Start = 0;
|
||||
for (uint64_t utf8Index = 0; utf8Index < index; ++utf8Index) {
|
||||
utf8Start += utf8CharLength(m_chars[utf8Start]);
|
||||
}
|
||||
uint64_t utf8CharLen = utf8CharLength(m_chars[utf8Start]);
|
||||
if (utf8Start + utf8CharLen > m_chars.size())
|
||||
utf8CharLen = m_chars.size() - utf8Start;
|
||||
return m_chars.substr(utf8Start, utf8CharLen);
|
||||
}
|
||||
|
||||
void setNeedsUpdate(bool needsUpdate) {
|
||||
m_colorized = m_colorized && !needsUpdate;
|
||||
}
|
||||
|
||||
void append(const char *text) {
|
||||
append(std::string(text));
|
||||
}
|
||||
|
||||
void append(const char text) {
|
||||
append(std::string(1, text));
|
||||
}
|
||||
|
||||
void append(const std::string &text) {
|
||||
Line line(text);
|
||||
append(line);
|
||||
}
|
||||
|
||||
void append(const Line &line) {
|
||||
append(line.begin(), line.end());
|
||||
}
|
||||
|
||||
void 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 insert(LineIterator iter, const std::string &text) {
|
||||
insert(iter, text.begin(), text.end());
|
||||
}
|
||||
|
||||
void insert(LineIterator iter, const char text) {
|
||||
insert(iter, std::string(1, text));
|
||||
}
|
||||
|
||||
void insert(LineIterator iter, strConstIter beginString, strConstIter endString) {
|
||||
Line line(std::string(beginString, endString));
|
||||
insert(iter, line);
|
||||
}
|
||||
|
||||
void insert(LineIterator iter, const Line &line) {
|
||||
insert(iter, line.begin(), line.end());
|
||||
}
|
||||
|
||||
void 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 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 erase(LineIterator begin, uint64_t count) {
|
||||
if (count == (uint64_t) -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 erase(uint64_t start, uint64_t length = (uint64_t) -1) {
|
||||
if (length == (uint64_t) -1 || start + length >= m_chars.size())
|
||||
length = m_chars.size() - start;
|
||||
uint64_t utf8Start = 0;
|
||||
for (uint64_t utf8Index = 0; utf8Index < start; ++utf8Index) {
|
||||
utf8Start += utf8CharLength(m_chars[utf8Start]);
|
||||
}
|
||||
uint64_t utf8Length = 0;
|
||||
for (uint64_t utf8Index = 0; utf8Index < length; ++utf8Index) {
|
||||
utf8Length += utf8CharLength(m_chars[utf8Start + utf8Length]);
|
||||
}
|
||||
utf8Length = std::min(utf8Length, (uint64_t) (m_chars.size() - utf8Start));
|
||||
erase(begin() + utf8Start, utf8Length);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
m_chars.clear();
|
||||
m_colors.clear();
|
||||
m_flags.clear();
|
||||
m_colorized = false;
|
||||
}
|
||||
|
||||
void 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 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 needsUpdate() const {
|
||||
return !m_colorized;
|
||||
}
|
||||
|
||||
// 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>;
|
||||
@@ -740,7 +447,7 @@ namespace hex::ui {
|
||||
|
||||
bool checkPosition(TextEditor *editor, const Coordinates &from);
|
||||
bool isNearABracket(TextEditor *editor, const Coordinates &from);
|
||||
int32_t detectDirection(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; }
|
||||
@@ -749,70 +456,34 @@ namespace hex::ui {
|
||||
|
||||
public:
|
||||
// Rendering
|
||||
inline void setTextChanged(bool value) { m_textChanged = value; }
|
||||
inline bool isTextChanged() { return m_textChanged; }
|
||||
inline void clearBreakpointsChanged() { m_breakPointsChanged = false; }
|
||||
inline bool isBreakpointsChanged() { return m_breakPointsChanged; }
|
||||
inline void setShowCursor(bool value) { m_showCursor = value; }
|
||||
inline void setReadOnly(bool value) { m_readOnly = value; }
|
||||
inline void setShowLineNumbers(bool value) { m_showLineNumbers = value; }
|
||||
inline void setImGuiChildIgnored(bool value) { m_ignoreImGuiChild = value; }
|
||||
inline bool isImGuiChildIgnored() const { return m_ignoreImGuiChild; }
|
||||
inline void setShowWhitespaces(bool value) { m_showWhitespaces = value; }
|
||||
inline bool isShowingWhitespaces() const { return m_showWhitespaces; }
|
||||
void setTabSize(int32_t value);
|
||||
inline int32_t getTabSize() const { return m_tabSize; }
|
||||
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;
|
||||
ImVec2 &getCharAdvance() { return m_charAdvance; }
|
||||
static const Palette &getDarkPalette();
|
||||
static const Palette &getLightPalette();
|
||||
static const Palette &getRetroBluePalette();
|
||||
bool isEndOfLine(const Coordinates &coordinates) const;
|
||||
bool isEndOfFile(const Coordinates &coordinates) const;
|
||||
Coordinates getCursorPosition() const { return setCoordinates(m_state.m_cursorPosition); }
|
||||
void setCursorPosition(const Coordinates &position);
|
||||
bool raiseContextMenu() { return m_raiseContextMenu; }
|
||||
void clearRaiseContextMenu() { m_raiseContextMenu = false; }
|
||||
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; }
|
||||
|
||||
void setTopMarginChanged(int32_t newMargin) {
|
||||
m_newTopMargin = newMargin;
|
||||
m_topMarginChanged = true;
|
||||
}
|
||||
|
||||
void setFocusAtCoords(const Coordinates &coords) {
|
||||
m_focusAtCoords = coords;
|
||||
m_updateFocus = true;
|
||||
}
|
||||
|
||||
bool isEndOfLine() const;
|
||||
bool isStartOfLine() const;
|
||||
void setTopLine();
|
||||
void render(const char *title, const ImVec2 &size = ImVec2(), bool border = false);
|
||||
|
||||
void clearErrorMarkers() {
|
||||
m_errorMarkers.clear();
|
||||
m_errorHoverBoxes.clear();
|
||||
}
|
||||
|
||||
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 clearActionables() {
|
||||
clearErrorMarkers();
|
||||
clearGotoBoxes();
|
||||
clearCursorBoxes();
|
||||
}
|
||||
|
||||
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; }
|
||||
ImVec2 underwaves(ImVec2 pos, uint32_t nChars, ImColor color = ImGui::GetStyleColorVec4(ImGuiCol_Text), const ImVec2 &size_arg = ImVec2(0, 0));
|
||||
void setLongestLineLength(uint64_t line) { m_longestLineLength = line; }
|
||||
uint64_t getLongestLineLength() const { return m_longestLineLength; }
|
||||
|
||||
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();
|
||||
@@ -823,43 +494,23 @@ namespace hex::ui {
|
||||
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, uint64_t i, uint32_t tokenLength, char color);
|
||||
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:
|
||||
bool isColorizerEnabled() const { return m_colorizerEnabled; }
|
||||
void colorize();
|
||||
void setLanguageDefinition(const LanguageDefinition &aLanguageDef);
|
||||
const LanguageDefinition &getLanguageDefinition() const { return m_languageDefinition; }
|
||||
static const Palette &getPalette();
|
||||
static void setPalette(const Palette &value);
|
||||
|
||||
void setNeedsUpdate(uint32_t line, bool needsUpdate) {
|
||||
if (line < m_lines.size())
|
||||
m_lines[line].setNeedsUpdate(needsUpdate);
|
||||
}
|
||||
|
||||
void setColorizedLine(uint64_t line, const std::string &tokens) {
|
||||
if (line < m_lines.size()) {
|
||||
auto &lineTokens = m_lines[line].m_colors;
|
||||
if (lineTokens.size() != tokens.size()) {
|
||||
lineTokens.resize(tokens.size());
|
||||
std::fill(lineTokens.begin(), lineTokens.end(), 0x00);
|
||||
}
|
||||
bool needsUpdate = false;
|
||||
for (uint64_t i = 0; i < tokens.size(); ++i) {
|
||||
if (tokens[i] != 0x00) {
|
||||
if (tokens[i] != lineTokens[i]) {
|
||||
lineTokens[i] = tokens[i];
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
setNeedsUpdate(line, needsUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -870,8 +521,8 @@ namespace hex::ui {
|
||||
void backspace();
|
||||
bool canUndo();
|
||||
bool canRedo() const;
|
||||
void undo(int32_t steps = 1);
|
||||
void redo(int32_t steps = 1);
|
||||
void undo(i32 steps = 1);
|
||||
void redo(i32 steps = 1);
|
||||
void copy();
|
||||
void cut();
|
||||
void paste();
|
||||
@@ -880,98 +531,96 @@ namespace hex::ui {
|
||||
void insertText(const char *value);
|
||||
void appendLine(const std::string &value);
|
||||
void setOverwrite(bool value) { m_overwrite = value; }
|
||||
int32_t getTotalLines() const { return (int32_t) m_lines.size(); }
|
||||
bool isOverwrite() const { return m_overwrite; }
|
||||
void setText(const std::string &text, bool undo = false);
|
||||
std::string getText() const;
|
||||
void setScrollY();
|
||||
std::vector<std::string> getTextLines() const;
|
||||
std::string getSelectedText() const;
|
||||
std::string getLineText(int32_t line) 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:
|
||||
float textDistanceToLineStart(const Coordinates &from) const;
|
||||
std::string getText(const Selection &selection) const;
|
||||
Coordinates setCoordinates(const Coordinates &value) const;
|
||||
Coordinates setCoordinates(int32_t line, int32_t column) const;
|
||||
Selection setCoordinates(const Selection &value) const;
|
||||
void advance(Coordinates &coordinates) const;
|
||||
void deleteRange(const Selection &selection);
|
||||
int32_t insertTextAt(Coordinates &where, const std::string &value);
|
||||
void removeLine(int32_t start, int32_t end);
|
||||
void removeLine(int32_t index);
|
||||
Line &insertLine(int32_t index);
|
||||
void insertLine(int32_t index, const std::string &text);
|
||||
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(int32_t line = -1);
|
||||
void jumpToLine(i32 line = -1);
|
||||
void jumpToCoords(const Coordinates &coords);
|
||||
void moveUp(int32_t amount = 1, bool select = false);
|
||||
void moveDown(int32_t amount = 1, bool select = false);
|
||||
void moveLeft(int32_t amount = 1, bool select = false, bool wordMode = false);
|
||||
void moveRight(int32_t amount = 1, bool select = false, bool wordMode = false);
|
||||
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;
|
||||
uint32_t skipSpaces(const Coordinates &from);
|
||||
u32 skipSpaces(const Coordinates &from);
|
||||
//Support
|
||||
public:
|
||||
std::string replaceStrings(std::string string, const std::string &search, const std::string &replace);
|
||||
static std::vector<std::string>
|
||||
splitString(const std::string &string, const std::string &delimiter, bool removeEmpty);
|
||||
std::string replaceTabsWithSpaces(const std::string &string, uint32_t tabSize);
|
||||
std::string preprocessText(const std::string &code);
|
||||
FindReplaceHandler *getFindReplaceHandler() { return &m_findReplaceHandler; }
|
||||
void setSourceCodeEditor(TextEditor *editor) { m_sourceCodeEditor = editor; }
|
||||
|
||||
TextEditor *GetSourceCodeEditor() {
|
||||
if (m_sourceCodeEditor != nullptr)
|
||||
return m_sourceCodeEditor;
|
||||
return this;
|
||||
}
|
||||
|
||||
void setSelection(const Selection &selection);
|
||||
Selection getSelection() const;
|
||||
void selectWordUnderCursor();
|
||||
void selectAll();
|
||||
bool hasSelection() const;
|
||||
|
||||
bool 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 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;
|
||||
int32_t lineCoordinateToIndex(const Coordinates &coordinates) const;
|
||||
Coordinates getCharacterCoordinates(int32_t line, int32_t index) const;
|
||||
int32_t getLineCharacterCount(int32_t line) const;
|
||||
uint64_t getLineByteCount(int32_t line) const;
|
||||
int32_t getLineMaxColumn(int32_t line) 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;
|
||||
@@ -980,14 +629,14 @@ namespace hex::ui {
|
||||
Lines m_lines;
|
||||
EditorState m_state = {};
|
||||
UndoBuffer m_undoBuffer;
|
||||
int32_t m_undoIndex = 0;
|
||||
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;
|
||||
|
||||
int32_t m_tabSize = 4;
|
||||
i32 m_tabSize = 4;
|
||||
bool m_overwrite = false;
|
||||
bool m_readOnly = false;
|
||||
bool m_withinRender = false;
|
||||
@@ -996,7 +645,7 @@ namespace hex::ui {
|
||||
bool m_textChanged = false;
|
||||
bool m_colorizerEnabled = true;
|
||||
float m_lineNumberFieldWidth = 0.0F;
|
||||
uint64_t m_longestLineLength = 0;
|
||||
u64 m_longestLineLength = 0;
|
||||
float m_leftMargin = 10.0;
|
||||
float m_topLine = 0.0F;
|
||||
bool m_setTopLine = false;
|
||||
@@ -1018,7 +667,7 @@ namespace hex::ui {
|
||||
CursorBoxes m_cursorBoxes = {};
|
||||
ImVec2 m_charAdvance = {};
|
||||
Selection m_interactiveSelection = {};
|
||||
uint64_t m_startTime = 0;
|
||||
u64 m_startTime = 0;
|
||||
std::vector<std::string> m_defines;
|
||||
TextEditor *m_sourceCodeEditor = nullptr;
|
||||
float m_shiftedScrollY = 0;
|
||||
@@ -1034,8 +683,8 @@ namespace hex::ui {
|
||||
|
||||
std::vector<std::string> m_clickableText;
|
||||
|
||||
static const int32_t s_cursorBlinkInterval;
|
||||
static const int32_t s_cursorBlinkOnTime;
|
||||
static const i32 s_cursorBlinkInterval;
|
||||
static const i32 s_cursorBlinkOnTime;
|
||||
static ImVec2 s_cursorScreenPosition;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
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