├── b.bat ├── scope-guard.h ├── .vscode ├── c_cpp_properties.json ├── launch.json └── settings.json ├── types.h ├── LICENSE ├── README.md ├── enum-utils.h ├── fredbuf-rbtree.h ├── fredbuf.h ├── fredbuf-test.cpp └── fredbuf.cpp /b.bat: -------------------------------------------------------------------------------- 1 | setlocal enabledelayedexpansion 2 | 3 | for %%a in (%*) do set "%%a=1" 4 | 5 | set timing_flag= 6 | if "%timing%"=="1" set timing_flag=/DTIMING_DATA 7 | 8 | cl /nologo /std:c++latest /EHsc /W4 /WX /diagnostics:caret /diagnostics:color /Z7 %timing_flag% fredbuf-test.cpp /Fefredbuf-test.exe -------------------------------------------------------------------------------- /scope-guard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template 7 | class ScopeGuard 8 | { 9 | public: 10 | template 11 | explicit constexpr ScopeGuard(T&& scope_exit_func): 12 | scope_exit_func{ std::forward(scope_exit_func) } { } 13 | 14 | ~ScopeGuard() 15 | { 16 | scope_exit_func(); 17 | } 18 | private: 19 | F scope_exit_func; 20 | }; 21 | 22 | template 23 | ScopeGuard(F&&) -> ScopeGuard; -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Win32", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [ 9 | "_DEBUG", 10 | "UNICODE", 11 | "_UNICODE" 12 | ], 13 | "windowsSdkVersion": "10.0.22000.0", 14 | "compilerPath": "cl.exe", 15 | "cStandard": "c17", 16 | "cppStandard": "c++20", 17 | "intelliSenseMode": "windows-msvc-x64" 18 | } 19 | ], 20 | "version": 4 21 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug textbuf", 9 | "type": "cppvsdbg", 10 | "request": "launch", 11 | "program": "${workspaceRoot}/fredbuf.exe", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceRoot}", 15 | "environment": [], 16 | "externalConsole": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "enum-utils.h" 4 | 5 | namespace Editor 6 | { 7 | enum class Column : size_t 8 | { 9 | Beginning 10 | }; 11 | 12 | enum class Length : size_t { }; 13 | 14 | enum class CharOffset : size_t 15 | { 16 | Sentinel = sentinel_for 17 | }; 18 | 19 | constexpr CharOffset operator+(CharOffset off, Length len) 20 | { 21 | return CharOffset{ rep(off) + rep(len) }; 22 | } 23 | 24 | constexpr Length distance(CharOffset first, CharOffset last) 25 | { 26 | return Length{ rep(last) - rep(first) }; 27 | } 28 | 29 | constexpr Length operator+(Length lhs, Length rhs) 30 | { 31 | return Length{ rep(lhs) + rep(rhs) }; 32 | } 33 | 34 | constexpr Length operator-(Length lhs, Length rhs) 35 | { 36 | return Length{ rep(lhs) - rep(rhs) }; 37 | } 38 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.ixx": "cpp", 4 | "xutility": "cpp", 5 | "xstring": "cpp", 6 | "vector": "cpp", 7 | "atomic": "cpp", 8 | "bit": "cpp", 9 | "cctype": "cpp", 10 | "compare": "cpp", 11 | "concepts": "cpp", 12 | "cstddef": "cpp", 13 | "cstdint": "cpp", 14 | "cstdio": "cpp", 15 | "cstdlib": "cpp", 16 | "cstring": "cpp", 17 | "ctime": "cpp", 18 | "cwchar": "cpp", 19 | "exception": "cpp", 20 | "forward_list": "cpp", 21 | "initializer_list": "cpp", 22 | "iosfwd": "cpp", 23 | "limits": "cpp", 24 | "memory": "cpp", 25 | "new": "cpp", 26 | "string": "cpp", 27 | "tuple": "cpp", 28 | "type_traits": "cpp", 29 | "typeinfo": "cpp", 30 | "utility": "cpp", 31 | "xmemory": "cpp", 32 | "xstddef": "cpp", 33 | "xtr1common": "cpp" 34 | } 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Cameron DaCamara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project 2 | 3 | This is the text buffer implementation described in [Text Editor Data Structures](https://cdacamar.github.io/data%20structures/algorithms/benchmarking/text%20editors/c++/editor-data-structures/?fbclid=IwAR1KPqHQU-torrzSq7LKWgK3uUZsTaoEpiAQeDT8XlvlOD3MCSt3sEl2YXc). 4 | 5 | ## API 6 | 7 | Building: 8 | 9 | ```c++ 10 | using namespace PieceTree; 11 | TreeBuilder builder; 12 | builder.accept("ABC"); 13 | builder.accept("DEF"); 14 | auto tree = builder.create(); 15 | // Resulting total buffer: "ABCDEF" 16 | ``` 17 | 18 | Insertion: 19 | 20 | ```c++ 21 | tree.insert(CharOffset{ 0 }, "foo"); 22 | // Resulting total buffer: "fooABCDEF" 23 | ``` 24 | 25 | Deletion: 26 | 27 | ```c++ 28 | tree.remove(CharOffset{ 6 }, Length{ 3 }); 29 | // Resulting total buffer: "fooABC" 30 | ``` 31 | 32 | Line retrieval: 33 | 34 | ```c++ 35 | std::string buf; 36 | tree.get_line_content(&buf, Line{ 1 }); 37 | // 'buf' contains "fooABC" 38 | ``` 39 | 40 | Iteration: 41 | 42 | ```c++ 43 | for (char c : buf) 44 | { 45 | printf("%c", c); 46 | } 47 | ``` 48 | 49 | ## Contributing 50 | 51 | Feel free to open up a PR or issue but there's no guarantee it will get merged. 52 | 53 | ## Building 54 | 55 | If you're on Windows, open a developer command prompt and invoke `b.bat`. Do the same for essentially any other compiler except change the flags to be specific to your compiler. It's all standard C++ all the way down. 56 | 57 | Optionally, you can have the build emit timing data by using `b timing`. -------------------------------------------------------------------------------- /enum-utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | template 8 | concept Enum = std::is_enum_v; 9 | 10 | template 11 | using PrimitiveType = std::underlying_type_t; 12 | 13 | template 14 | constexpr auto sentinel_for = std::numeric_limits>::max(); 15 | 16 | template 17 | constexpr auto rep(E e) { return PrimitiveType(e); } 18 | 19 | template 20 | constexpr E operator&(E a, E b) { return E(rep(a) & rep(b)); } 21 | 22 | template 23 | constexpr E operator|(E a, E b) { return E(rep(a) | rep(b)); } 24 | 25 | template 26 | constexpr E& operator&=(E& a, E b) { return a = a & b; } 27 | 28 | template 29 | constexpr E& operator|=(E& a, E b) { return a = a | b; } 30 | 31 | template 32 | constexpr auto retract(E e, PrimitiveType x = 1) { return E(rep(e) - x); } 33 | 34 | template 35 | constexpr auto extend(E e, PrimitiveType x = 1) { return E(rep(e) + x); } 36 | 37 | template 38 | constexpr bool implies(E a, E b) { return (a & b) == b; } 39 | 40 | template 41 | constexpr E unit = { }; 42 | 43 | template 44 | concept YesNoEnum = Enum 45 | && std::same_as, bool> 46 | && requires { 47 | { E::Yes }; 48 | { E::No }; 49 | } 50 | && rep(E::Yes) == true 51 | && rep(E::No) == false; 52 | 53 | template 54 | constexpr bool is_yes(E e) 55 | { 56 | return rep(e); 57 | } 58 | 59 | template 60 | constexpr bool is_no(E e) 61 | { 62 | return not rep(e); 63 | } 64 | 65 | template 66 | constexpr T make_yes_no(bool value) noexcept 67 | { 68 | return static_cast(value); 69 | } 70 | 71 | template 72 | concept Countable = requires(T) { 73 | T::Count; 74 | }; 75 | 76 | template 77 | constexpr bool last_of(T t) 78 | { 79 | return extend(t) == T::Count; 80 | } 81 | 82 | template 83 | constexpr auto count_of = rep(T::Count); -------------------------------------------------------------------------------- /fredbuf-rbtree.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "types.h" 6 | 7 | // The concept for the RB tree is borrowed from 8 | // https://bartoszmilewski.com/2013/11/25/functional-data-structures-in-c-trees/ (Bartosz Milewski) 9 | // but some other pieces were assembled together from other sources, in particular deletion was 10 | // gathered from https://github.com/dotnwat/persistent-rbtree after having implemented the deletion 11 | // described by Bartosz, it was discovered that the algorithm was not always preserving the RB 12 | // invariants. 13 | 14 | // This enables the old node removal code by Bartosz. Perhaps I'll revisit it at a later time. 15 | //#define EXPERIMENTAL_REMOVE 16 | 17 | namespace PieceTree 18 | { 19 | enum class BufferIndex : size_t 20 | { 21 | ModBuf = sentinel_for 22 | }; 23 | 24 | enum class Line : size_t 25 | { 26 | IndexBeginning, 27 | Beginning 28 | }; 29 | 30 | using Editor::CharOffset; 31 | using Editor::Length; 32 | using Editor::Column; 33 | 34 | enum class LFCount : size_t { }; 35 | 36 | struct BufferCursor 37 | { 38 | // Relative line in the current buffer. 39 | Line line = { }; 40 | // Column into the current line. 41 | Column column = { }; 42 | 43 | bool operator==(const BufferCursor&) const = default; 44 | }; 45 | 46 | struct Piece 47 | { 48 | BufferIndex index = { }; // Index into a buffer in PieceTree. This could be an immutable buffer or the mutable buffer. 49 | BufferCursor first = { }; 50 | BufferCursor last = { }; 51 | Length length = { }; 52 | LFCount newline_count = { }; 53 | }; 54 | 55 | using Offset = PieceTree::CharOffset; 56 | 57 | struct NodeData 58 | { 59 | PieceTree::Piece piece; 60 | 61 | PieceTree::Length left_subtree_length = { }; 62 | PieceTree::LFCount left_subtree_lf_count = { }; 63 | }; 64 | 65 | class RedBlackTree; 66 | 67 | NodeData attribute(const NodeData& data, const RedBlackTree& left); 68 | 69 | enum class Color 70 | { 71 | Red, 72 | Black, 73 | DoubleBlack 74 | }; 75 | 76 | inline const char* to_string(Color c) 77 | { 78 | switch (c) 79 | { 80 | case Color::Red: return "Red"; 81 | case Color::Black: return "Black"; 82 | case Color::DoubleBlack: return "DoubleBlack"; 83 | } 84 | return "unknown"; 85 | } 86 | 87 | class RedBlackTree 88 | { 89 | struct Node; 90 | using NodePtr = std::shared_ptr; 91 | 92 | struct Node 93 | { 94 | Node(Color c, const NodePtr& lft, const NodeData& data, const NodePtr& rgt); 95 | 96 | Color color; 97 | NodePtr left; 98 | NodeData data; 99 | NodePtr right; 100 | }; 101 | public: 102 | struct ColorTree; 103 | 104 | explicit RedBlackTree() = default; 105 | 106 | // Queries. 107 | const Node* root_ptr() const; 108 | bool is_empty() const; 109 | const NodeData& root() const; 110 | RedBlackTree left() const; 111 | RedBlackTree right() const; 112 | Color root_color() const; 113 | 114 | // Helpers. 115 | bool operator==(const RedBlackTree&) const = default; 116 | 117 | // Mutators. 118 | RedBlackTree insert(const NodeData& x, Offset at) const; 119 | RedBlackTree remove(Offset at) const; 120 | private: 121 | RedBlackTree(Color c, 122 | const RedBlackTree& lft, 123 | const NodeData& val, 124 | const RedBlackTree& rgt); 125 | 126 | RedBlackTree(const NodePtr& node); 127 | 128 | // Removal. 129 | #ifdef EXPERIMENTAL_REMOVE 130 | ColorTree rem(Offset at, Offset total) const; 131 | ColorTree remove_node() const; 132 | static ColorTree remove_double_black(Color c, ColorTree const &lft, const NodeData& x, ColorTree const &rgt); 133 | #else 134 | static RedBlackTree fuse(const RedBlackTree& left, const RedBlackTree& right); 135 | static RedBlackTree balance(const RedBlackTree& node); 136 | static RedBlackTree balance_left(const RedBlackTree& left); 137 | static RedBlackTree balance_right(const RedBlackTree& right); 138 | static RedBlackTree remove_left(const RedBlackTree& root, Offset at, Offset total); 139 | static RedBlackTree remove_right(const RedBlackTree& root, Offset at, Offset total); 140 | static RedBlackTree rem(const RedBlackTree& root, Offset at, Offset total); 141 | #endif // EXPERIMENTAL_REMOVE 142 | 143 | // Insertion. 144 | RedBlackTree ins(const NodeData& x, Offset at, Offset total_offset) const; 145 | static RedBlackTree balance(Color c, const RedBlackTree& lft, const NodeData& x, const RedBlackTree& rgt); 146 | bool doubled_left() const; 147 | bool doubled_right() const; 148 | 149 | // General. 150 | RedBlackTree paint(Color c) const; 151 | 152 | NodePtr root_node; 153 | }; 154 | 155 | // Global queries. 156 | PieceTree::Length tree_length(const RedBlackTree& root); 157 | PieceTree::LFCount tree_lf_count(const RedBlackTree& root); 158 | } // namespace PieceTree -------------------------------------------------------------------------------- /fredbuf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "fredbuf-rbtree.h" 10 | #include "types.h" 11 | 12 | #ifndef NDEBUG 13 | #define TEXTBUF_DEBUG 14 | #endif // NDEBUG 15 | 16 | // This is a C++ implementation of the textbuf data structure described in 17 | // https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation. The differences are 18 | // that this version is based on immutable data structures to achieve fast undo/redo. 19 | namespace PieceTree 20 | { 21 | struct UndoRedoEntry 22 | { 23 | RedBlackTree root; 24 | CharOffset op_offset; 25 | }; 26 | 27 | // We need the ability to 'release' old entries in this stack. 28 | using UndoStack = std::forward_list; 29 | using RedoStack = std::forward_list; 30 | 31 | enum class LineStart : size_t { }; 32 | 33 | using LineStarts = std::vector; 34 | 35 | struct NodePosition 36 | { 37 | // Piece Index 38 | const PieceTree::NodeData* node = nullptr; 39 | // Remainder in current piece. 40 | Length remainder = { }; 41 | // Node start offset in document. 42 | CharOffset start_offset = { }; 43 | // The line (relative to the document) where this node starts. 44 | Line line = { }; 45 | }; 46 | 47 | struct CharBuffer 48 | { 49 | std::string buffer; 50 | LineStarts line_starts; 51 | }; 52 | 53 | using BufferReference = std::shared_ptr; 54 | 55 | using Buffers = std::vector; 56 | 57 | struct BufferCollection 58 | { 59 | const CharBuffer* buffer_at(BufferIndex index) const; 60 | CharOffset buffer_offset(BufferIndex index, const BufferCursor& cursor) const; 61 | 62 | Buffers orig_buffers; 63 | CharBuffer mod_buffer; 64 | }; 65 | 66 | struct LineRange 67 | { 68 | CharOffset first; 69 | CharOffset last; // Does not include LF. 70 | }; 71 | 72 | struct UndoRedoResult 73 | { 74 | bool success; 75 | CharOffset op_offset; 76 | }; 77 | 78 | // Owning snapshot owns its own buffer data (performs a lightweight copy) so 79 | // that even if the original tree is destroyed, the owning snapshot can still 80 | // reference the underlying text. 81 | class OwningSnapshot; 82 | 83 | // Reference snapshot owns no data and is only valid for as long as the original 84 | // tree buffers are valid. 85 | class ReferenceSnapshot; 86 | 87 | // When mutating the tree nodes are saved by default into the undo stack. This 88 | // allows callers to suppress this behavior. 89 | enum class SuppressHistory : bool { No, Yes }; 90 | 91 | struct BufferMeta 92 | { 93 | LFCount lf_count = { }; 94 | Length total_content_length = { }; 95 | }; 96 | 97 | // Indicates whether or not line was missing a CR (e.g. only a '\n' was at the end). 98 | enum class IncompleteCRLF : bool { No, Yes }; 99 | 100 | class Tree 101 | { 102 | public: 103 | explicit Tree(); 104 | explicit Tree(Buffers&& buffers); 105 | 106 | // Interface. 107 | // Initialization after populating initial immutable buffers from ctor. 108 | void build_tree(); 109 | 110 | // Manipulation. 111 | void insert(CharOffset offset, std::string_view txt, SuppressHistory suppress_history = SuppressHistory::No); 112 | void remove(CharOffset offset, Length count, SuppressHistory suppress_history = SuppressHistory::No); 113 | UndoRedoResult try_undo(CharOffset op_offset); 114 | UndoRedoResult try_redo(CharOffset op_offset); 115 | 116 | // Direct history manipulation. 117 | // This will commit the current node to the history. The offset provided will be the undo point later. 118 | void commit_head(CharOffset offset); 119 | RedBlackTree head() const; 120 | // Snaps the tree back to the specified root. This needs to be called with a root that is derived from 121 | // the set of buffers based on its creation. 122 | void snap_to(const RedBlackTree& new_root); 123 | 124 | // Queries. 125 | void get_line_content(std::string* buf, Line line) const; 126 | [[nodiscard]] IncompleteCRLF get_line_content_crlf(std::string* buf, Line line) const; 127 | char at(CharOffset offset) const; 128 | Line line_at(CharOffset offset) const; 129 | LineRange get_line_range(Line line) const; 130 | LineRange get_line_range_crlf(Line line) const; 131 | LineRange get_line_range_with_newline(Line line) const; 132 | 133 | Length length() const 134 | { 135 | return meta.total_content_length; 136 | } 137 | 138 | bool is_empty() const 139 | { 140 | return meta.total_content_length == Length{}; 141 | } 142 | 143 | LFCount line_feed_count() const 144 | { 145 | return meta.lf_count; 146 | } 147 | 148 | Length line_count() const 149 | { 150 | return Length{ rep(line_feed_count()) + 1 }; 151 | } 152 | 153 | OwningSnapshot owning_snap() const; 154 | ReferenceSnapshot ref_snap() const; 155 | private: 156 | friend class TreeWalker; 157 | friend class ReverseTreeWalker; 158 | friend class OwningSnapshot; 159 | friend class ReferenceSnapshot; 160 | #ifdef TEXTBUF_DEBUG 161 | friend void print_piece(const Piece& piece, const Tree* tree, int level); 162 | friend void print_tree(const Tree& tree); 163 | #endif // TEXTBUF_DEBUG 164 | void internal_insert(CharOffset offset, std::string_view txt); 165 | void internal_remove(CharOffset offset, Length count); 166 | 167 | using Accumulator = Length(*)(const BufferCollection*, const Piece&, Line); 168 | 169 | template 170 | static void line_start(CharOffset* offset, const BufferCollection* buffers, const RedBlackTree& node, Line line); 171 | static void line_end_crlf(CharOffset* offset, const BufferCollection* buffers, const RedBlackTree& root, const RedBlackTree& node, Line line); 172 | static Length accumulate_value(const BufferCollection* buffers, const Piece& piece, Line index); 173 | static Length accumulate_value_no_lf(const BufferCollection* buffers, const Piece& piece, Line index); 174 | static void populate_from_node(std::string* buf, const BufferCollection* buffers, const RedBlackTree& node); 175 | static void populate_from_node(std::string* buf, const BufferCollection* buffers, const RedBlackTree& node, Line line_index); 176 | static LFCount line_feed_count(const BufferCollection* buffers, BufferIndex index, const BufferCursor& start, const BufferCursor& end); 177 | static NodePosition node_at(const BufferCollection* buffers, RedBlackTree node, CharOffset off); 178 | static BufferCursor buffer_position(const BufferCollection* buffers, const Piece& piece, Length remainder); 179 | static char char_at(const BufferCollection* buffers, const RedBlackTree& node, CharOffset offset); 180 | static Piece trim_piece_right(const BufferCollection* buffers, const Piece& piece, const BufferCursor& pos); 181 | static Piece trim_piece_left(const BufferCollection* buffers, const Piece& piece, const BufferCursor& pos); 182 | 183 | struct ShrinkResult 184 | { 185 | Piece left; 186 | Piece right; 187 | }; 188 | 189 | static ShrinkResult shrink_piece(const BufferCollection* buffers, const Piece& piece, const BufferCursor& first, const BufferCursor& last); 190 | 191 | // Direct mutations. 192 | void assemble_line(std::string* buf, const RedBlackTree& node, Line line) const; 193 | Piece build_piece(std::string_view txt); 194 | void combine_pieces(NodePosition existing_piece, Piece new_piece); 195 | void remove_node_range(NodePosition first, Length length); 196 | void compute_buffer_meta(); 197 | void append_undo(const RedBlackTree& old_root, CharOffset op_offset); 198 | 199 | BufferCollection buffers; 200 | //Buffers buffers; 201 | //CharBuffer mod_buffer; 202 | PieceTree::RedBlackTree root; 203 | LineStarts scratch_starts; 204 | BufferCursor last_insert; 205 | // Note: This is absolute position. Initialize to nonsense value. 206 | CharOffset end_last_insert = CharOffset::Sentinel; 207 | BufferMeta meta; 208 | UndoStack undo_stack; 209 | RedoStack redo_stack; 210 | }; 211 | 212 | class OwningSnapshot 213 | { 214 | public: 215 | explicit OwningSnapshot(const Tree* tree); 216 | explicit OwningSnapshot(const Tree* tree, const RedBlackTree& dt); 217 | 218 | // Queries. 219 | void get_line_content(std::string* buf, Line line) const; 220 | [[nodiscard]] IncompleteCRLF get_line_content_crlf(std::string* buf, Line line) const; 221 | Line line_at(CharOffset offset) const; 222 | LineRange get_line_range(Line line) const; 223 | LineRange get_line_range_crlf(Line line) const; 224 | LineRange get_line_range_with_newline(Line line) const; 225 | bool is_empty() const 226 | { 227 | return meta.total_content_length == Length{}; 228 | } 229 | 230 | Length line_count() const 231 | { 232 | return Length{ rep(meta.lf_count) + 1 }; 233 | } 234 | private: 235 | friend class TreeWalker; 236 | friend class ReverseTreeWalker; 237 | 238 | RedBlackTree root; 239 | BufferMeta meta; 240 | // This should be fairly lightweight. The original buffers 241 | // will retain the majority of the memory consumption. 242 | BufferCollection buffers; 243 | }; 244 | 245 | class ReferenceSnapshot 246 | { 247 | public: 248 | explicit ReferenceSnapshot(const Tree* tree); 249 | explicit ReferenceSnapshot(const Tree* tree, const RedBlackTree& dt); 250 | 251 | // Queries. 252 | void get_line_content(std::string* buf, Line line) const; 253 | [[nodiscard]] IncompleteCRLF get_line_content_crlf(std::string* buf, Line line) const; 254 | Line line_at(CharOffset offset) const; 255 | LineRange get_line_range(Line line) const; 256 | LineRange get_line_range_crlf(Line line) const; 257 | LineRange get_line_range_with_newline(Line line) const; 258 | bool is_empty() const 259 | { 260 | return meta.total_content_length == Length{}; 261 | } 262 | 263 | Length line_count() const 264 | { 265 | return Length{ rep(meta.lf_count) + 1 }; 266 | } 267 | private: 268 | friend class TreeWalker; 269 | friend class ReverseTreeWalker; 270 | 271 | RedBlackTree root; 272 | BufferMeta meta; 273 | // A reference to the underlying tree buffers. 274 | const BufferCollection* buffers; 275 | }; 276 | 277 | struct TreeBuilder 278 | { 279 | Buffers buffers; 280 | LineStarts scratch_starts; 281 | 282 | void accept(std::string_view txt); 283 | 284 | Tree create() 285 | { 286 | return Tree{ std::move(buffers) }; 287 | } 288 | }; 289 | 290 | class TreeWalker 291 | { 292 | public: 293 | TreeWalker(const Tree* tree, CharOffset offset = CharOffset{ }); 294 | TreeWalker(const OwningSnapshot* snap, CharOffset offset = CharOffset{ }); 295 | TreeWalker(const ReferenceSnapshot* snap, CharOffset offset = CharOffset{ }); 296 | TreeWalker(const TreeWalker&) = delete; 297 | 298 | char current(); 299 | char next(); 300 | void seek(CharOffset offset); 301 | bool exhausted() const; 302 | Length remaining() const; 303 | CharOffset offset() const 304 | { 305 | return total_offset; 306 | } 307 | 308 | // For Iterator-like behavior. 309 | TreeWalker& operator++() 310 | { 311 | return *this; 312 | } 313 | 314 | char operator*() 315 | { 316 | return next(); 317 | } 318 | private: 319 | void populate_ptrs(); 320 | void fast_forward_to(CharOffset offset); 321 | 322 | enum class Direction { Left, Center, Right }; 323 | 324 | struct StackEntry 325 | { 326 | PieceTree::RedBlackTree node; 327 | Direction dir = Direction::Left; 328 | }; 329 | 330 | const BufferCollection* buffers; 331 | RedBlackTree root; 332 | BufferMeta meta; 333 | std::vector stack; 334 | CharOffset total_offset = CharOffset{ 0 }; 335 | const char* first_ptr = nullptr; 336 | const char* last_ptr = nullptr; 337 | }; 338 | 339 | class ReverseTreeWalker 340 | { 341 | public: 342 | ReverseTreeWalker(const Tree* tree, CharOffset offset = CharOffset{ }); 343 | ReverseTreeWalker(const OwningSnapshot* snap, CharOffset offset = CharOffset{ }); 344 | ReverseTreeWalker(const ReferenceSnapshot* snap, CharOffset offset = CharOffset{ }); 345 | ReverseTreeWalker(const TreeWalker&) = delete; 346 | 347 | char current(); 348 | char next(); 349 | void seek(CharOffset offset); 350 | bool exhausted() const; 351 | Length remaining() const; 352 | CharOffset offset() const 353 | { 354 | return total_offset; 355 | } 356 | 357 | // For Iterator-like behavior. 358 | ReverseTreeWalker& operator++() 359 | { 360 | return *this; 361 | } 362 | 363 | char operator*() 364 | { 365 | return next(); 366 | } 367 | private: 368 | void populate_ptrs(); 369 | void fast_forward_to(CharOffset offset); 370 | 371 | enum class Direction { Left, Center, Right }; 372 | 373 | struct StackEntry 374 | { 375 | PieceTree::RedBlackTree node; 376 | Direction dir = Direction::Right; 377 | }; 378 | 379 | const BufferCollection* buffers; 380 | RedBlackTree root; 381 | BufferMeta meta; 382 | std::vector stack; 383 | CharOffset total_offset = CharOffset{ 0 }; 384 | const char* first_ptr = nullptr; 385 | const char* last_ptr = nullptr; 386 | }; 387 | 388 | struct WalkSentinel { }; 389 | 390 | inline TreeWalker begin(const Tree& tree) 391 | { 392 | return TreeWalker{ &tree }; 393 | } 394 | 395 | constexpr WalkSentinel end(const Tree&) 396 | { 397 | return WalkSentinel{ }; 398 | } 399 | 400 | inline bool operator==(const TreeWalker& walker, WalkSentinel) 401 | { 402 | return walker.exhausted(); 403 | } 404 | 405 | enum class EmptySelection : bool { No, Yes }; 406 | 407 | struct SelectionMeta 408 | { 409 | OwningSnapshot snap; 410 | Offset first; 411 | Offset last; 412 | EmptySelection empty; 413 | }; 414 | } // namespace PieceTree -------------------------------------------------------------------------------- /fredbuf-test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "fredbuf.cpp" 7 | 8 | void assume_buffer_snapshots(const PieceTree::Tree* tree, std::string_view expected, PieceTree::CharOffset offset, std::source_location locus) 9 | { 10 | // Owning snapshot. 11 | { 12 | auto owning_snap = tree->owning_snap(); 13 | PieceTree::TreeWalker walker{ &owning_snap, offset }; 14 | std::string buf; 15 | while (not walker.exhausted()) 16 | { 17 | buf.push_back(walker.next()); 18 | } 19 | assert(walker.remaining() == PieceTree::Length{ 0 }); 20 | 21 | if (expected != buf) 22 | { 23 | auto s = std::format("owning snapshot buffer string '{}' did not match expected value of '{}'. Line({})", buf, expected, locus.line()); 24 | fprintf(stderr, "%s\n", s.c_str()); 25 | assert(false); 26 | } 27 | } 28 | // Reference snapshot. 29 | { 30 | auto owning_snap = tree->ref_snap(); 31 | PieceTree::TreeWalker walker{ &owning_snap, offset }; 32 | std::string buf; 33 | while (not walker.exhausted()) 34 | { 35 | buf.push_back(walker.next()); 36 | } 37 | assert(walker.remaining() == PieceTree::Length{ 0 }); 38 | 39 | if (expected != buf) 40 | { 41 | auto s = std::format("reference snapshot buffer string '{}' did not match expected value of '{}'. Line({})", buf, expected, locus.line()); 42 | fprintf(stderr, "%s\n", s.c_str()); 43 | assert(false); 44 | } 45 | } 46 | } 47 | 48 | void assume_reverse_buffer(const PieceTree::Tree* tree, std::string_view forward_buf, PieceTree::CharOffset offset, std::source_location locus) 49 | { 50 | PieceTree::ReverseTreeWalker walker{ tree, offset }; 51 | std::string buf; 52 | while (not walker.exhausted()) 53 | { 54 | buf.push_back(walker.next()); 55 | } 56 | assert(walker.remaining() == PieceTree::Length{ 0 }); 57 | 58 | // Walk 'forward_buf' in reverse and compare. 59 | auto rfirst = rbegin(forward_buf); 60 | auto rlast = rend(forward_buf); 61 | const bool result = std::equal(rfirst, rlast, begin(buf), end(buf)); 62 | if (not result) 63 | { 64 | auto s = std::format("Reversed buffer '{}' is not equal to forward buffer '{}'. Line({})", buf, forward_buf, locus.line()); 65 | fprintf(stderr, "%s\n", s.c_str()); 66 | assert(false); 67 | } 68 | } 69 | 70 | void assume_buffer(const PieceTree::Tree* tree, std::string_view expected, std::source_location locus = std::source_location::current()) 71 | { 72 | constexpr auto start = PieceTree::CharOffset{ 0 }; 73 | PieceTree::TreeWalker walker{ tree, start }; 74 | std::string buf; 75 | while (not walker.exhausted()) 76 | { 77 | buf.push_back(walker.next()); 78 | } 79 | assert(walker.remaining() == PieceTree::Length{ 0 }); 80 | 81 | if (expected != buf) 82 | { 83 | auto s = std::format("buffer string '{}' did not match expected value of '{}'. Line({})", buf, expected, locus.line()); 84 | fprintf(stderr, "%s\n", s.c_str()); 85 | assert(false); 86 | } 87 | assume_buffer_snapshots(tree, expected, start, locus); 88 | assume_reverse_buffer(tree, buf, start + retract(tree->length()), locus); 89 | } 90 | 91 | using namespace PieceTree; 92 | void test1() 93 | { 94 | TreeBuilder builder; 95 | std::string buf; 96 | builder.accept("A\nB\nC\nD"); 97 | auto tree = builder.create(); 98 | assume_buffer(&tree, "A\nB\nC\nD"); 99 | 100 | tree.remove(CharOffset{ 4 }, Length{ 1 }); 101 | tree.remove(CharOffset{ 3 }, Length{ 1 }); 102 | 103 | print_buffer(&tree); 104 | assume_buffer(&tree, "A\nB\nD"); 105 | } 106 | 107 | void test2() 108 | { 109 | TreeBuilder builder; 110 | std::string buf; 111 | auto tree = builder.create(); 112 | tree.insert(CharOffset{ 0 } + tree.length(), "a"); 113 | tree.insert(CharOffset{ 0 } + tree.length(), "s"); 114 | tree.insert(CharOffset{ 0 } + tree.length(), "d"); 115 | tree.insert(CharOffset{ 0 } + tree.length(), "f"); 116 | tree.insert(CharOffset{ 0 } + tree.length(), "\n"); 117 | tree.insert(CharOffset{ 0 } + tree.length(), "a"); 118 | tree.insert(CharOffset{ 0 } + tree.length(), "s"); 119 | tree.insert(CharOffset{ 0 } + tree.length(), "d"); 120 | tree.insert(CharOffset{ 0 } + tree.length(), "f"); 121 | tree.insert(CharOffset{ 0 } + tree.length(), "\n"); 122 | tree.insert(CharOffset{ 0 } + tree.length(), "a"); 123 | tree.insert(CharOffset{ 0 } + tree.length(), "s"); 124 | tree.insert(CharOffset{ 0 } + tree.length(), "d"); 125 | tree.insert(CharOffset{ 0 } + tree.length(), "f"); 126 | tree.insert(CharOffset{ 0 } + tree.length(), "\n"); 127 | tree.insert(CharOffset{ 0 } + tree.length(), "a"); 128 | tree.insert(CharOffset{ 0 } + tree.length(), "s"); 129 | tree.insert(CharOffset{ 0 } + tree.length(), "d"); 130 | tree.insert(CharOffset{ 0 } + tree.length(), "f"); 131 | tree.insert(CharOffset{ 0 } + tree.length(), "\n"); 132 | #if 1 133 | tree.insert(CharOffset{ 1 }, "a"); 134 | tree.insert(CharOffset{ 2 }, "s"); 135 | tree.insert(CharOffset{ 3 }, "d"); 136 | tree.insert(CharOffset{ 4 }, "f"); 137 | tree.insert(CharOffset{ 5 }, "\n"); 138 | tree.insert(CharOffset{ 6 }, "a"); 139 | tree.insert(CharOffset{ 12 }, "s"); 140 | tree.insert(CharOffset{ 15 }, "d"); 141 | tree.insert(CharOffset{ 17 }, "f"); 142 | tree.insert(CharOffset{ 18 }, "\n"); 143 | tree.insert(CharOffset{ 2 }, "a"); 144 | tree.insert(CharOffset{ 21 }, "s"); 145 | tree.insert(CharOffset{ 21 }, "d"); 146 | tree.insert(CharOffset{ 23 }, "f"); 147 | tree.insert(CharOffset{ 29 }, "\n"); 148 | tree.insert(CharOffset{ 30 }, "a"); 149 | tree.insert(CharOffset{ 0 }, "s"); 150 | tree.insert(CharOffset{ 1 }, "d"); 151 | tree.insert(CharOffset{ 10 }, "f"); 152 | tree.insert(CharOffset{ 11 }, "\n"); 153 | #endif 154 | auto line = Line{ 1 }; 155 | auto range = tree.get_line_range(line); 156 | printf("Line{%zu} range: first{%zu} last{%zu}\n", line, range.first, range.last); 157 | tree.get_line_content(&buf, line); 158 | printf("content: %s\n", buf.c_str()); 159 | printf("Line number: %zu\n", tree.line_at(range.first)); 160 | 161 | print_buffer(&tree); 162 | 163 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 164 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 165 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 166 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 167 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 168 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 169 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 170 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 171 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 172 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 173 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 174 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 175 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 176 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 177 | tree.remove(CharOffset{ 5 }, Length{ 1 }); 178 | 179 | print_buffer(&tree); 180 | assume_buffer(&tree, "sdaaadff\n\ndsfasdf\n\naasdf\n"); 181 | } 182 | 183 | void test3() 184 | { 185 | TreeBuilder builder; 186 | builder.accept("Hello"); 187 | builder.accept(","); 188 | builder.accept(" "); 189 | builder.accept("World"); 190 | builder.accept("!"); 191 | builder.accept("\nThis is a second line."); 192 | builder.accept(" Continue...\nANOTHER!"); 193 | 194 | auto tree = builder.create(); 195 | 196 | print_tree(tree); 197 | 198 | std::string buf; 199 | 200 | printf("line content at 1:\n"); 201 | tree.get_line_content(&buf, Line{ 1 }); 202 | printf("%.*s\n", int(buf.size()), buf.c_str()); 203 | 204 | printf("line content at 2:\n"); 205 | tree.get_line_content(&buf, Line{ 2 }); 206 | printf("%.*s\n", int(buf.size()), buf.c_str()); 207 | 208 | printf("line content at 3:\n"); 209 | tree.get_line_content(&buf, Line{ 3 }); 210 | printf("%.*s\n", int(buf.size()), buf.c_str()); 211 | 212 | tree.insert(CharOffset{ 37 }, "Hello"); 213 | 214 | printf("line content at 1:\n"); 215 | tree.get_line_content(&buf, Line{ 1 }); 216 | printf("%.*s\n", int(buf.size()), buf.c_str()); 217 | 218 | printf("line content at 2:\n"); 219 | tree.get_line_content(&buf, Line{ 2 }); 220 | printf("%.*s\n", int(buf.size()), buf.c_str()); 221 | 222 | print_buffer(&tree); 223 | 224 | auto off = CharOffset{ 13 }; 225 | auto len = Length{ 5 }; 226 | printf("--- Delete at off{%zu}, len{%zu} ---\n", off, len); 227 | tree.remove(off, len); 228 | 229 | #if 0 230 | printf("line content at 1:\n"); 231 | tree.get_line_content(&buf, Line{ 1 }); 232 | printf("%.*s\n", int(buf.size()), buf.c_str()); 233 | 234 | printf("line content at 2:\n"); 235 | tree.get_line_content(&buf, Line{ 2 }); 236 | printf("%.*s\n", int(buf.size()), buf.c_str()); 237 | #endif 238 | 239 | print_buffer(&tree); 240 | 241 | off = CharOffset{ 37 }; 242 | len = Length{ 5 }; 243 | printf("--- Delete at off{%zu}, len{%zu} ---\n", off, len); 244 | tree.remove(off, len); 245 | 246 | #if 0 247 | printf("line content at 1:\n"); 248 | tree.get_line_content(&buf, Line{ 1 }); 249 | printf("%.*s\n", int(buf.size()), buf.c_str()); 250 | 251 | printf("line content at 2:\n"); 252 | tree.get_line_content(&buf, Line{ 2 }); 253 | printf("%.*s\n", int(buf.size()), buf.c_str()); 254 | #endif 255 | 256 | print_buffer(&tree); 257 | 258 | tree.insert(CharOffset{ 0 } + tree.length(), "a"); 259 | print_buffer(&tree); 260 | 261 | tree.insert(CharOffset{ 0 } + tree.length(), "a"); 262 | print_buffer(&tree); 263 | 264 | tree.insert(CharOffset{ 0 } + tree.length(), "a"); 265 | print_buffer(&tree); 266 | 267 | tree.insert(CharOffset{ 0 } + tree.length(), "a"); 268 | print_buffer(&tree); 269 | 270 | tree.insert(CharOffset{ 0 } + tree.length(), "END!!"); 271 | print_buffer(&tree); 272 | 273 | tree.remove(CharOffset{ 52 }, Length{ 4 }); 274 | print_buffer(&tree); 275 | 276 | //print_buffer(&tree, CharOffset{ 26 }); 277 | 278 | tree.insert(CharOffset{} + tree.length(), "\nfoobar\nnext\nnextnext\nnextnextnext"); 279 | tree.insert(CharOffset{} + tree.length(), "\nfoobar2\nnext\nnextnext\nnextnextnext"); 280 | 281 | print_buffer(&tree); 282 | 283 | auto total_lines = Line{ rep(tree.line_feed_count()) + 1 }; 284 | for (Line line = Line{ 1 }; line <= total_lines;line = extend(line)) 285 | { 286 | auto range = tree.get_line_range(line); 287 | printf("Line{%zu} range: first{%zu} last{%zu}\n", line, range.first, range.last); 288 | tree.get_line_content(&buf, line); 289 | printf("content: %s\n", buf.c_str()); 290 | printf("Line number: %zu\n", tree.line_at(range.first)); 291 | } 292 | 293 | printf("out of range line:\n"); 294 | auto range = tree.get_line_range(Line{ 99 }); 295 | printf("Line{%zu} range: first{%zu} last{%zu}\n", size_t{99}, range.first, range.last); 296 | tree.get_line_content(&buf, Line{ 99 }); 297 | printf("content: %s\n", buf.c_str()); 298 | printf("Line number: %zu\n", tree.line_at(range.first)); 299 | #if 0 300 | tree.remove(PieceTree::CharOffset{ 37 }, PieceTree::Length{ 5 }); 301 | 302 | printf("line content at 1:\n"); 303 | tree.get_line_content(&buf, PieceTree::Line{ 1 }); 304 | printf("%.*s\n", int(buf.size()), buf.c_str()); 305 | 306 | printf("line content at 2:\n"); 307 | tree.get_line_content(&buf, PieceTree::Line{ 2 }); 308 | printf("%.*s\n", int(buf.size()), buf.c_str()); 309 | 310 | tree.remove(PieceTree::CharOffset{ 25 }, PieceTree::Length{ 5 }); 311 | 312 | printf("line content at 1:\n"); 313 | tree.get_line_content(&buf, PieceTree::Line{ 1 }); 314 | printf("%.*s\n", int(buf.size()), buf.c_str()); 315 | 316 | printf("line content at 2:\n"); 317 | tree.get_line_content(&buf, PieceTree::Line{ 2 }); 318 | printf("%.*s\n", int(buf.size()), buf.c_str()); 319 | #endif 320 | } 321 | 322 | void test4() 323 | { 324 | TreeBuilder builder; 325 | std::string buf; 326 | builder.accept("ABCD"); 327 | auto tree = builder.create(); 328 | 329 | tree.insert(CharOffset{4}, "a"); 330 | 331 | assume_buffer(&tree, "ABCDa"); 332 | 333 | tree.remove(CharOffset{3}, Length{2}); 334 | 335 | assume_buffer(&tree, "ABC"); 336 | } 337 | 338 | void test5() 339 | { 340 | TreeBuilder builder; 341 | std::string buf; 342 | builder.accept(""); 343 | auto tree = builder.create(); 344 | 345 | tree.insert(CharOffset{ 0 }, "a"); 346 | 347 | assume_buffer(&tree, "a"); 348 | 349 | tree.remove(CharOffset{ 0 }, Length{ 1 }); 350 | 351 | assume_buffer(&tree, ""); 352 | } 353 | 354 | void test6() 355 | { 356 | TreeBuilder builder; 357 | std::string buf; 358 | builder.accept("Hello, World!"); 359 | auto tree = builder.create(); 360 | 361 | tree.insert(CharOffset{ 0 }, "a"); 362 | tree.insert(CharOffset{ 1 }, "b"); 363 | tree.insert(CharOffset{ 2 }, "c"); 364 | 365 | assume_buffer(&tree, "abcHello, World!"); 366 | 367 | tree.remove(CharOffset{ 0 }, Length{ 3 }); 368 | 369 | assume_buffer(&tree, "Hello, World!"); 370 | 371 | auto r = tree.try_undo(CharOffset{ 0 }); 372 | assert(r.success); 373 | 374 | assume_buffer(&tree, "abcHello, World!"); 375 | 376 | r = tree.try_redo(CharOffset{ 0 }); 377 | assert(r.success); 378 | 379 | assume_buffer(&tree, "Hello, World!"); 380 | 381 | r = tree.try_undo(CharOffset{ 0 }); 382 | assert(r.success); 383 | 384 | assume_buffer(&tree, "abcHello, World!"); 385 | 386 | r = tree.try_undo(CharOffset{ 0 }); 387 | assert(r.success); 388 | 389 | assume_buffer(&tree, "Hello, World!"); 390 | 391 | r = tree.try_undo(CharOffset{ 0 }); 392 | assert(not r.success); 393 | 394 | r = tree.try_redo(CharOffset{ 0 }); 395 | assert(r.success); 396 | 397 | assume_buffer(&tree, "abcHello, World!"); 398 | 399 | r = tree.try_undo(CharOffset{ 0 }); 400 | assert(r.success); 401 | 402 | assume_buffer(&tree, "Hello, World!"); 403 | 404 | // Destroy the redo stack. 405 | 406 | tree.insert(CharOffset{ 0 }, "NEW"); 407 | 408 | assume_buffer(&tree, "NEWHello, World!"); 409 | 410 | r = tree.try_redo(CharOffset{ 0 }); 411 | assert(not r.success); 412 | 413 | r = tree.try_undo(CharOffset{ 0 }); 414 | assert(r.success); 415 | 416 | assume_buffer(&tree, "Hello, World!"); 417 | } 418 | 419 | void test7() 420 | { 421 | TreeBuilder builder; 422 | std::string buf; 423 | builder.accept("ABC"); 424 | builder.accept("DEF"); 425 | auto tree = builder.create(); 426 | 427 | tree.insert(CharOffset{ 0 }, "foo"); 428 | 429 | assume_buffer(&tree, "fooABCDEF"); 430 | 431 | tree.remove(CharOffset{ 6 }, Length{ 3 }); 432 | 433 | assume_buffer(&tree, "fooABC"); 434 | 435 | tree.get_line_content(&buf, Line{ 1 }); 436 | 437 | assert(buf == "fooABC"); 438 | 439 | for (char c : buf) 440 | { 441 | printf("%c", c); 442 | } 443 | printf("\n"); 444 | } 445 | 446 | void test8() 447 | { 448 | TreeBuilder builder; 449 | std::string buf; 450 | builder.accept("Hello, World!"); 451 | auto tree = builder.create(); 452 | 453 | tree.insert(CharOffset{ 0 }, "a", PieceTree::SuppressHistory::Yes); 454 | assume_buffer(&tree, "aHello, World!"); 455 | 456 | auto r = tree.try_undo(CharOffset{ 0 }); 457 | assert(not r.success); 458 | 459 | tree.remove(CharOffset{ 0 }, Length{ 1 }, PieceTree::SuppressHistory::Yes); 460 | assume_buffer(&tree, "Hello, World!"); 461 | 462 | r = tree.try_undo(CharOffset{ 0 }); 463 | assert(not r.success); 464 | 465 | // Snap back to "Hello, World!" 466 | tree.commit_head(CharOffset{ 0 }); 467 | tree.insert(CharOffset{ 0 }, "a", PieceTree::SuppressHistory::Yes); 468 | tree.insert(CharOffset{ 1 }, "b", PieceTree::SuppressHistory::Yes); 469 | tree.insert(CharOffset{ 2 }, "c", PieceTree::SuppressHistory::Yes); 470 | assume_buffer(&tree, "abcHello, World!"); 471 | 472 | r = tree.try_undo(CharOffset{ 0 }); 473 | assert(r.success); 474 | assume_buffer(&tree, "Hello, World!"); 475 | 476 | // Snap back to "Hello, World!" 477 | tree.commit_head(CharOffset{ 0 }); 478 | tree.remove(CharOffset{ 0 }, Length{ 7 }, PieceTree::SuppressHistory::Yes); 479 | assume_buffer(&tree, "World!"); 480 | 481 | tree.remove(CharOffset{ 5 }, Length{ 1 }, PieceTree::SuppressHistory::Yes); 482 | assume_buffer(&tree, "World"); 483 | 484 | r = tree.try_undo(CharOffset{ 0 }); 485 | assert(r.success); 486 | assume_buffer(&tree, "Hello, World!"); 487 | 488 | r = tree.try_redo(CharOffset{ 0 }); 489 | assume_buffer(&tree, "World"); 490 | } 491 | 492 | void test9() 493 | { 494 | TreeBuilder builder; 495 | std::string buf; 496 | builder.accept("Hello, World!"); 497 | auto tree = builder.create(); 498 | 499 | auto initial_commit = tree.head(); 500 | 501 | tree.insert(CharOffset{ 0 }, "a", PieceTree::SuppressHistory::Yes); 502 | assume_buffer(&tree, "aHello, World!"); 503 | 504 | auto r = tree.try_undo(CharOffset{ 0 }); 505 | assert(not r.success); 506 | 507 | auto commit = tree.head(); 508 | tree.snap_to(initial_commit); 509 | assume_buffer(&tree, "Hello, World!"); 510 | 511 | tree.snap_to(commit); 512 | assume_buffer(&tree, "aHello, World!"); 513 | 514 | tree.remove(CharOffset{ 0 }, Length{ 8 }, PieceTree::SuppressHistory::Yes); 515 | assume_buffer(&tree, "World!"); 516 | 517 | tree.snap_to(commit); 518 | assume_buffer(&tree, "aHello, World!"); 519 | 520 | tree.snap_to(initial_commit); 521 | assume_buffer(&tree, "Hello, World!"); 522 | 523 | // Create a new branch. 524 | tree.insert(CharOffset{ 13 }, " My name is fredbuf.", PieceTree::SuppressHistory::Yes); 525 | assume_buffer(&tree, "Hello, World! My name is fredbuf."); 526 | 527 | auto branch = tree.head(); 528 | 529 | // Revert back. 530 | tree.snap_to(commit); 531 | assume_buffer(&tree, "aHello, World!"); 532 | 533 | // Revert back to branch. 534 | tree.snap_to(branch); 535 | assume_buffer(&tree, "Hello, World! My name is fredbuf."); 536 | } 537 | 538 | #ifdef TIMING_DATA 539 | #include 540 | 541 | struct Stopwatch 542 | { 543 | using Clock = std::chrono::high_resolution_clock; 544 | 545 | void start() 546 | { 547 | start_ = Clock::now(); 548 | } 549 | 550 | void stop() 551 | { 552 | stop_ = Clock::now(); 553 | } 554 | 555 | Clock::duration ticks() const 556 | { 557 | return stop_ - start_; 558 | } 559 | 560 | // helpers 561 | template 562 | Tick to_ticks() const 563 | { 564 | return std::chrono::duration_cast(ticks()); 565 | } 566 | 567 | std::chrono::microseconds to_us() const 568 | { 569 | return to_ticks(); 570 | } 571 | 572 | Clock::time_point start_ = { }; 573 | Clock::time_point stop_ = { }; 574 | }; 575 | 576 | #define EachIndex(i,n) (uint64_t i=0;i(total_count) / timing_count; 632 | printf("Average: %.2fus\n", mean); 633 | } 634 | 635 | // In-place insertions in the middle. 636 | { 637 | for EachIndex(i, timing_count) 638 | { 639 | tree.snap_to(initial_commit); 640 | auto mid = extend(CharOffset{}, (rep(tree.length()) / 2)); 641 | std::string_view ins_txt = "a"; 642 | sw.start(); 643 | for (int j = 0; j < 100; ++j) 644 | { 645 | // Don't change 'mid' to append to the string. 646 | tree.insert(mid, ins_txt, SuppressHistory::Yes); 647 | } 648 | sw.stop(); 649 | timing_data[i] = sw.to_us(); 650 | assume_buffer(&tree, inserted_buf_expected); 651 | } 652 | // Aggregate and display. 653 | printf("---------- In-place insertions ----------\n"); 654 | int64_t total_count = 0; 655 | for EachIndex(i, timing_count) 656 | { 657 | printf("[%u] = %ldus\n", unsigned(i), long(timing_data[i].count())); 658 | total_count += timing_data[i].count(); 659 | } 660 | // Find mean. 661 | double mean = static_cast(total_count) / timing_count; 662 | printf("Average: %.2fus\n", mean); 663 | } 664 | 665 | // Deleting characters to beginning starting at middle. 666 | { 667 | for EachIndex(i, timing_count) 668 | { 669 | tree.snap_to(initial_commit); 670 | auto mid = extend(CharOffset{}, (rep(tree.length()) / 2)); 671 | sw.start(); 672 | while (mid != CharOffset::Sentinel) 673 | { 674 | // Don't change 'mid' to append to the string. 675 | tree.remove(mid, Length{ 1 }, SuppressHistory::Yes); 676 | mid = retract(mid); 677 | } 678 | sw.stop(); 679 | timing_data[i] = sw.to_us(); 680 | assume_buffer(&tree, upper_half); 681 | } 682 | // Aggregate and display. 683 | printf("---------- Deletion starting at middle ----------\n"); 684 | int64_t total_count = 0; 685 | for EachIndex(i, timing_count) 686 | { 687 | printf("[%u] = %ldus\n", unsigned(i), long(timing_data[i].count())); 688 | total_count += timing_data[i].count(); 689 | } 690 | // Find mean. 691 | double mean = static_cast(total_count) / timing_count; 692 | printf("Average: %.2fus\n", mean); 693 | } 694 | 695 | // Deleting half the characters starting at beginning. 696 | { 697 | for EachIndex(i, timing_count) 698 | { 699 | tree.snap_to(initial_commit); 700 | // This deletion needs to be inclusive of the midpoint (to be consistent with deletion above). 701 | auto len_to_del = Length{ (rep(tree.length()) / 2) + 1 }; 702 | sw.start(); 703 | while (len_to_del != Length{}) 704 | { 705 | // Don't change 'mid' to append to the string. 706 | tree.remove(CharOffset{}, Length{ 1 }, SuppressHistory::Yes); 707 | len_to_del = retract(len_to_del); 708 | } 709 | sw.stop(); 710 | timing_data[i] = sw.to_us(); 711 | assume_buffer(&tree, upper_half); 712 | } 713 | // Aggregate and display. 714 | printf("---------- Deletion starting at beginning ----------\n"); 715 | int64_t total_count = 0; 716 | for EachIndex(i, timing_count) 717 | { 718 | printf("[%u] = %ldus\n", unsigned(i), long(timing_data[i].count())); 719 | total_count += timing_data[i].count(); 720 | } 721 | // Find mean. 722 | double mean = static_cast(total_count) / timing_count; 723 | printf("Average: %.2fus\n", mean); 724 | } 725 | } 726 | #endif // TIMING_DATA 727 | 728 | int main() 729 | { 730 | test1(); 731 | test2(); 732 | test3(); 733 | test4(); 734 | test5(); 735 | test6(); 736 | test7(); 737 | test8(); 738 | test9(); 739 | #ifdef TIMING_DATA 740 | time_buffer(); 741 | #endif // TIMING_DATA 742 | } -------------------------------------------------------------------------------- /fredbuf.cpp: -------------------------------------------------------------------------------- 1 | #include "fredbuf.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "enum-utils.h" 11 | #include "scope-guard.h" 12 | 13 | namespace PieceTree 14 | { 15 | constexpr LFCount operator+(LFCount lhs, LFCount rhs) 16 | { 17 | return LFCount{ rep(lhs) + rep(rhs) }; 18 | } 19 | 20 | RedBlackTree::Node::Node(Color c, const NodePtr& lft, const NodeData& data, const NodePtr& rgt) 21 | : color(c), left(lft), data(data), right(rgt) 22 | { 23 | } 24 | 25 | const RedBlackTree::Node* RedBlackTree::root_ptr() const 26 | { 27 | return root_node.get(); 28 | } 29 | 30 | bool RedBlackTree::is_empty() const 31 | { 32 | return not root_node; 33 | } 34 | 35 | const NodeData& RedBlackTree::root() const 36 | { 37 | assert(not is_empty()); 38 | return root_node->data; 39 | } 40 | 41 | RedBlackTree RedBlackTree::left() const 42 | { 43 | assert(not is_empty()); 44 | return RedBlackTree(root_node->left); 45 | } 46 | 47 | RedBlackTree RedBlackTree::right() const 48 | { 49 | assert(not is_empty()); 50 | return RedBlackTree(root_node->right); 51 | } 52 | 53 | Color RedBlackTree::root_color() const 54 | { 55 | assert(!is_empty()); 56 | return root_node->color; 57 | } 58 | 59 | RedBlackTree RedBlackTree::insert(const NodeData& x, Offset at) const 60 | { 61 | RedBlackTree t = ins(x, at, Offset{ 0 }); 62 | return RedBlackTree(Color::Black, t.left(), t.root(), t.right()); 63 | } 64 | 65 | RedBlackTree::RedBlackTree(Color c, 66 | const RedBlackTree& lft, 67 | const NodeData& val, 68 | const RedBlackTree& rgt) 69 | : root_node(std::make_shared(c, lft.root_node, attribute(val, lft), rgt.root_node)) 70 | { 71 | } 72 | 73 | RedBlackTree::RedBlackTree(const NodePtr& node) 74 | : root_node(node) 75 | { 76 | } 77 | 78 | RedBlackTree RedBlackTree::ins(const NodeData& x, Offset at, Offset total_offset) const 79 | { 80 | if (is_empty()) 81 | return RedBlackTree(Color::Red, RedBlackTree(), x, RedBlackTree()); 82 | const NodeData& y = root(); 83 | if (at < total_offset + y.left_subtree_length + y.piece.length) 84 | return balance(root_color(), left().ins(x, at, total_offset), y, right()); 85 | return balance(root_color(), left(), y, right().ins(x, at, total_offset + y.left_subtree_length + y.piece.length)); 86 | } 87 | 88 | RedBlackTree RedBlackTree::balance(Color c, const RedBlackTree& lft, const NodeData& x, const RedBlackTree& rgt) 89 | { 90 | if (c == Color::Black and lft.doubled_left()) 91 | return RedBlackTree(Color::Red, 92 | lft.left().paint(Color::Black), 93 | lft.root(), 94 | RedBlackTree(Color::Black, 95 | lft.right(), 96 | x, 97 | rgt)); 98 | else if (c == Color::Black and lft.doubled_right()) 99 | return RedBlackTree(Color::Red, 100 | RedBlackTree(Color::Black, 101 | lft.left(), 102 | lft.root(), 103 | lft.right().left()), 104 | lft.right().root(), 105 | RedBlackTree(Color::Black, 106 | lft.right().right(), 107 | x, 108 | rgt)); 109 | else if (c == Color::Black and rgt.doubled_left()) 110 | return RedBlackTree(Color::Red, 111 | RedBlackTree(Color::Black, 112 | lft, 113 | x, 114 | rgt.left().left()), 115 | rgt.left().root(), 116 | RedBlackTree(Color::Black, 117 | rgt.left().right(), 118 | rgt.root(), 119 | rgt.right())); 120 | else if (c == Color::Black and rgt.doubled_right()) 121 | return RedBlackTree(Color::Red, 122 | RedBlackTree(Color::Black, 123 | lft, 124 | x, 125 | rgt.left()), 126 | rgt.root(), 127 | rgt.right().paint(Color::Black)); 128 | return RedBlackTree(c, lft, x, rgt); 129 | } 130 | 131 | bool RedBlackTree::doubled_left() const 132 | { 133 | return not is_empty() 134 | and root_color() == Color::Red 135 | and not left().is_empty() 136 | and left().root_color() == Color::Red; 137 | } 138 | 139 | bool RedBlackTree::doubled_right() const 140 | { 141 | return not is_empty() 142 | and root_color() == Color::Red 143 | and not right().is_empty() 144 | and right().root_color() == Color::Red; 145 | } 146 | 147 | RedBlackTree RedBlackTree::paint(Color c) const 148 | { 149 | assert(not is_empty()); 150 | return RedBlackTree(c, left(), root(), right()); 151 | } 152 | 153 | PieceTree::Length tree_length(const RedBlackTree& root) 154 | { 155 | if (root.is_empty()) 156 | return { }; 157 | return root.root().left_subtree_length + root.root().piece.length + tree_length(root.right()); 158 | } 159 | 160 | PieceTree::LFCount tree_lf_count(const RedBlackTree& root) 161 | { 162 | if (root.is_empty()) 163 | return { }; 164 | return root.root().left_subtree_lf_count + root.root().piece.newline_count + tree_lf_count(root.right()); 165 | } 166 | 167 | NodeData attribute(const NodeData& data, const RedBlackTree& left) 168 | { 169 | auto new_data = data; 170 | new_data.left_subtree_length = tree_length(left); 171 | new_data.left_subtree_lf_count = tree_lf_count(left); 172 | return new_data; 173 | } 174 | 175 | struct RedBlackTree::ColorTree 176 | { 177 | const Color color; 178 | const RedBlackTree tree; 179 | 180 | static ColorTree double_black() 181 | { 182 | return ColorTree(); 183 | } 184 | 185 | explicit ColorTree(RedBlackTree const &tree) 186 | : color(tree.is_empty() ? Color::Black : tree.root_color()), tree(tree) 187 | { 188 | } 189 | 190 | explicit ColorTree(Color c, const RedBlackTree& lft, const NodeData& x, const RedBlackTree& rgt) 191 | : color(c), tree(c, lft, x, rgt) 192 | { 193 | } 194 | 195 | private: 196 | ColorTree(): color(Color::DoubleBlack) 197 | { 198 | } 199 | }; 200 | 201 | struct WalkResult 202 | { 203 | RedBlackTree tree; 204 | Offset accumulated_offset; 205 | }; 206 | 207 | WalkResult pred(const RedBlackTree& root, Offset start_offset) 208 | { 209 | RedBlackTree t = root.left(); 210 | while (!t.right().is_empty()) 211 | { 212 | start_offset = start_offset + t.root().left_subtree_length + t.root().piece.length; 213 | t = t.right(); 214 | } 215 | // Add the final offset from the last right node. 216 | start_offset = start_offset + t.root().left_subtree_length; 217 | return { .tree = t, .accumulated_offset = start_offset }; 218 | } 219 | 220 | 221 | #ifdef EXPERIMENTAL_REMOVE 222 | RedBlackTree RedBlackTree::remove(Offset at) const 223 | { 224 | auto t = rem(at, Offset{ 0 }).tree; 225 | if (t.is_empty()) 226 | return RedBlackTree(); 227 | return RedBlackTree(Color::Black, t.left(), t.root(), t.right()); 228 | } 229 | 230 | RedBlackTree::ColorTree RedBlackTree::remove_double_black(Color c, ColorTree const &lft, const NodeData& x, ColorTree const &rgt) 231 | { 232 | if (lft.color == Color::DoubleBlack) 233 | { 234 | auto left = lft.tree.is_empty() ? RedBlackTree() : lft.tree.paint(Color::Black); 235 | 236 | if (rgt.color == Color::Black) 237 | { 238 | assert(c != Color::DoubleBlack); 239 | return ColorTree(extend(c), left, x, rgt.tree.paint(Color::Red)); 240 | } 241 | else 242 | return ColorTree(Color::Black, RedBlackTree(Color::Black, left, x, rgt.tree.left().paint(Color::Red)), rgt.tree.root(), rgt.tree.right()); 243 | } 244 | else if (rgt.color == Color::DoubleBlack) 245 | { 246 | auto right = rgt.tree.is_empty() ? RedBlackTree() : rgt.tree.paint(Color::Black); 247 | 248 | if (lft.color == Color::Black) 249 | { 250 | assert(c != Color::DoubleBlack); 251 | return ColorTree(extend(c), lft.tree.paint(Color::Red), x, right); 252 | } 253 | else 254 | return ColorTree(Color::Black, lft.tree.left(), lft.tree.root(), RedBlackTree(Color::Black, lft.tree.right().paint(Color::Red), x, right)); 255 | } 256 | else 257 | return ColorTree(c, lft.tree, x, rgt.tree); 258 | } 259 | 260 | RedBlackTree::ColorTree RedBlackTree::rem(Offset at, Offset total) const 261 | { 262 | if (is_empty()) 263 | return ColorTree(RedBlackTree()); 264 | const NodeData& y = root(); 265 | if (at < total + y.left_subtree_length) 266 | return remove_double_black(root_color(), left().rem(at, total), y, ColorTree(right())); 267 | if (at == total + y.left_subtree_length) 268 | return remove_node(); 269 | return remove_double_black(root_color(), ColorTree(left()), y, right().rem(at, total + y.left_subtree_length + y.piece.length)); 270 | } 271 | 272 | RedBlackTree::ColorTree RedBlackTree::remove_node() const 273 | { 274 | if (not left().is_empty() 275 | and not right().is_empty()) 276 | { 277 | auto [p, off] = pred(*this, Offset(0)); 278 | const NodeData& x = p.root(); 279 | 280 | Color c = root_color(); 281 | 282 | return remove_double_black(c, left().rem(off, Offset(0)), x, ColorTree(right())); 283 | } 284 | else if (not left().is_empty()) 285 | { 286 | return ColorTree(left().paint(Color::Black)); 287 | } 288 | else if (not right().is_empty()) 289 | { 290 | return ColorTree(right().paint(Color::Black)); 291 | } 292 | else if (root_color() == Color::Black) 293 | { 294 | return ColorTree::double_black(); 295 | } 296 | return ColorTree(RedBlackTree()); 297 | } 298 | #else 299 | RedBlackTree RedBlackTree::remove(Offset at) const 300 | { 301 | auto t = rem(*this, at, Offset{ 0 }); 302 | if (t.is_empty()) 303 | return RedBlackTree(); 304 | return RedBlackTree(Color::Black, t.left(), t.root(), t.right()); 305 | } 306 | 307 | RedBlackTree RedBlackTree::fuse(const RedBlackTree& left, const RedBlackTree& right) 308 | { 309 | // match: (left, right) 310 | // case: (None, r) 311 | if (left.is_empty()) 312 | return right; 313 | if (right.is_empty()) 314 | return left; 315 | // match: (left.color, right.color) 316 | // case: (B, R) 317 | if (left.root_color() == Color::Black and right.root_color() == Color::Red) 318 | { 319 | return RedBlackTree(Color::Red, 320 | fuse(left, right.left()), 321 | right.root(), 322 | right.right()); 323 | } 324 | // case: (R, B) 325 | if (left.root_color() == Color::Red and right.root_color() == Color::Black) 326 | { 327 | return RedBlackTree(Color::Red, 328 | left.left(), 329 | left.root(), 330 | fuse(left.right(), right)); 331 | } 332 | // case: (R, R) 333 | if (left.root_color() == Color::Red and right.root_color() == Color::Red) 334 | { 335 | auto fused = fuse(left.right(), right.left()); 336 | if (not fused.is_empty() and fused.root_color() == Color::Red) 337 | { 338 | auto new_left = RedBlackTree(Color::Red, 339 | left.left(), 340 | left.root(), 341 | fused.left()); 342 | auto new_right = RedBlackTree(Color::Red, 343 | fused.right(), 344 | right.root(), 345 | right.right()); 346 | return RedBlackTree(Color::Red, 347 | new_left, 348 | fused.root(), 349 | new_right); 350 | } 351 | auto new_right = RedBlackTree(Color::Red, 352 | fused, 353 | right.root(), 354 | right.right()); 355 | return RedBlackTree(Color::Red, 356 | left.left(), 357 | left.root(), 358 | new_right); 359 | } 360 | // case: (B, B) 361 | assert(left.root_color() == right.root_color() and left.root_color() == Color::Black); 362 | auto fused = fuse(left.right(), right.left()); 363 | if (not fused.is_empty() and fused.root_color() == Color::Red) 364 | { 365 | auto new_left = RedBlackTree(Color::Black, 366 | left.left(), 367 | left.root(), 368 | fused.left()); 369 | auto new_right = RedBlackTree(Color::Black, 370 | fused.right(), 371 | right.root(), 372 | right.right()); 373 | return RedBlackTree(Color::Red, 374 | new_left, 375 | fused.root(), 376 | new_right); 377 | } 378 | auto new_right = RedBlackTree(Color::Black, 379 | fused, 380 | right.root(), 381 | right.right()); 382 | auto new_node = RedBlackTree(Color::Red, 383 | left.left(), 384 | left.root(), 385 | new_right); 386 | return balance_left(new_node); 387 | } 388 | 389 | RedBlackTree RedBlackTree::balance(const RedBlackTree& node) 390 | { 391 | // Two red children. 392 | if (not node.left().is_empty() 393 | and node.left().root_color() == Color::Red 394 | and not node.right().is_empty() 395 | and node.right().root_color() == Color::Red) 396 | { 397 | auto l = node.left().paint(Color::Black); 398 | auto r = node.right().paint(Color::Black); 399 | return RedBlackTree(Color::Red, 400 | l, 401 | node.root(), 402 | r); 403 | } 404 | 405 | assert(node.root_color() == Color::Black); 406 | return balance(node.root_color(), node.left(), node.root(), node.right()); 407 | } 408 | 409 | RedBlackTree RedBlackTree::balance_left(const RedBlackTree& left) 410 | { 411 | // match: (color_l, color_r, color_r_l) 412 | // case: (Some(R), ..) 413 | if (not left.left().is_empty() and left.left().root_color() == Color::Red) 414 | { 415 | return RedBlackTree(Color::Red, 416 | left.left().paint(Color::Black), 417 | left.root(), 418 | left.right()); 419 | } 420 | // case: (_, Some(B), _) 421 | if (not left.right().is_empty() and left.right().root_color() == Color::Black) 422 | { 423 | auto new_left = RedBlackTree(Color::Black, 424 | left.left(), 425 | left.root(), 426 | left.right().paint(Color::Red)); 427 | return balance(new_left); 428 | } 429 | // case: (_, Some(R), Some(B)) 430 | if (not left.right().is_empty() and left.right().root_color() == Color::Red 431 | and not left.right().left().is_empty() and left.right().left().root_color() == Color::Black) 432 | { 433 | auto unbalanced_new_right = RedBlackTree(Color::Black, 434 | left.right().left().right(), 435 | left.right().root(), 436 | left.right().right().paint(Color::Red)); 437 | auto new_right = balance(unbalanced_new_right); 438 | auto new_left = RedBlackTree(Color::Black, 439 | left.left(), 440 | left.root(), 441 | left.right().left().left()); 442 | return RedBlackTree(Color::Red, 443 | new_left, 444 | left.right().left().root(), 445 | new_right); 446 | } 447 | assert(!"impossible"); 448 | return left; 449 | } 450 | 451 | RedBlackTree RedBlackTree::balance_right(const RedBlackTree& right) 452 | { 453 | // match: (color_l, color_l_r, color_r) 454 | // case: (.., Some(R)) 455 | if (not right.right().is_empty() and right.right().root_color() == Color::Red) 456 | { 457 | return RedBlackTree(Color::Red, 458 | right.left(), 459 | right.root(), 460 | right.right().paint(Color::Black)); 461 | } 462 | // case: (Some(B), ..) 463 | if (not right.left().is_empty() and right.left().root_color() == Color::Black) 464 | { 465 | auto new_right = RedBlackTree(Color::Black, 466 | right.left().paint(Color::Red), 467 | right.root(), 468 | right.right()); 469 | return balance(new_right); 470 | } 471 | // case: (Some(R), Some(B), _) 472 | if (not right.left().is_empty() and right.left().root_color() == Color::Red 473 | and not right.left().right().is_empty() and right.left().right().root_color() == Color::Black) 474 | { 475 | auto unbalanced_new_left = RedBlackTree(Color::Black, 476 | // Note: Because 'left' is red, it must have a left child. 477 | right.left().left().paint(Color::Red), 478 | right.left().root(), 479 | right.left().right().left()); 480 | auto new_left = balance(unbalanced_new_left); 481 | auto new_right = RedBlackTree(Color::Black, 482 | right.left().right().right(), 483 | right.root(), 484 | right.right()); 485 | return RedBlackTree(Color::Red, 486 | new_left, 487 | right.left().right().root(), 488 | new_right); 489 | } 490 | assert(!"impossible"); 491 | return right; 492 | } 493 | 494 | RedBlackTree RedBlackTree::remove_left(const RedBlackTree& root, Offset at, Offset total) 495 | { 496 | auto new_left = rem(root.left(), at, total); 497 | auto new_node = RedBlackTree(Color::Red, 498 | new_left, 499 | root.root(), 500 | root.right()); 501 | // In this case, the root was a red node and must've had at least two children. 502 | if (not root.left().is_empty() 503 | and root.left().root_color() == Color::Black) 504 | return balance_left(new_node); 505 | return new_node; 506 | } 507 | 508 | RedBlackTree RedBlackTree::remove_right(const RedBlackTree& root, Offset at, Offset total) 509 | { 510 | const NodeData& y = root.root(); 511 | auto new_right = rem(root.right(), at, total + y.left_subtree_length + y.piece.length); 512 | auto new_node = RedBlackTree(Color::Red, 513 | root.left(), 514 | root.root(), 515 | new_right); 516 | // In this case, the root was a red node and must've had at least two children. 517 | if (not root.right().is_empty() 518 | and root.right().root_color() == Color::Black) 519 | return balance_right(new_node); 520 | return new_node; 521 | } 522 | 523 | RedBlackTree RedBlackTree::rem(const RedBlackTree& root, Offset at, Offset total) 524 | { 525 | if (root.is_empty()) 526 | return RedBlackTree(); 527 | const NodeData& y = root.root(); 528 | if (at < total + y.left_subtree_length) 529 | return remove_left(root, at, total); 530 | if (at == total + y.left_subtree_length) 531 | return fuse(root.left(), root.right()); 532 | return remove_right(root, at, total); 533 | } 534 | #endif // EXPERIMENTAL_REMOVE 535 | 536 | #ifdef TEXTBUF_DEBUG 537 | // Borrowed from https://github.com/dotnwat/persistent-rbtree/blob/master/tree.h:checkConsistency. 538 | int check_black_node_invariant(const RedBlackTree& node) 539 | { 540 | if (node.is_empty()) 541 | return 1; 542 | if (node.root_color() == Color::Red and 543 | ((not node.left().is_empty() and node.left().root_color() == Color::Red) 544 | or (not node.right().is_empty() and node.right().root_color() == Color::Red))) 545 | { 546 | return 1; 547 | } 548 | auto l = check_black_node_invariant(node.left()); 549 | auto r = check_black_node_invariant(node.right()); 550 | 551 | if (l != 0 and r != 0 and l != r) 552 | return 0; 553 | 554 | if (l != 0 and r != 0) 555 | return node.root_color() == Color::Red ? l : l + 1; 556 | return 0; 557 | } 558 | 559 | void satisfies_rb_invariants(const RedBlackTree& root) 560 | { 561 | // 1. Every node is either red or black. 562 | // 2. All NIL nodes (figure 1) are considered black. 563 | // 3. A red node does not have a red child. 564 | // 4. Every path from a given node to any of its descendant NIL nodes goes through the same number of black nodes. 565 | 566 | // The internal nodes in this RB tree can be totally black so we will not count them directly, we'll just track 567 | // odd nodes as either red or black. 568 | // Measure the number of black nodes we need to validate. 569 | if (root.is_empty() 570 | or (root.left().is_empty() and root.right().is_empty())) 571 | return; 572 | assert(check_black_node_invariant(root) != 0); 573 | } 574 | #endif // TEXTBUF_DEBUG 575 | } // namespace PieceTree 576 | 577 | namespace PieceTree 578 | { 579 | namespace 580 | { 581 | void populate_line_starts(LineStarts* starts, std::string_view buf) 582 | { 583 | starts->clear(); 584 | LineStart start { }; 585 | starts->push_back(start); 586 | const auto len = buf.size(); 587 | for (size_t i = 0; i < len; ++i) 588 | { 589 | char c = buf[i]; 590 | if (c == '\n') 591 | { 592 | start = LineStart{ i + 1 }; 593 | starts->push_back(start); 594 | } 595 | } 596 | } 597 | 598 | void compute_buffer_meta(BufferMeta* meta, const RedBlackTree& root) 599 | { 600 | meta->lf_count = tree_lf_count(root); 601 | meta->total_content_length = tree_length(root); 602 | } 603 | } // namespace [anon] 604 | 605 | const CharBuffer* BufferCollection::buffer_at(BufferIndex index) const 606 | { 607 | if (index == BufferIndex::ModBuf) 608 | return &mod_buffer; 609 | return orig_buffers[rep(index)].get(); 610 | } 611 | 612 | CharOffset BufferCollection::buffer_offset(BufferIndex index, const BufferCursor& cursor) const 613 | { 614 | auto& starts = buffer_at(index)->line_starts; 615 | return CharOffset{ rep(starts[rep(cursor.line)]) + rep(cursor.column) }; 616 | } 617 | 618 | Tree::Tree(): 619 | buffers{ } 620 | { 621 | build_tree(); 622 | } 623 | 624 | Tree::Tree(Buffers&& buffers): 625 | buffers{ std::move(buffers) } 626 | { 627 | build_tree(); 628 | } 629 | 630 | void Tree::build_tree() 631 | { 632 | buffers.mod_buffer.line_starts.clear(); 633 | buffers.mod_buffer.buffer.clear(); 634 | // In order to maintain the invariant of other buffers, the mod_buffer needs a single line-start of 0. 635 | buffers.mod_buffer.line_starts.push_back({}); 636 | last_insert = { }; 637 | 638 | const auto buf_count = buffers.orig_buffers.size(); 639 | CharOffset offset = { }; 640 | for (size_t i = 0; i < buf_count; ++i) 641 | { 642 | const auto& buf = *buffers.orig_buffers[i]; 643 | assert(not buf.line_starts.empty()); 644 | // If this immutable buffer is empty, we can avoid creating a piece for it altogether. 645 | if (buf.buffer.empty()) 646 | continue; 647 | auto last_line = Line{ buf.line_starts.size() - 1 }; 648 | // Create a new node that spans this buffer and retains an index to it. 649 | // Insert the node into the balanced tree. 650 | Piece piece { 651 | .index = BufferIndex{ i }, 652 | .first = { .line = Line{ 0 }, .column = Column{ 0 } }, 653 | .last = { .line = last_line, .column = Column{ buf.buffer.size() - rep(buf.line_starts[rep(last_line)]) } }, 654 | .length = Length{ buf.buffer.size() }, 655 | // Note: the number of newlines 656 | .newline_count = LFCount{ rep(last_line) } 657 | }; 658 | root = root.insert({ piece }, offset); 659 | offset = offset + piece.length; 660 | } 661 | 662 | compute_buffer_meta(); 663 | } 664 | 665 | void Tree::internal_insert(CharOffset offset, std::string_view txt) 666 | { 667 | assert(not txt.empty()); 668 | end_last_insert = extend(offset, txt.size()); 669 | ScopeGuard guard{ [&] { 670 | compute_buffer_meta(); 671 | #ifdef TEXTBUF_DEBUG 672 | satisfies_rb_invariants(root); 673 | #endif // TEXTBUF_DEBUG 674 | } }; 675 | if (root.is_empty()) 676 | { 677 | auto piece = build_piece(txt); 678 | root = root.insert({ piece }, CharOffset{ 0 }); 679 | return; 680 | } 681 | 682 | auto result = node_at(&buffers, root, offset); 683 | // If the offset is beyond the buffer, just select the last node. 684 | if (result.node == nullptr) 685 | { 686 | auto off = CharOffset{ 0 }; 687 | if (meta.total_content_length != Length{}) 688 | { 689 | off = off + retract(meta.total_content_length); 690 | } 691 | result = node_at(&buffers, root, off); 692 | } 693 | 694 | // There are 3 cases: 695 | // 1. We are inserting at the beginning of an existing node. 696 | // 2. We are inserting at the end of an existing node. 697 | // 3. We are inserting in the middle of the node. 698 | auto [node, remainder, node_start_offset, line] = result; 699 | assert(node != nullptr); 700 | auto insert_pos = buffer_position(&buffers, node->piece, remainder); 701 | // Case #1. 702 | if (node_start_offset == offset) 703 | { 704 | // There's a bonus case here. If our last insertion point was the same as this piece's 705 | // last and it inserted into the mod buffer, then we can simply 'extend' this piece by 706 | // the following process: 707 | // 1. Fetch the previous node (if we can) and compare. 708 | // 2. Build the new piece. 709 | // 3. Remove the old piece. 710 | // 4. Extend the old piece's length to the length of the newly created piece. 711 | // 5. Re-insert the new piece. 712 | if (offset != CharOffset{}) 713 | { 714 | auto prev_node_result = node_at(&buffers, root, retract(offset)); 715 | if (prev_node_result.node->piece.index == BufferIndex::ModBuf 716 | and prev_node_result.node->piece.last == last_insert) 717 | { 718 | auto new_piece = build_piece(txt); 719 | combine_pieces(prev_node_result, new_piece); 720 | return; 721 | } 722 | } 723 | auto piece = build_piece(txt); 724 | root = root.insert({ piece }, offset); 725 | return; 726 | } 727 | 728 | const bool inside_node = offset < node_start_offset + node->piece.length; 729 | 730 | // Case #2. 731 | if (not inside_node) 732 | { 733 | // There's a bonus case here. If our last insertion point was the same as this piece's 734 | // last and it inserted into the mod buffer, then we can simply 'extend' this piece by 735 | // the following process: 736 | // 1. Build the new piece. 737 | // 2. Remove the old piece. 738 | // 3. Extend the old piece's length to the length of the newly created piece. 739 | // 4. Re-insert the new piece. 740 | if (node->piece.index == BufferIndex::ModBuf and node->piece.last == last_insert) 741 | { 742 | auto new_piece = build_piece(txt); 743 | combine_pieces(result, new_piece); 744 | return; 745 | } 746 | // Insert the new piece at the end. 747 | auto piece = build_piece(txt); 748 | root = root.insert({ piece }, offset); 749 | return; 750 | } 751 | 752 | // Case #3. 753 | // The basic approach here is to split the existing node into two pieces 754 | // and insert the new piece in between them. 755 | auto new_len_right = distance(buffers.buffer_offset(node->piece.index, insert_pos), 756 | buffers.buffer_offset(node->piece.index, node->piece.last)); 757 | auto new_piece_right = node->piece; 758 | new_piece_right.first = insert_pos; 759 | new_piece_right.length = new_len_right; 760 | new_piece_right.newline_count = line_feed_count(&buffers, node->piece.index, insert_pos, node->piece.last); 761 | 762 | // Remove the original node tail. 763 | auto new_piece_left = trim_piece_right(&buffers, node->piece, insert_pos); 764 | 765 | auto new_piece = build_piece(txt); 766 | 767 | // Remove the original node. 768 | root = root.remove(node_start_offset); 769 | 770 | // Insert the left. 771 | root = root.insert({ new_piece_left }, node_start_offset); 772 | 773 | // Insert the new mid. 774 | node_start_offset = node_start_offset + new_piece_left.length; 775 | root = root.insert({ new_piece }, node_start_offset); 776 | 777 | // Insert remainder. 778 | node_start_offset = node_start_offset + new_piece.length; 779 | root = root.insert({ new_piece_right }, node_start_offset); 780 | } 781 | 782 | void Tree::internal_remove(CharOffset offset, Length count) 783 | { 784 | assert(rep(count) != 0 and not root.is_empty()); 785 | ScopeGuard guard{ [&] { 786 | compute_buffer_meta(); 787 | #ifdef TEXTBUF_DEBUG 788 | satisfies_rb_invariants(root); 789 | #endif // TEXTBUF_DEBUG 790 | } }; 791 | auto first = node_at(&buffers, root, offset); 792 | auto last = node_at(&buffers, root, offset + count); 793 | auto first_node = first.node; 794 | auto last_node = last.node; 795 | 796 | auto start_split_pos = buffer_position(&buffers, first_node->piece, first.remainder); 797 | 798 | // Simple case: the range of characters we want to delete are 799 | // held directly within this node. Remove the node, resize it 800 | // then add it back. 801 | if (first_node == last_node) 802 | { 803 | auto end_split_pos = buffer_position(&buffers, first_node->piece, last.remainder); 804 | // We're going to shrink the node starting from the beginning. 805 | if (first.start_offset == offset) 806 | { 807 | // Delete the entire node. 808 | if (count == first_node->piece.length) 809 | { 810 | root = root.remove(first.start_offset); 811 | return; 812 | } 813 | // Shrink the node. 814 | auto new_piece = trim_piece_left(&buffers, first_node->piece, end_split_pos); 815 | // Remove the old one and update. 816 | root = root.remove(first.start_offset) 817 | .insert({ new_piece }, first.start_offset); 818 | return; 819 | } 820 | 821 | // Trim the tail of this piece. 822 | if (first.start_offset + first_node->piece.length == offset + count) 823 | { 824 | auto new_piece = trim_piece_right(&buffers, first_node->piece, start_split_pos); 825 | // Remove the old one and update. 826 | root = root.remove(first.start_offset) 827 | .insert({ new_piece }, first.start_offset); 828 | return; 829 | } 830 | 831 | // The removed buffer is somewhere in the middle. Trim it in both directions. 832 | auto [left, right] = shrink_piece(&buffers, first_node->piece, start_split_pos, end_split_pos); 833 | root = root.remove(first.start_offset) 834 | // Note: We insert right first so that the 'left' will be inserted 835 | // to the right node's left. 836 | .insert({ right }, first.start_offset) 837 | .insert({ left }, first.start_offset); 838 | return; 839 | } 840 | 841 | // Traverse nodes and delete all nodes within the offset range. First we will build the 842 | // partial pieces for the nodes that will eventually make up this range. 843 | // There are four cases here: 844 | // 1. The entire first node is deleted as well as all of the last node. 845 | // 2. Part of the first node is deleted and all of the last node. 846 | // 3. Part of the first node is deleted and part of the last node. 847 | // 4. The entire first node is deleted and part of the last node. 848 | 849 | auto new_first = trim_piece_right(&buffers, first_node->piece, start_split_pos); 850 | if (last_node == nullptr) 851 | { 852 | remove_node_range(first, count); 853 | } 854 | else 855 | { 856 | auto end_split_pos = buffer_position(&buffers, last_node->piece, last.remainder); 857 | auto new_last = trim_piece_left(&buffers, last_node->piece, end_split_pos); 858 | remove_node_range(first, count); 859 | // There's an edge case here where we delete all the nodes up to 'last' but 860 | // last itself remains untouched. The test of 'remainder' in 'last' can identify 861 | // this scenario to avoid inserting a duplicate of 'last'. 862 | if (last.remainder != Length{}) 863 | { 864 | if (new_last.length != Length{}) 865 | { 866 | root = root.insert({ new_last }, first.start_offset); 867 | } 868 | } 869 | } 870 | 871 | if (new_first.length != Length{}) 872 | { 873 | root = root.insert({ new_first }, first.start_offset); 874 | } 875 | } 876 | 877 | // Fetches the length of the piece starting from the first line to 'index' or to the end of 878 | // the piece. 879 | Length Tree::accumulate_value(const BufferCollection* buffers, const Piece& piece, Line index) 880 | { 881 | auto* buffer = buffers->buffer_at(piece.index); 882 | auto& line_starts = buffer->line_starts; 883 | // Extend it so we can capture the entire line content including newline. 884 | auto expected_start = extend(piece.first.line, rep(index) + 1); 885 | auto first = rep(line_starts[rep(piece.first.line)]) + rep(piece.first.column); 886 | if (expected_start > piece.last.line) 887 | { 888 | auto last = rep(line_starts[rep(piece.last.line)]) + rep(piece.last.column); 889 | return Length{ last - first }; 890 | } 891 | auto last = rep(line_starts[rep(expected_start)]); 892 | return Length{ last - first }; 893 | } 894 | 895 | // Fetches the length of the piece starting from the first line to 'index' or to the end of 896 | // the piece. 897 | Length Tree::accumulate_value_no_lf(const BufferCollection* buffers, const Piece& piece, Line index) 898 | { 899 | auto* buffer = buffers->buffer_at(piece.index); 900 | auto& line_starts = buffer->line_starts; 901 | // Extend it so we can capture the entire line content including newline. 902 | auto expected_start = extend(piece.first.line, rep(index) + 1); 903 | auto first = rep(line_starts[rep(piece.first.line)]) + rep(piece.first.column); 904 | if (expected_start > piece.last.line) 905 | { 906 | auto last = rep(line_starts[rep(piece.last.line)]) + rep(piece.last.column); 907 | if (last == first) 908 | return Length{ }; 909 | if (buffer->buffer[last - 1] == '\n') 910 | return Length{ last - 1 - first }; 911 | return Length{ last - first }; 912 | } 913 | auto last = rep(line_starts[rep(expected_start)]); 914 | if (last == first) 915 | return Length{ }; 916 | if (buffer->buffer[last - 1] == '\n') 917 | return Length{ last - 1 - first }; 918 | return Length{ last - first }; 919 | } 920 | 921 | void Tree::populate_from_node(std::string* buf, const BufferCollection* buffers, const PieceTree::RedBlackTree& node) 922 | { 923 | auto& buffer = buffers->buffer_at(node.root().piece.index)->buffer; 924 | auto old_buf_size = buf->size(); 925 | // We know we want the first line (index 0). 926 | auto accumulated_value = accumulate_value(buffers, node.root().piece, node.root().piece.first.line); 927 | auto start_offset = buffers->buffer_offset(node.root().piece.index, node.root().piece.first); 928 | auto first = buffer.data() + rep(start_offset); 929 | auto last = first + rep(accumulated_value); 930 | buf->resize(buf->size() + std::distance(first, last)); 931 | std::copy(first, last, buf->data() + old_buf_size); 932 | } 933 | 934 | void Tree::populate_from_node(std::string* buf, const BufferCollection* buffers, const PieceTree::RedBlackTree& node, Line line_index) 935 | { 936 | auto accumulated_value = accumulate_value(buffers, node.root().piece, line_index); 937 | Length prev_accumulated_value = { }; 938 | if (line_index != Line::IndexBeginning) 939 | { 940 | prev_accumulated_value = accumulate_value(buffers, node.root().piece, retract(line_index)); 941 | } 942 | auto& buffer = buffers->buffer_at(node.root().piece.index)->buffer; 943 | auto start_offset = buffers->buffer_offset(node.root().piece.index, node.root().piece.first); 944 | 945 | auto first = buffer.data() + rep(start_offset) + rep(prev_accumulated_value); 946 | auto last = buffer.data() + rep(start_offset) + rep(accumulated_value); 947 | auto old_buf_size = buf->size(); 948 | buf->resize(buf->size() + std::distance(first, last)); 949 | std::copy(first, last, buf->data() + old_buf_size); 950 | } 951 | 952 | template 953 | void Tree::line_start(CharOffset* offset, const BufferCollection* buffers, const PieceTree::RedBlackTree& node, Line line) 954 | { 955 | if (node.is_empty()) 956 | return; 957 | assert(line != Line::IndexBeginning); 958 | auto line_index = rep(retract(line)); 959 | if (rep(node.root().left_subtree_lf_count) >= line_index) 960 | { 961 | line_start(offset, buffers, node.left(), line); 962 | } 963 | // The desired line is directly within the node. 964 | else if (rep(node.root().left_subtree_lf_count + node.root().piece.newline_count) >= line_index) 965 | { 966 | line_index -= rep(node.root().left_subtree_lf_count); 967 | Length len = node.root().left_subtree_length; 968 | if (line_index != 0) 969 | { 970 | len = len + (*accumulate)(buffers, node.root().piece, Line{ line_index - 1 }); 971 | } 972 | *offset = *offset + len; 973 | } 974 | // assemble the LHS and RHS. 975 | else 976 | { 977 | // This case implies that 'left_subtree_lf_count' is strictly < line_index. 978 | // The content is somewhere in the middle. 979 | line_index -= rep(node.root().left_subtree_lf_count + node.root().piece.newline_count); 980 | *offset = *offset + node.root().left_subtree_length + node.root().piece.length; 981 | line_start(offset, buffers, node.right(), Line{ line_index + 1 }); 982 | } 983 | } 984 | 985 | void Tree::line_end_crlf(CharOffset* offset, const BufferCollection* buffers, const RedBlackTree& root, const RedBlackTree& node, Line line) 986 | { 987 | if (node.is_empty()) 988 | return; 989 | assert(line != Line::IndexBeginning); 990 | auto line_index = rep(retract(line)); 991 | if (rep(node.root().left_subtree_lf_count) >= line_index) 992 | { 993 | line_end_crlf(offset, buffers, root, node.left(), line); 994 | } 995 | // The desired line is directly within the node. 996 | else if (rep(node.root().left_subtree_lf_count + node.root().piece.newline_count) >= line_index) 997 | { 998 | line_index -= rep(node.root().left_subtree_lf_count); 999 | Length len = node.root().left_subtree_length; 1000 | if (line_index != 0) 1001 | { 1002 | len = len + accumulate_value_no_lf(buffers, node.root().piece, Line{ line_index - 1 }); 1003 | } 1004 | 1005 | // If the length is anything but 0, we need to check if the last character was a carriage return. 1006 | if (len != Length{}) 1007 | { 1008 | auto last_char_offset = *offset + retract(len); 1009 | if (char_at(buffers, root, last_char_offset) == '\r' and char_at(buffers, root, extend(last_char_offset)) == '\n') 1010 | { 1011 | len = retract(len); 1012 | } 1013 | } 1014 | *offset = *offset + len; 1015 | } 1016 | // assemble the LHS and RHS. 1017 | else 1018 | { 1019 | // This case implies that 'left_subtree_lf_count + piece NL count' is strictly < line_index. 1020 | // The content is somewhere in the middle. 1021 | auto& piece = node.root().piece; 1022 | line_index -= rep(node.root().left_subtree_lf_count + piece.newline_count); 1023 | *offset = *offset + node.root().left_subtree_length + piece.length; 1024 | line_end_crlf(offset, buffers, root, node.right(), Line{ line_index + 1 }); 1025 | } 1026 | } 1027 | 1028 | LineRange Tree::get_line_range(Line line) const 1029 | { 1030 | LineRange range{ }; 1031 | line_start<&Tree::accumulate_value>(&range.first, &buffers, root, line); 1032 | line_start<&Tree::accumulate_value_no_lf>(&range.last, &buffers, root, extend(line)); 1033 | return range; 1034 | } 1035 | 1036 | LineRange Tree::get_line_range_crlf(Line line) const 1037 | { 1038 | LineRange range{ }; 1039 | line_start<&Tree::accumulate_value>(&range.first, &buffers, root, line); 1040 | line_end_crlf(&range.last, &buffers, root, root, extend(line)); 1041 | return range; 1042 | } 1043 | 1044 | LineRange Tree::get_line_range_with_newline(Line line) const 1045 | { 1046 | LineRange range{ }; 1047 | line_start<&Tree::accumulate_value>(&range.first, &buffers, root, line); 1048 | line_start<&Tree::accumulate_value>(&range.last, &buffers, root, extend(line)); 1049 | return range; 1050 | } 1051 | 1052 | OwningSnapshot Tree::owning_snap() const 1053 | { 1054 | return OwningSnapshot{ this }; 1055 | } 1056 | 1057 | ReferenceSnapshot Tree::ref_snap() const 1058 | { 1059 | return ReferenceSnapshot{ this }; 1060 | } 1061 | 1062 | Line Tree::line_at(CharOffset offset) const 1063 | { 1064 | if (is_empty()) 1065 | return Line::Beginning; 1066 | auto result = node_at(&buffers, root, offset); 1067 | return result.line; 1068 | } 1069 | 1070 | char Tree::at(CharOffset offset) const 1071 | { 1072 | return char_at(&buffers, root, offset); 1073 | } 1074 | 1075 | char Tree::char_at(const BufferCollection* buffers, const RedBlackTree& node, CharOffset offset) 1076 | { 1077 | auto result = node_at(buffers, node, offset); 1078 | if (result.node == nullptr) 1079 | return '\0'; 1080 | auto* buffer = buffers->buffer_at(result.node->piece.index); 1081 | auto buf_offset = buffers->buffer_offset(result.node->piece.index, result.node->piece.first); 1082 | const char* p = buffer->buffer.data() + rep(buf_offset) + rep(result.remainder); 1083 | return *p; 1084 | } 1085 | 1086 | void Tree::assemble_line(std::string* buf, const PieceTree::RedBlackTree& node, Line line) const 1087 | { 1088 | if (node.is_empty()) 1089 | return; 1090 | // Trying this new logic for now. 1091 | #if 1 1092 | CharOffset line_offset{ }; 1093 | line_start<&Tree::accumulate_value>(&line_offset, &buffers, node, line); 1094 | TreeWalker walker{ this, line_offset }; 1095 | while (not walker.exhausted()) 1096 | { 1097 | char c = walker.next(); 1098 | if (c == '\n') 1099 | break; 1100 | buf->push_back(c); 1101 | } 1102 | #else 1103 | assert(line != Line::IndexBeginning); 1104 | auto line_index = rep(retract(line)); 1105 | if (rep(node.root().left_subtree_lf_count) >= line_index) 1106 | { 1107 | assemble_line(buf, node.left(), line); 1108 | const bool same_index = rep(node.root().left_subtree_lf_count) == line_index; 1109 | if (same_index) 1110 | { 1111 | populate_from_node(buf, node); 1112 | // Visit the RHS if this piece did not introduce a newline. 1113 | if (rep(node.root().piece.newline_count) == 0) 1114 | { 1115 | assemble_line(buf, node.right(), retract(line, rep(node.root().left_subtree_lf_count))); 1116 | } 1117 | } 1118 | } 1119 | // The desired line is directly within the node. 1120 | else if (rep(node.root().left_subtree_lf_count + node.root().piece.newline_count) > line_index) 1121 | { 1122 | line_index -= rep(node.root().left_subtree_lf_count); 1123 | populate_from_node(buf, node, Line{ line_index }); 1124 | return; 1125 | } 1126 | // assemble the LHS and RHS. 1127 | else 1128 | { 1129 | // This case implies that 'left_subtree_lf_count' is strictly < line_index. 1130 | // The content is somewhere in the middle. We only need to populate from this node if the line index is exactly 1131 | assemble_line(buf, node.left(), line); 1132 | line_index -= rep(node.root().left_subtree_lf_count); 1133 | populate_from_node(buf, node, Line{ line_index }); 1134 | if (rep(node.root().piece.newline_count) <= line_index) 1135 | { 1136 | assemble_line(buf, node.right(), retract(line, rep(node.root().left_subtree_lf_count + node.root().piece.newline_count))); 1137 | } 1138 | } 1139 | #endif 1140 | } 1141 | 1142 | void Tree::get_line_content(std::string* buf, Line line) const 1143 | { 1144 | // Reset the buffer. 1145 | buf->clear(); 1146 | if (line == Line::IndexBeginning) 1147 | return; 1148 | assemble_line(buf, root, line); 1149 | } 1150 | 1151 | void OwningSnapshot::get_line_content(std::string* buf, Line line) const 1152 | { 1153 | // Reset the buffer. 1154 | buf->clear(); 1155 | if (line == Line::IndexBeginning) 1156 | return; 1157 | if (root.is_empty()) 1158 | return; 1159 | CharOffset line_offset{ }; 1160 | Tree::line_start<&Tree::accumulate_value>(&line_offset, &buffers, root, line); 1161 | TreeWalker walker{ this, line_offset }; 1162 | while (not walker.exhausted()) 1163 | { 1164 | char c = walker.next(); 1165 | if (c == '\n') 1166 | break; 1167 | buf->push_back(c); 1168 | } 1169 | } 1170 | 1171 | void ReferenceSnapshot::get_line_content(std::string* buf, Line line) const 1172 | { 1173 | // Reset the buffer. 1174 | buf->clear(); 1175 | if (line == Line::IndexBeginning) 1176 | return; 1177 | if (root.is_empty()) 1178 | return; 1179 | CharOffset line_offset{ }; 1180 | Tree::line_start<&Tree::accumulate_value>(&line_offset, buffers, root, line); 1181 | TreeWalker walker{ this, line_offset }; 1182 | while (not walker.exhausted()) 1183 | { 1184 | char c = walker.next(); 1185 | if (c == '\n') 1186 | break; 1187 | buf->push_back(c); 1188 | } 1189 | } 1190 | 1191 | namespace 1192 | { 1193 | template 1194 | [[nodiscard]] IncompleteCRLF trim_crlf(std::string* buf, TreeT* tree, CharOffset line_offset) 1195 | { 1196 | TreeWalker walker{ tree, line_offset }; 1197 | auto prev_char = '\0'; 1198 | while (not walker.exhausted()) 1199 | { 1200 | char c = walker.next(); 1201 | if (c == '\n') 1202 | { 1203 | if (prev_char == '\r') 1204 | { 1205 | buf->pop_back(); 1206 | return IncompleteCRLF::No; 1207 | } 1208 | return IncompleteCRLF::Yes; 1209 | } 1210 | buf->push_back(c); 1211 | prev_char = c; 1212 | } 1213 | // End of the buffer is not an incomplete CRLF. 1214 | return IncompleteCRLF::No; 1215 | } 1216 | } // namespace [anon] 1217 | 1218 | IncompleteCRLF Tree::get_line_content_crlf(std::string* buf, Line line) const 1219 | { 1220 | // Reset the buffer. 1221 | buf->clear(); 1222 | if (line == Line::IndexBeginning) 1223 | return IncompleteCRLF::No; 1224 | auto node = root; 1225 | if (node.is_empty()) 1226 | return IncompleteCRLF::No; 1227 | // Trying this new logic for now. 1228 | CharOffset line_offset{ }; 1229 | line_start<&Tree::accumulate_value>(&line_offset, &buffers, node, line); 1230 | return trim_crlf(buf, this, line_offset); 1231 | } 1232 | 1233 | IncompleteCRLF OwningSnapshot::get_line_content_crlf(std::string* buf, Line line) const 1234 | { 1235 | // Reset the buffer. 1236 | buf->clear(); 1237 | if (line == Line::IndexBeginning) 1238 | return IncompleteCRLF::No; 1239 | auto node = root; 1240 | if (node.is_empty()) 1241 | return IncompleteCRLF::No; 1242 | // Trying this new logic for now. 1243 | CharOffset line_offset{ }; 1244 | Tree::line_start<&Tree::accumulate_value>(&line_offset, &buffers, node, line); 1245 | return trim_crlf(buf, this, line_offset); 1246 | } 1247 | 1248 | IncompleteCRLF ReferenceSnapshot::get_line_content_crlf(std::string* buf, Line line) const 1249 | { 1250 | // Reset the buffer. 1251 | buf->clear(); 1252 | if (line == Line::IndexBeginning) 1253 | return IncompleteCRLF::No; 1254 | auto node = root; 1255 | if (node.is_empty()) 1256 | return IncompleteCRLF::No; 1257 | // Trying this new logic for now. 1258 | CharOffset line_offset{ }; 1259 | Tree::line_start<&Tree::accumulate_value>(&line_offset, buffers, node, line); 1260 | return trim_crlf(buf, this, line_offset); 1261 | } 1262 | 1263 | Line OwningSnapshot::line_at(CharOffset offset) const 1264 | { 1265 | if (is_empty()) 1266 | return Line::Beginning; 1267 | auto result = Tree::node_at(&buffers, root, offset); 1268 | return result.line; 1269 | } 1270 | 1271 | Line ReferenceSnapshot::line_at(CharOffset offset) const 1272 | { 1273 | if (is_empty()) 1274 | return Line::Beginning; 1275 | auto result = Tree::node_at(buffers, root, offset); 1276 | return result.line; 1277 | } 1278 | 1279 | LineRange OwningSnapshot::get_line_range(Line line) const 1280 | { 1281 | LineRange range{ }; 1282 | Tree::line_start<&Tree::accumulate_value>(&range.first, &buffers, root, line); 1283 | Tree::line_start<&Tree::accumulate_value_no_lf>(&range.last, &buffers, root, extend(line)); 1284 | return range; 1285 | } 1286 | 1287 | LineRange ReferenceSnapshot::get_line_range(Line line) const 1288 | { 1289 | LineRange range{ }; 1290 | Tree::line_start<&Tree::accumulate_value>(&range.first, buffers, root, line); 1291 | Tree::line_start<&Tree::accumulate_value_no_lf>(&range.last, buffers, root, extend(line)); 1292 | return range; 1293 | } 1294 | 1295 | LineRange OwningSnapshot::get_line_range_crlf(Line line) const 1296 | { 1297 | LineRange range{ }; 1298 | Tree::line_start<&Tree::accumulate_value>(&range.first, &buffers, root, line); 1299 | Tree::line_end_crlf(&range.last, &buffers, root, root, extend(line)); 1300 | return range; 1301 | } 1302 | 1303 | LineRange ReferenceSnapshot::get_line_range_crlf(Line line) const 1304 | { 1305 | LineRange range{ }; 1306 | Tree::line_start<&Tree::accumulate_value>(&range.first, buffers, root, line); 1307 | Tree::line_end_crlf(&range.last, buffers, root, root, extend(line)); 1308 | return range; 1309 | } 1310 | 1311 | LineRange OwningSnapshot::get_line_range_with_newline(Line line) const 1312 | { 1313 | LineRange range{ }; 1314 | Tree::line_start<&Tree::accumulate_value>(&range.first, &buffers, root, line); 1315 | Tree::line_start<&Tree::accumulate_value>(&range.last, &buffers, root, extend(line)); 1316 | return range; 1317 | } 1318 | 1319 | LineRange ReferenceSnapshot::get_line_range_with_newline(Line line) const 1320 | { 1321 | LineRange range{ }; 1322 | Tree::line_start<&Tree::accumulate_value>(&range.first, buffers, root, line); 1323 | Tree::line_start<&Tree::accumulate_value>(&range.last, buffers, root, extend(line)); 1324 | return range; 1325 | } 1326 | 1327 | LFCount Tree::line_feed_count(const BufferCollection* buffers, BufferIndex index, const BufferCursor& start, const BufferCursor& end) 1328 | { 1329 | // If the end position is the beginning of a new line, then we can just return the difference in lines. 1330 | if (rep(end.column) == 0) 1331 | return LFCount{ rep(retract(end.line, rep(start.line))) }; 1332 | auto& starts = buffers->buffer_at(index)->line_starts; 1333 | // It means, there is no LF after end. 1334 | if (end.line == Line{ starts.size() - 1}) 1335 | return LFCount{ rep(retract(end.line, rep(start.line))) }; 1336 | // Due to the check above, we know that there's at least one more line after 'end.line'. 1337 | auto next_start_offset = starts[rep(extend(end.line))]; 1338 | auto end_offset = rep(starts[rep(end.line)]) + rep(end.column); 1339 | // There are more than 1 character after end, which means it can't be LF. 1340 | if (rep(next_start_offset) > end_offset + 1) 1341 | return LFCount{ rep(retract(end.line, rep(start.line))) }; 1342 | // This must be the case. next_start_offset is a line down, so it is 1343 | // not possible for end_offset to be < it at this point. 1344 | assert(end_offset + 1 == rep(next_start_offset)); 1345 | return LFCount{ rep(retract(end.line, rep(start.line))) }; 1346 | } 1347 | 1348 | Piece Tree::build_piece(std::string_view txt) 1349 | { 1350 | auto start_offset = buffers.mod_buffer.buffer.size(); 1351 | populate_line_starts(&scratch_starts, txt); 1352 | auto start = last_insert; 1353 | // TODO: Handle CRLF (where the new buffer starts with LF and the end of our buffer ends with CR). 1354 | // Offset the new starts relative to the existing buffer. 1355 | for (auto& new_start : scratch_starts) 1356 | { 1357 | new_start = extend(new_start, start_offset); 1358 | } 1359 | // Append new starts. 1360 | // Note: we can drop the first start because the algorithm always adds an empty start. 1361 | auto new_starts_end = scratch_starts.size(); 1362 | buffers.mod_buffer.line_starts.reserve(buffers.mod_buffer.line_starts.size() + new_starts_end); 1363 | for (size_t i = 1; i < new_starts_end; ++i) 1364 | { 1365 | buffers.mod_buffer.line_starts.push_back(scratch_starts[i]); 1366 | } 1367 | auto old_size = buffers.mod_buffer.buffer.size(); 1368 | buffers.mod_buffer.buffer.resize(buffers.mod_buffer.buffer.size() + txt.size()); 1369 | auto insert_at = buffers.mod_buffer.buffer.data() + old_size; 1370 | std::copy(txt.data(), txt.data() + txt.size(), insert_at); 1371 | 1372 | // Build the new piece for the inserted buffer. 1373 | auto end_offset = buffers.mod_buffer.buffer.size(); 1374 | auto end_index = buffers.mod_buffer.line_starts.size() - 1; 1375 | auto end_col = end_offset - rep(buffers.mod_buffer.line_starts[end_index]); 1376 | BufferCursor end_pos = { .line = Line{ end_index }, .column = Column{ end_col } }; 1377 | Piece piece = { .index = BufferIndex::ModBuf, 1378 | .first = start, 1379 | .last = end_pos, 1380 | .length = Length{ end_offset - start_offset }, 1381 | .newline_count = line_feed_count(&buffers, BufferIndex::ModBuf, start, end_pos) }; 1382 | // Update the last insertion. 1383 | last_insert = end_pos; 1384 | return piece; 1385 | } 1386 | 1387 | NodePosition Tree::node_at(const BufferCollection* buffers, RedBlackTree node, CharOffset off) 1388 | { 1389 | size_t node_start_offset = 0; 1390 | size_t newline_count = 0; 1391 | while (not node.is_empty()) 1392 | { 1393 | if (rep(node.root().left_subtree_length) > rep(off)) 1394 | { 1395 | node = node.left(); 1396 | } 1397 | else if (rep(node.root().left_subtree_length + node.root().piece.length) > rep(off)) 1398 | { 1399 | node_start_offset += rep(node.root().left_subtree_length); 1400 | newline_count += rep(node.root().left_subtree_lf_count); 1401 | // Now we find the line within this piece. 1402 | auto remainder = Length{ rep(retract(off, rep(node.root().left_subtree_length))) }; 1403 | auto pos = buffer_position(buffers, node.root().piece, remainder); 1404 | // Note: since buffer_position will return us a newline relative to the buffer itself, we need 1405 | // to retract it by the starting line of the piece to get the real difference. 1406 | newline_count += rep(retract(pos.line, rep(node.root().piece.first.line))); 1407 | return { .node = &node.root(), 1408 | .remainder = remainder, 1409 | .start_offset = CharOffset{ node_start_offset }, 1410 | .line = Line{ newline_count + 1 } }; 1411 | } 1412 | else 1413 | { 1414 | // If there are no more nodes to traverse to, return this final node. 1415 | if (node.right().is_empty()) 1416 | { 1417 | auto offset_amount = rep(node.root().left_subtree_length); 1418 | node_start_offset += offset_amount; 1419 | newline_count += rep(node.root().left_subtree_lf_count + node.root().piece.newline_count); 1420 | // Now we find the line within this piece. 1421 | auto remainder = node.root().piece.length; 1422 | return { .node = &node.root(), 1423 | .remainder = remainder, 1424 | .start_offset = CharOffset{ node_start_offset }, 1425 | .line = Line{ newline_count + 1 } }; 1426 | } 1427 | auto offset_amount = rep(node.root().left_subtree_length + node.root().piece.length); 1428 | off = retract(off, offset_amount); 1429 | node_start_offset += offset_amount; 1430 | newline_count += rep(node.root().left_subtree_lf_count + node.root().piece.newline_count); 1431 | node = node.right(); 1432 | } 1433 | } 1434 | return { }; 1435 | } 1436 | 1437 | BufferCursor Tree::buffer_position(const BufferCollection* buffers, const Piece& piece, Length remainder) 1438 | { 1439 | auto& starts = buffers->buffer_at(piece.index)->line_starts; 1440 | auto start_offset = rep(starts[rep(piece.first.line)]) + rep(piece.first.column); 1441 | auto offset = start_offset + rep(remainder); 1442 | 1443 | // Binary search for 'offset' between start and ending offset. 1444 | auto low = rep(piece.first.line); 1445 | auto high = rep(piece.last.line); 1446 | 1447 | size_t mid = 0; 1448 | size_t mid_start = 0; 1449 | size_t mid_stop = 0; 1450 | 1451 | while (low <= high) 1452 | { 1453 | mid = low + ((high - low) / 2); 1454 | mid_start = rep(starts[mid]); 1455 | 1456 | if (mid == high) 1457 | break; 1458 | mid_stop = rep(starts[mid + 1]); 1459 | 1460 | if (offset < mid_start) 1461 | { 1462 | high = mid - 1; 1463 | } 1464 | else if (offset >= mid_stop) 1465 | { 1466 | low = mid + 1; 1467 | } 1468 | else 1469 | { 1470 | break; 1471 | } 1472 | } 1473 | 1474 | return { .line = Line{ mid }, 1475 | .column = Column{ offset - mid_start } }; 1476 | } 1477 | 1478 | Piece Tree::trim_piece_right(const BufferCollection* buffers, const Piece& piece, const BufferCursor& pos) 1479 | { 1480 | auto orig_end_offset = buffers->buffer_offset(piece.index, piece.last); 1481 | 1482 | auto new_end_offset = buffers->buffer_offset(piece.index, pos); 1483 | auto new_lf_count = line_feed_count(buffers, piece.index, piece.first, pos); 1484 | 1485 | auto len_delta = distance(new_end_offset, orig_end_offset); 1486 | auto new_len = retract(piece.length, rep(len_delta)); 1487 | 1488 | auto new_piece = piece; 1489 | new_piece.last = pos; 1490 | new_piece.newline_count = new_lf_count; 1491 | new_piece.length = new_len; 1492 | 1493 | return new_piece; 1494 | } 1495 | 1496 | Piece Tree::trim_piece_left(const BufferCollection* buffers, const Piece& piece, const BufferCursor& pos) 1497 | { 1498 | auto orig_start_offset = buffers->buffer_offset(piece.index, piece.first); 1499 | 1500 | auto new_start_offset = buffers->buffer_offset(piece.index, pos); 1501 | auto new_lf_count = line_feed_count(buffers, piece.index, pos, piece.last); 1502 | 1503 | auto len_delta = distance(orig_start_offset, new_start_offset); 1504 | auto new_len = retract(piece.length, rep(len_delta)); 1505 | 1506 | auto new_piece = piece; 1507 | new_piece.first = pos; 1508 | new_piece.newline_count = new_lf_count; 1509 | new_piece.length = new_len; 1510 | 1511 | return new_piece; 1512 | } 1513 | 1514 | Tree::ShrinkResult Tree::shrink_piece(const BufferCollection* buffers, const Piece& piece, const BufferCursor& first, const BufferCursor& last) 1515 | { 1516 | auto left = trim_piece_right(buffers, piece, first); 1517 | auto right = trim_piece_left(buffers, piece, last); 1518 | 1519 | return { .left = left, .right = right }; 1520 | } 1521 | 1522 | void Tree::combine_pieces(NodePosition existing, Piece new_piece) 1523 | { 1524 | // This transformation is only valid under the following conditions. 1525 | assert(existing.node->piece.index == BufferIndex::ModBuf); 1526 | // This assumes that the piece was just built. 1527 | assert(existing.node->piece.last == new_piece.first); 1528 | auto old_piece = existing.node->piece; 1529 | new_piece.first = old_piece.first; 1530 | new_piece.newline_count = new_piece.newline_count + old_piece.newline_count; 1531 | new_piece.length = new_piece.length + old_piece.length; 1532 | root = root.remove(existing.start_offset) 1533 | .insert({ new_piece }, existing.start_offset); 1534 | } 1535 | 1536 | void Tree::remove_node_range(NodePosition first, Length length) 1537 | { 1538 | // Remove pieces until we reach the desired length. 1539 | Length deleted_len{}; 1540 | // Because we could be deleting content in the range starting at 'first' where the piece 1541 | // length could be much larger than 'length', we need to adjust 'length' to contain the 1542 | // delta in length within the piece to the end where 'length' starts: 1543 | // "abcd" "efg" 1544 | // ^ ^ 1545 | // |_____| 1546 | // length to delete = 3 1547 | // P1 length: 4 1548 | // P2 length: 3 (though this length does not matter) 1549 | // We're going to remove all of 'P1' and 'P2' in this range and the caller will re-insert 1550 | // these pieces with the correct lengths. If we fail to adjust 'length' we will delete P1 1551 | // and believe that the entire range was deleted. 1552 | assert(first.node != nullptr); 1553 | auto total_length = first.node->piece.length; 1554 | // (total - remainder) is the section of 'length' where 'first' intersects. 1555 | length = length - (total_length - first.remainder) + total_length; 1556 | auto delete_at_offset = first.start_offset; 1557 | while (deleted_len < length and first.node != nullptr) 1558 | { 1559 | deleted_len = deleted_len + first.node->piece.length; 1560 | root = root.remove(delete_at_offset); 1561 | first = node_at(&buffers, root, delete_at_offset); 1562 | } 1563 | } 1564 | 1565 | void Tree::insert(CharOffset offset, std::string_view txt, SuppressHistory suppress_history) 1566 | { 1567 | if (txt.empty()) 1568 | return; 1569 | // This allows us to undo blocks of code. 1570 | if (is_no(suppress_history) 1571 | and (end_last_insert != offset or root.is_empty())) 1572 | { 1573 | append_undo(root, offset); 1574 | } 1575 | internal_insert(offset, txt); 1576 | } 1577 | 1578 | void Tree::remove(CharOffset offset, Length count, SuppressHistory suppress_history) 1579 | { 1580 | // Rule out the obvious noop. 1581 | if (rep(count) == 0 or root.is_empty()) 1582 | return; 1583 | if (is_no(suppress_history)) 1584 | { 1585 | append_undo(root, offset); 1586 | } 1587 | internal_remove(offset, count); 1588 | } 1589 | 1590 | void Tree::compute_buffer_meta() 1591 | { 1592 | ::PieceTree::compute_buffer_meta(&meta, root); 1593 | } 1594 | 1595 | void Tree::append_undo(const RedBlackTree& old_root, CharOffset op_offset) 1596 | { 1597 | // Can't redo if we're creating a new undo entry. 1598 | if (not redo_stack.empty()) 1599 | { 1600 | redo_stack.clear(); 1601 | } 1602 | undo_stack.push_front({ .root = old_root, .op_offset = op_offset }); 1603 | } 1604 | 1605 | UndoRedoResult Tree::try_undo(CharOffset op_offset) 1606 | { 1607 | if (undo_stack.empty()) 1608 | return { .success = false, .op_offset = CharOffset{ } }; 1609 | redo_stack.push_front({ .root = root, .op_offset = op_offset }); 1610 | auto [node, undo_offset] = undo_stack.front(); 1611 | root = node; 1612 | undo_stack.pop_front(); 1613 | compute_buffer_meta(); 1614 | return { .success = true, .op_offset = undo_offset }; 1615 | } 1616 | 1617 | UndoRedoResult Tree::try_redo(CharOffset op_offset) 1618 | { 1619 | if (redo_stack.empty()) 1620 | return { .success = false, .op_offset = CharOffset{ } }; 1621 | undo_stack.push_front({ .root = root, .op_offset = op_offset }); 1622 | auto [node, redo_offset] = redo_stack.front(); 1623 | root = node; 1624 | redo_stack.pop_front(); 1625 | compute_buffer_meta(); 1626 | return { .success = true, .op_offset = redo_offset }; 1627 | } 1628 | 1629 | // Direct history manipulation. 1630 | void Tree::commit_head(CharOffset offset) 1631 | { 1632 | append_undo(root, offset); 1633 | } 1634 | 1635 | RedBlackTree Tree::head() const 1636 | { 1637 | return root; 1638 | } 1639 | 1640 | void Tree::snap_to(const RedBlackTree& new_root) 1641 | { 1642 | root = new_root; 1643 | compute_buffer_meta(); 1644 | } 1645 | 1646 | #ifdef TEXTBUF_DEBUG 1647 | void print_piece(const Piece& piece, const Tree* tree, int level) 1648 | { 1649 | const char* levels = "|||||||||||||||||||||||||||||||"; 1650 | printf("%.*sidx{%zd}, first{l{%zd}, c{%zd}}, last{l{%zd}, c{%zd}}, len{%zd}, lf{%zd}\n", 1651 | level, levels, 1652 | rep(piece.index), rep(piece.first.line), rep(piece.first.column), 1653 | rep(piece.last.line), rep(piece.last.column), 1654 | rep(piece.length), rep(piece.newline_count)); 1655 | auto* buffer = tree->buffers.buffer_at(piece.index); 1656 | auto offset = tree->buffers.buffer_offset(piece.index, piece.first); 1657 | printf("%.*sPiece content: %.*s\n", level, levels, static_cast(piece.length), buffer->buffer.data() + rep(offset)); 1658 | } 1659 | #endif // TEXTBUF_DEBUG 1660 | 1661 | void TreeBuilder::accept(std::string_view txt) 1662 | { 1663 | populate_line_starts(&scratch_starts, txt); 1664 | buffers.push_back(std::make_shared(std::string{ txt }, scratch_starts)); 1665 | } 1666 | 1667 | OwningSnapshot::OwningSnapshot(const Tree* tree): 1668 | root{ tree->root }, 1669 | meta{ tree->meta }, 1670 | buffers{ tree->buffers } { } 1671 | 1672 | OwningSnapshot::OwningSnapshot(const Tree* tree, const RedBlackTree& dt): 1673 | root{ tree->root }, 1674 | meta{ tree->meta }, 1675 | buffers{ tree->buffers } 1676 | { 1677 | // Compute the buffer meta for 'dt'. 1678 | compute_buffer_meta(&meta, dt); 1679 | } 1680 | 1681 | ReferenceSnapshot::ReferenceSnapshot(const Tree* tree): 1682 | root{ tree->root }, 1683 | meta{ tree->meta }, 1684 | buffers{ &tree->buffers } { } 1685 | 1686 | ReferenceSnapshot::ReferenceSnapshot(const Tree* tree, const RedBlackTree& dt): 1687 | root{ dt }, 1688 | meta{ tree->meta }, 1689 | buffers{ &tree->buffers } 1690 | { 1691 | // Compute the buffer meta for 'dt'. 1692 | compute_buffer_meta(&meta, dt); 1693 | } 1694 | 1695 | TreeWalker::TreeWalker(const Tree* tree, CharOffset offset): 1696 | buffers{ &tree->buffers }, 1697 | root{ tree->root }, 1698 | meta{ tree->meta }, 1699 | stack{ { root } }, 1700 | total_offset{ offset } 1701 | { 1702 | fast_forward_to(offset); 1703 | } 1704 | 1705 | TreeWalker::TreeWalker(const OwningSnapshot* snap, CharOffset offset): 1706 | buffers{ &snap->buffers }, 1707 | root{ snap->root }, 1708 | meta{ snap->meta }, 1709 | stack{ { root } }, 1710 | total_offset{ offset } 1711 | { 1712 | fast_forward_to(offset); 1713 | } 1714 | 1715 | TreeWalker::TreeWalker(const ReferenceSnapshot* snap, CharOffset offset): 1716 | buffers{ snap->buffers }, 1717 | root{ snap->root }, 1718 | meta{ snap->meta }, 1719 | stack{ { root } }, 1720 | total_offset{ offset } 1721 | { 1722 | fast_forward_to(offset); 1723 | } 1724 | 1725 | char TreeWalker::next() 1726 | { 1727 | if (first_ptr == last_ptr) 1728 | { 1729 | populate_ptrs(); 1730 | // If this is exhausted, we're done. 1731 | if (exhausted()) 1732 | return '\0'; 1733 | // Catchall. 1734 | if (first_ptr == last_ptr) 1735 | return next(); 1736 | } 1737 | total_offset = total_offset + Length{ 1 }; 1738 | return *first_ptr++; 1739 | } 1740 | 1741 | char TreeWalker::current() 1742 | { 1743 | if (first_ptr == last_ptr) 1744 | { 1745 | populate_ptrs(); 1746 | // If this is exhausted, we're done. 1747 | if (exhausted()) 1748 | return '\0'; 1749 | } 1750 | return *first_ptr; 1751 | } 1752 | 1753 | void TreeWalker::seek(CharOffset offset) 1754 | { 1755 | stack.clear(); 1756 | stack.push_back({ root }); 1757 | total_offset = offset; 1758 | fast_forward_to(offset); 1759 | } 1760 | 1761 | bool TreeWalker::exhausted() const 1762 | { 1763 | if (stack.empty()) 1764 | return true; 1765 | // If we have not exhausted the pointers, we're still active. 1766 | if (first_ptr != last_ptr) 1767 | return false; 1768 | // If there's more than one entry on the stack, we're still active. 1769 | if (stack.size() > 1) 1770 | return false; 1771 | // Now, if there's exactly one entry and that entry itself is exhausted (no right subtree) 1772 | // we're done. 1773 | auto& entry = stack.back(); 1774 | // We descended into a null child, we're done. 1775 | if (entry.node.is_empty()) 1776 | return true; 1777 | if (entry.dir == Direction::Right and entry.node.right().is_empty()) 1778 | return true; 1779 | return false; 1780 | } 1781 | 1782 | Length TreeWalker::remaining() const 1783 | { 1784 | return meta.total_content_length - distance(CharOffset{}, total_offset); 1785 | } 1786 | 1787 | void TreeWalker::populate_ptrs() 1788 | { 1789 | if (exhausted()) 1790 | return; 1791 | if (stack.back().node.is_empty()) 1792 | { 1793 | stack.pop_back(); 1794 | populate_ptrs(); 1795 | return; 1796 | } 1797 | 1798 | auto& [node, dir] = stack.back(); 1799 | if (dir == Direction::Left) 1800 | { 1801 | if (not node.left().is_empty()) 1802 | { 1803 | auto left = node.left(); 1804 | // Change the dir for when we pop back. 1805 | stack.back().dir = Direction::Center; 1806 | stack.push_back({ left }); 1807 | populate_ptrs(); 1808 | return; 1809 | } 1810 | // Otherwise, let's visit the center, we can actually fallthrough. 1811 | stack.back().dir = Direction::Center; 1812 | dir = Direction::Center; 1813 | } 1814 | 1815 | if (dir == Direction::Center) 1816 | { 1817 | auto& piece = node.root().piece; 1818 | auto* buffer = buffers->buffer_at(piece.index); 1819 | auto first_offset = buffers->buffer_offset(piece.index, piece.first); 1820 | auto last_offset = buffers->buffer_offset(piece.index, piece.last); 1821 | first_ptr = buffer->buffer.data() + rep(first_offset); 1822 | last_ptr = buffer->buffer.data() + rep(last_offset); 1823 | // Change this direction. 1824 | stack.back().dir = Direction::Right; 1825 | return; 1826 | } 1827 | 1828 | assert(dir == Direction::Right); 1829 | auto right = node.right(); 1830 | stack.pop_back(); 1831 | stack.push_back({ right }); 1832 | populate_ptrs(); 1833 | } 1834 | 1835 | void TreeWalker::fast_forward_to(CharOffset offset) 1836 | { 1837 | auto node = root; 1838 | while (not node.is_empty()) 1839 | { 1840 | if (rep(node.root().left_subtree_length) > rep(offset)) 1841 | { 1842 | // For when we revisit this node. 1843 | stack.back().dir = Direction::Center; 1844 | node = node.left(); 1845 | stack.push_back({ node }); 1846 | } 1847 | // It is inside this node. 1848 | else if (rep(node.root().left_subtree_length + node.root().piece.length) > rep(offset)) 1849 | { 1850 | stack.back().dir = Direction::Right; 1851 | // Make the offset relative to this piece. 1852 | offset = retract(offset, rep(node.root().left_subtree_length)); 1853 | auto& piece = node.root().piece; 1854 | auto* buffer = buffers->buffer_at(piece.index); 1855 | auto first_offset = buffers->buffer_offset(piece.index, piece.first); 1856 | auto last_offset = buffers->buffer_offset(piece.index, piece.last); 1857 | first_ptr = buffer->buffer.data() + rep(first_offset) + rep(offset); 1858 | last_ptr = buffer->buffer.data() + rep(last_offset); 1859 | return; 1860 | } 1861 | else 1862 | { 1863 | assert(not stack.empty()); 1864 | // This parent is no longer relevant. 1865 | stack.pop_back(); 1866 | auto offset_amount = rep(node.root().left_subtree_length + node.root().piece.length); 1867 | offset = retract(offset, offset_amount); 1868 | node = node.right(); 1869 | stack.push_back({ node }); 1870 | } 1871 | } 1872 | } 1873 | 1874 | ReverseTreeWalker::ReverseTreeWalker(const Tree* tree, CharOffset offset): 1875 | buffers{ &tree->buffers }, 1876 | root{ tree->root }, 1877 | meta{ tree->meta }, 1878 | stack{ { root } }, 1879 | total_offset{ offset } 1880 | { 1881 | fast_forward_to(offset); 1882 | } 1883 | 1884 | ReverseTreeWalker::ReverseTreeWalker(const OwningSnapshot* snap, CharOffset offset): 1885 | buffers{ &snap->buffers }, 1886 | root{ snap->root }, 1887 | meta{ snap->meta }, 1888 | stack{ { root } }, 1889 | total_offset{ offset } 1890 | { 1891 | fast_forward_to(offset); 1892 | } 1893 | 1894 | ReverseTreeWalker::ReverseTreeWalker(const ReferenceSnapshot* snap, CharOffset offset): 1895 | buffers{ snap->buffers }, 1896 | root{ snap->root }, 1897 | meta{ snap->meta }, 1898 | stack{ { root } }, 1899 | total_offset{ offset } 1900 | { 1901 | fast_forward_to(offset); 1902 | } 1903 | 1904 | char ReverseTreeWalker::next() 1905 | { 1906 | if (first_ptr == last_ptr) 1907 | { 1908 | populate_ptrs(); 1909 | // If this is exhausted, we're done. 1910 | if (exhausted()) 1911 | return '\0'; 1912 | // Catchall. 1913 | if (first_ptr == last_ptr) 1914 | return next(); 1915 | } 1916 | // Since CharOffset is unsigned, this will end up wrapping, both 'exhausted' and 1917 | // 'remaining' will return 'true' and '0' respectively. 1918 | total_offset = retract(total_offset); 1919 | // A dereference is the pointer value _before_ this actual pointer, just like 1920 | // STL reverse iterator models. 1921 | return *(--first_ptr); 1922 | } 1923 | 1924 | char ReverseTreeWalker::current() 1925 | { 1926 | if (first_ptr == last_ptr) 1927 | { 1928 | populate_ptrs(); 1929 | // If this is exhausted, we're done. 1930 | if (exhausted()) 1931 | return '\0'; 1932 | } 1933 | return *(first_ptr - 1); 1934 | } 1935 | 1936 | void ReverseTreeWalker::seek(CharOffset offset) 1937 | { 1938 | stack.clear(); 1939 | stack.push_back({ root }); 1940 | total_offset = offset; 1941 | fast_forward_to(offset); 1942 | } 1943 | 1944 | bool ReverseTreeWalker::exhausted() const 1945 | { 1946 | if (stack.empty()) 1947 | return true; 1948 | // If we have not exhausted the pointers, we're still active. 1949 | if (first_ptr != last_ptr) 1950 | return false; 1951 | // If there's more than one entry on the stack, we're still active. 1952 | if (stack.size() > 1) 1953 | return false; 1954 | // Now, if there's exactly one entry and that entry itself is exhausted (no right subtree) 1955 | // we're done. 1956 | auto& entry = stack.back(); 1957 | // We descended into a null child, we're done. 1958 | if (entry.node.is_empty()) 1959 | return true; 1960 | // Do we need this check for reverse iterators? 1961 | if (entry.dir == Direction::Left and entry.node.left().is_empty()) 1962 | return true; 1963 | return false; 1964 | } 1965 | 1966 | Length ReverseTreeWalker::remaining() const 1967 | { 1968 | return distance(CharOffset{}, extend(total_offset)); 1969 | } 1970 | 1971 | void ReverseTreeWalker::populate_ptrs() 1972 | { 1973 | if (exhausted()) 1974 | return; 1975 | if (stack.back().node.is_empty()) 1976 | { 1977 | stack.pop_back(); 1978 | populate_ptrs(); 1979 | return; 1980 | } 1981 | 1982 | auto& [node, dir] = stack.back(); 1983 | if (dir == Direction::Right) 1984 | { 1985 | if (not node.right().is_empty()) 1986 | { 1987 | auto right = node.right(); 1988 | // Change the dir for when we pop back. 1989 | stack.back().dir = Direction::Center; 1990 | stack.push_back({ right }); 1991 | populate_ptrs(); 1992 | return; 1993 | } 1994 | // Otherwise, let's visit the center, we can actually fallthrough. 1995 | stack.back().dir = Direction::Center; 1996 | dir = Direction::Center; 1997 | } 1998 | 1999 | if (dir == Direction::Center) 2000 | { 2001 | auto& piece = node.root().piece; 2002 | auto* buffer = buffers->buffer_at(piece.index); 2003 | auto first_offset = buffers->buffer_offset(piece.index, piece.first); 2004 | auto last_offset = buffers->buffer_offset(piece.index, piece.last); 2005 | last_ptr = buffer->buffer.data() + rep(first_offset); 2006 | first_ptr = buffer->buffer.data() + rep(last_offset); 2007 | // Change this direction. 2008 | stack.back().dir = Direction::Left; 2009 | return; 2010 | } 2011 | 2012 | assert(dir == Direction::Left); 2013 | auto left = node.left(); 2014 | stack.pop_back(); 2015 | stack.push_back({ left }); 2016 | populate_ptrs(); 2017 | } 2018 | 2019 | void ReverseTreeWalker::fast_forward_to(CharOffset offset) 2020 | { 2021 | auto node = root; 2022 | while (not node.is_empty()) 2023 | { 2024 | if (rep(node.root().left_subtree_length) > rep(offset)) 2025 | { 2026 | assert(not stack.empty()); 2027 | // This parent is no longer relevant. 2028 | stack.pop_back(); 2029 | node = node.left(); 2030 | stack.push_back({ node }); 2031 | } 2032 | // It is inside this node. 2033 | else if (rep(node.root().left_subtree_length + node.root().piece.length) > rep(offset)) 2034 | { 2035 | stack.back().dir = Direction::Left; 2036 | // Make the offset relative to this piece. 2037 | offset = retract(offset, rep(node.root().left_subtree_length)); 2038 | auto& piece = node.root().piece; 2039 | auto* buffer = buffers->buffer_at(piece.index); 2040 | auto first_offset = buffers->buffer_offset(piece.index, piece.first); 2041 | last_ptr = buffer->buffer.data() + rep(first_offset); 2042 | // We extend offset because it is the point where we want to start and because this walker works by dereferencing 2043 | // 'first_ptr - 1', offset + 1 is our 'begin'. 2044 | first_ptr = buffer->buffer.data() + rep(first_offset) + rep(extend(offset)); 2045 | return; 2046 | } 2047 | else 2048 | { 2049 | // For when we revisit this node. 2050 | stack.back().dir = Direction::Center; 2051 | auto offset_amount = rep(node.root().left_subtree_length + node.root().piece.length); 2052 | offset = retract(offset, offset_amount); 2053 | node = node.right(); 2054 | stack.push_back({ node }); 2055 | } 2056 | } 2057 | } 2058 | } // namespace PieceTree 2059 | 2060 | // Debugging stuff 2061 | #ifdef TEXTBUF_DEBUG 2062 | void print_tree(const PieceTree::RedBlackTree& root, const PieceTree::Tree* tree, int level = 0, size_t node_offset = 0) 2063 | { 2064 | if (root.is_empty()) 2065 | return; 2066 | const char* levels = "|||||||||||||||||||||||||||||||"; 2067 | auto this_offset = node_offset + rep(root.root().left_subtree_length); 2068 | printf("%.*sme: %p, left: %p, right: %p, color: %s\n", level, levels, root.root_ptr(), root.left().root_ptr(), root.right().root_ptr(), to_string(root.root_color())); 2069 | print_piece(root.root().piece, tree, level); 2070 | printf("%.*sleft_len{%zd}, left_lf{%zd}, node_offset{%zd}\n", level, levels, rep(root.root().left_subtree_length), rep(root.root().left_subtree_lf_count), this_offset); 2071 | printf("\n"); 2072 | print_tree(root.left(), tree, level + 1, node_offset); 2073 | printf("\n"); 2074 | print_tree(root.right(), tree, level + 1, this_offset + rep(root.root().piece.length)); 2075 | } 2076 | 2077 | namespace PieceTree 2078 | { 2079 | void print_tree(const PieceTree::Tree& tree) 2080 | { 2081 | ::print_tree(tree.root, &tree); 2082 | } 2083 | } 2084 | 2085 | void print_buffer(const PieceTree::Tree* tree) 2086 | { 2087 | printf("--- Entire Buffer ---\n"); 2088 | PieceTree::TreeWalker walker{ tree }; 2089 | std::string buf; 2090 | while (not walker.exhausted()) 2091 | { 2092 | buf.push_back(walker.next()); 2093 | } 2094 | 2095 | for (size_t i = 0; i < buf.size(); ++i) 2096 | { 2097 | printf("|%2zu", i); 2098 | } 2099 | printf("\n"); 2100 | for (char c : buf) 2101 | { 2102 | if (c == '\n') 2103 | printf("|\\n"); 2104 | else 2105 | printf("| %c", c); 2106 | } 2107 | printf("\n"); 2108 | } 2109 | 2110 | void flush() 2111 | { 2112 | fflush(stdout); 2113 | } 2114 | #endif // TEXTBUF_DEBUG --------------------------------------------------------------------------------