-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathfredbuf.h
More file actions
414 lines (347 loc) · 13.5 KB
/
fredbuf.h
File metadata and controls
414 lines (347 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
#pragma once
#include <forward_list>
#include <memory>
#include <string_view>
#include <string>
#include <vector>
#include "fredbuf-rbtree.h"
#include "types.h"
#ifndef NDEBUG
#define TEXTBUF_DEBUG
#endif // NDEBUG
// This is a C++ implementation of the textbuf data structure described in
// https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation. The differences are
// that this version is based on immutable data structures to achieve fast undo/redo.
namespace PieceTree
{
struct UndoRedoEntry
{
RedBlackTree root;
CharOffset op_offset;
};
// We need the ability to 'release' old entries in this stack.
using UndoStack = std::forward_list<UndoRedoEntry>;
using RedoStack = std::forward_list<UndoRedoEntry>;
enum class LineStart : size_t { };
using LineStarts = std::vector<LineStart>;
struct NodePosition
{
// Piece Index
const PieceTree::NodeData* node = nullptr;
// Remainder in current piece.
Length remainder = { };
// Node start offset in document.
CharOffset start_offset = { };
// The line (relative to the document) where this node starts.
Line line = { };
};
struct CharBuffer
{
std::string buffer;
LineStarts line_starts;
};
using BufferReference = std::shared_ptr<const CharBuffer>;
using Buffers = std::vector<BufferReference>;
struct BufferCollection
{
const CharBuffer* buffer_at(BufferIndex index) const;
CharOffset buffer_offset(BufferIndex index, const BufferCursor& cursor) const;
Buffers orig_buffers;
CharBuffer mod_buffer;
};
struct LineRange
{
CharOffset first;
CharOffset last; // Does not include LF.
};
struct UndoRedoResult
{
bool success;
CharOffset op_offset;
};
// Owning snapshot owns its own buffer data (performs a lightweight copy) so
// that even if the original tree is destroyed, the owning snapshot can still
// reference the underlying text.
class OwningSnapshot;
// Reference snapshot owns no data and is only valid for as long as the original
// tree buffers are valid.
class ReferenceSnapshot;
// When mutating the tree nodes are saved by default into the undo stack. This
// allows callers to suppress this behavior.
enum class SuppressHistory : bool { No, Yes };
struct BufferMeta
{
LFCount lf_count = { };
Length total_content_length = { };
};
// Indicates whether or not line was missing a CR (e.g. only a '\n' was at the end).
enum class IncompleteCRLF : bool { No, Yes };
class Tree
{
public:
explicit Tree();
explicit Tree(Buffers&& buffers);
// Interface.
// Initialization after populating initial immutable buffers from ctor.
void build_tree();
// Manipulation.
void insert(CharOffset offset, std::string_view txt, SuppressHistory suppress_history = SuppressHistory::No);
void remove(CharOffset offset, Length count, SuppressHistory suppress_history = SuppressHistory::No);
UndoRedoResult try_undo(CharOffset op_offset);
UndoRedoResult try_redo(CharOffset op_offset);
// Direct history manipulation.
// This will commit the current node to the history. The offset provided will be the undo point later.
void commit_head(CharOffset offset);
RedBlackTree head() const;
// Snaps the tree back to the specified root. This needs to be called with a root that is derived from
// the set of buffers based on its creation.
void snap_to(const RedBlackTree& new_root);
// Queries.
void get_line_content(std::string* buf, Line line) const;
[[nodiscard]] IncompleteCRLF get_line_content_crlf(std::string* buf, Line line) const;
char at(CharOffset offset) const;
Line line_at(CharOffset offset) const;
LineRange get_line_range(Line line) const;
LineRange get_line_range_crlf(Line line) const;
LineRange get_line_range_with_newline(Line line) const;
Length length() const
{
return meta.total_content_length;
}
bool is_empty() const
{
return meta.total_content_length == Length{};
}
LFCount line_feed_count() const
{
return meta.lf_count;
}
Length line_count() const
{
return Length{ rep(line_feed_count()) + 1 };
}
OwningSnapshot owning_snap() const;
ReferenceSnapshot ref_snap() const;
private:
friend class TreeWalker;
friend class ReverseTreeWalker;
friend class OwningSnapshot;
friend class ReferenceSnapshot;
#ifdef TEXTBUF_DEBUG
friend void print_piece(const Piece& piece, const Tree* tree, int level);
friend void print_tree(const Tree& tree);
#endif // TEXTBUF_DEBUG
void internal_insert(CharOffset offset, std::string_view txt);
void internal_remove(CharOffset offset, Length count);
using Accumulator = Length(*)(const BufferCollection*, const Piece&, Line);
template <Accumulator accumulate>
static void line_start(CharOffset* offset, const BufferCollection* buffers, const RedBlackTree& node, Line line);
static void line_end_crlf(CharOffset* offset, const BufferCollection* buffers, const RedBlackTree& root, const RedBlackTree& node, Line line);
static Length accumulate_value(const BufferCollection* buffers, const Piece& piece, Line index);
static Length accumulate_value_no_lf(const BufferCollection* buffers, const Piece& piece, Line index);
static void populate_from_node(std::string* buf, const BufferCollection* buffers, const RedBlackTree& node);
static void populate_from_node(std::string* buf, const BufferCollection* buffers, const RedBlackTree& node, Line line_index);
static LFCount line_feed_count(const BufferCollection* buffers, BufferIndex index, const BufferCursor& start, const BufferCursor& end);
static NodePosition node_at(const BufferCollection* buffers, RedBlackTree node, CharOffset off);
static BufferCursor buffer_position(const BufferCollection* buffers, const Piece& piece, Length remainder);
static char char_at(const BufferCollection* buffers, const RedBlackTree& node, CharOffset offset);
static Piece trim_piece_right(const BufferCollection* buffers, const Piece& piece, const BufferCursor& pos);
static Piece trim_piece_left(const BufferCollection* buffers, const Piece& piece, const BufferCursor& pos);
struct ShrinkResult
{
Piece left;
Piece right;
};
static ShrinkResult shrink_piece(const BufferCollection* buffers, const Piece& piece, const BufferCursor& first, const BufferCursor& last);
// Direct mutations.
void assemble_line(std::string* buf, const RedBlackTree& node, Line line) const;
Piece build_piece(std::string_view txt);
void combine_pieces(NodePosition existing_piece, Piece new_piece);
void remove_node_range(NodePosition first, Length length);
void compute_buffer_meta();
void append_undo(const RedBlackTree& old_root, CharOffset op_offset);
BufferCollection buffers;
//Buffers buffers;
//CharBuffer mod_buffer;
PieceTree::RedBlackTree root;
LineStarts scratch_starts;
BufferCursor last_insert;
// Note: This is absolute position. Initialize to nonsense value.
CharOffset end_last_insert = CharOffset::Sentinel;
BufferMeta meta;
UndoStack undo_stack;
RedoStack redo_stack;
};
class OwningSnapshot
{
public:
explicit OwningSnapshot(const Tree* tree);
explicit OwningSnapshot(const Tree* tree, const RedBlackTree& dt);
// Queries.
void get_line_content(std::string* buf, Line line) const;
[[nodiscard]] IncompleteCRLF get_line_content_crlf(std::string* buf, Line line) const;
Line line_at(CharOffset offset) const;
LineRange get_line_range(Line line) const;
LineRange get_line_range_crlf(Line line) const;
LineRange get_line_range_with_newline(Line line) const;
bool is_empty() const
{
return meta.total_content_length == Length{};
}
Length line_count() const
{
return Length{ rep(meta.lf_count) + 1 };
}
private:
friend class TreeWalker;
friend class ReverseTreeWalker;
RedBlackTree root;
BufferMeta meta;
// This should be fairly lightweight. The original buffers
// will retain the majority of the memory consumption.
BufferCollection buffers;
};
class ReferenceSnapshot
{
public:
explicit ReferenceSnapshot(const Tree* tree);
explicit ReferenceSnapshot(const Tree* tree, const RedBlackTree& dt);
// Queries.
void get_line_content(std::string* buf, Line line) const;
[[nodiscard]] IncompleteCRLF get_line_content_crlf(std::string* buf, Line line) const;
Line line_at(CharOffset offset) const;
LineRange get_line_range(Line line) const;
LineRange get_line_range_crlf(Line line) const;
LineRange get_line_range_with_newline(Line line) const;
bool is_empty() const
{
return meta.total_content_length == Length{};
}
Length line_count() const
{
return Length{ rep(meta.lf_count) + 1 };
}
private:
friend class TreeWalker;
friend class ReverseTreeWalker;
RedBlackTree root;
BufferMeta meta;
// A reference to the underlying tree buffers.
const BufferCollection* buffers;
};
struct TreeBuilder
{
Buffers buffers;
LineStarts scratch_starts;
void accept(std::string_view txt);
Tree create()
{
return Tree{ std::move(buffers) };
}
};
class TreeWalker
{
public:
TreeWalker(const Tree* tree, CharOffset offset = CharOffset{ });
TreeWalker(const OwningSnapshot* snap, CharOffset offset = CharOffset{ });
TreeWalker(const ReferenceSnapshot* snap, CharOffset offset = CharOffset{ });
TreeWalker(const TreeWalker&) = delete;
char current();
char next();
void seek(CharOffset offset);
bool exhausted() const;
Length remaining() const;
CharOffset offset() const
{
return total_offset;
}
// For Iterator-like behavior.
TreeWalker& operator++()
{
return *this;
}
char operator*()
{
return next();
}
private:
void populate_ptrs();
void fast_forward_to(CharOffset offset);
enum class Direction { Left, Center, Right };
struct StackEntry
{
PieceTree::RedBlackTree node;
Direction dir = Direction::Left;
};
const BufferCollection* buffers;
RedBlackTree root;
BufferMeta meta;
std::vector<StackEntry> stack;
CharOffset total_offset = CharOffset{ 0 };
const char* first_ptr = nullptr;
const char* last_ptr = nullptr;
};
class ReverseTreeWalker
{
public:
ReverseTreeWalker(const Tree* tree, CharOffset offset = CharOffset{ });
ReverseTreeWalker(const OwningSnapshot* snap, CharOffset offset = CharOffset{ });
ReverseTreeWalker(const ReferenceSnapshot* snap, CharOffset offset = CharOffset{ });
ReverseTreeWalker(const TreeWalker&) = delete;
char current();
char next();
void seek(CharOffset offset);
bool exhausted() const;
Length remaining() const;
CharOffset offset() const
{
return total_offset;
}
// For Iterator-like behavior.
ReverseTreeWalker& operator++()
{
return *this;
}
char operator*()
{
return next();
}
private:
void populate_ptrs();
void fast_forward_to(CharOffset offset);
enum class Direction { Left, Center, Right };
struct StackEntry
{
PieceTree::RedBlackTree node;
Direction dir = Direction::Right;
};
const BufferCollection* buffers;
RedBlackTree root;
BufferMeta meta;
std::vector<StackEntry> stack;
CharOffset total_offset = CharOffset{ 0 };
const char* first_ptr = nullptr;
const char* last_ptr = nullptr;
};
struct WalkSentinel { };
inline TreeWalker begin(const Tree& tree)
{
return TreeWalker{ &tree };
}
constexpr WalkSentinel end(const Tree&)
{
return WalkSentinel{ };
}
inline bool operator==(const TreeWalker& walker, WalkSentinel)
{
return walker.exhausted();
}
enum class EmptySelection : bool { No, Yes };
struct SelectionMeta
{
OwningSnapshot snap;
Offset first;
Offset last;
EmptySelection empty;
};
} // namespace PieceTree