1 | // Scintilla source code edit control |
2 | /** @file CellBuffer.cxx |
3 | ** Manages a buffer of cells. |
4 | **/ |
5 | // Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org> |
6 | // The License.txt file describes the conditions under which this software may be distributed. |
7 | |
8 | #include <cstddef> |
9 | #include <cstdlib> |
10 | #include <cassert> |
11 | #include <cstring> |
12 | #include <cstdio> |
13 | #include <cstdarg> |
14 | |
15 | #include <stdexcept> |
16 | #include <string> |
17 | #include <string_view> |
18 | #include <vector> |
19 | #include <optional> |
20 | #include <algorithm> |
21 | #include <memory> |
22 | |
23 | #include "ScintillaTypes.h" |
24 | |
25 | #include "Debugging.h" |
26 | |
27 | #include "Position.h" |
28 | #include "SplitVector.h" |
29 | #include "Partitioning.h" |
30 | #include "CellBuffer.h" |
31 | #include "UniConversion.h" |
32 | |
33 | namespace Scintilla::Internal { |
34 | |
35 | struct CountWidths { |
36 | // Measures the number of characters in a string divided into those |
37 | // from the Base Multilingual Plane and those from other planes. |
38 | Sci::Position countBasePlane; |
39 | Sci::Position countOtherPlanes; |
40 | explicit CountWidths(Sci::Position countBasePlane_=0, Sci::Position countOtherPlanes_=0) noexcept : |
41 | countBasePlane(countBasePlane_), |
42 | countOtherPlanes(countOtherPlanes_) { |
43 | } |
44 | CountWidths operator-() const noexcept { |
45 | return CountWidths(-countBasePlane , -countOtherPlanes); |
46 | } |
47 | Sci::Position WidthUTF32() const noexcept { |
48 | // All code points take one code unit in UTF-32. |
49 | return countBasePlane + countOtherPlanes; |
50 | } |
51 | Sci::Position WidthUTF16() const noexcept { |
52 | // UTF-16 takes 2 code units for other planes |
53 | return countBasePlane + 2 * countOtherPlanes; |
54 | } |
55 | void CountChar(int lenChar) noexcept { |
56 | if (lenChar == 4) { |
57 | countOtherPlanes++; |
58 | } else { |
59 | countBasePlane++; |
60 | } |
61 | } |
62 | }; |
63 | |
64 | class ILineVector { |
65 | public: |
66 | virtual void Init() = 0; |
67 | virtual void SetPerLine(PerLine *pl) noexcept = 0; |
68 | virtual void InsertText(Sci::Line line, Sci::Position delta) noexcept = 0; |
69 | virtual void InsertLine(Sci::Line line, Sci::Position position, bool lineStart) = 0; |
70 | virtual void InsertLines(Sci::Line line, const Sci::Position *positions, size_t lines, bool lineStart) = 0; |
71 | virtual void SetLineStart(Sci::Line line, Sci::Position position) noexcept = 0; |
72 | virtual void RemoveLine(Sci::Line line) = 0; |
73 | virtual Sci::Line Lines() const noexcept = 0; |
74 | virtual void AllocateLines(Sci::Line lines) = 0; |
75 | virtual Sci::Line LineFromPosition(Sci::Position pos) const noexcept = 0; |
76 | virtual Sci::Position LineStart(Sci::Line line) const noexcept = 0; |
77 | virtual void InsertCharacters(Sci::Line line, CountWidths delta) noexcept = 0; |
78 | virtual void SetLineCharactersWidth(Sci::Line line, CountWidths width) noexcept = 0; |
79 | virtual Scintilla::LineCharacterIndexType LineCharacterIndex() const noexcept = 0; |
80 | virtual bool AllocateLineCharacterIndex(Scintilla::LineCharacterIndexType lineCharacterIndex, Sci::Line lines) = 0; |
81 | virtual bool ReleaseLineCharacterIndex(Scintilla::LineCharacterIndexType lineCharacterIndex) = 0; |
82 | virtual Sci::Position IndexLineStart(Sci::Line line, Scintilla::LineCharacterIndexType lineCharacterIndex) const noexcept = 0; |
83 | virtual Sci::Line LineFromPositionIndex(Sci::Position pos, Scintilla::LineCharacterIndexType lineCharacterIndex) const noexcept = 0; |
84 | virtual ~ILineVector() {} |
85 | }; |
86 | |
87 | } |
88 | |
89 | using namespace Scintilla; |
90 | using namespace Scintilla::Internal; |
91 | |
92 | template <typename POS> |
93 | class LineStartIndex { |
94 | public: |
95 | int refCount; |
96 | Partitioning<POS> starts; |
97 | |
98 | LineStartIndex() : refCount(0), starts(4) { |
99 | // Minimal initial allocation |
100 | } |
101 | // Deleted so LineStartIndex objects can not be copied. |
102 | LineStartIndex(const LineStartIndex &) = delete; |
103 | LineStartIndex(LineStartIndex &&) = delete; |
104 | void operator=(const LineStartIndex &) = delete; |
105 | void operator=(LineStartIndex &&) = delete; |
106 | virtual ~LineStartIndex() { |
107 | } |
108 | bool Allocate(Sci::Line lines) { |
109 | refCount++; |
110 | Sci::Position length = starts.PositionFromPartition(starts.Partitions()); |
111 | for (Sci::Line line = starts.Partitions(); line < lines; line++) { |
112 | // Produce an ascending sequence that will be filled in with correct widths later |
113 | length++; |
114 | starts.InsertPartition(static_cast<POS>(line), static_cast<POS>(length)); |
115 | } |
116 | return refCount == 1; |
117 | } |
118 | bool Release() { |
119 | if (refCount == 1) { |
120 | starts.DeleteAll(); |
121 | } |
122 | refCount--; |
123 | return refCount == 0; |
124 | } |
125 | bool Active() const noexcept { |
126 | return refCount > 0; |
127 | } |
128 | Sci::Position LineWidth(Sci::Line line) const noexcept { |
129 | return starts.PositionFromPartition(static_cast<POS>(line) + 1) - |
130 | starts.PositionFromPartition(static_cast<POS>(line)); |
131 | } |
132 | void SetLineWidth(Sci::Line line, Sci::Position width) noexcept { |
133 | const Sci::Position widthCurrent = LineWidth(line); |
134 | starts.InsertText(static_cast<POS>(line), static_cast<POS>(width - widthCurrent)); |
135 | } |
136 | void AllocateLines(Sci::Line lines) { |
137 | if (lines > starts.Partitions()) { |
138 | starts.ReAllocate(lines); |
139 | } |
140 | } |
141 | void InsertLines(Sci::Line line, Sci::Line lines) { |
142 | // Insert multiple lines with each temporarily 1 character wide. |
143 | // The line widths will be fixed up by later measuring code. |
144 | const POS lineAsPos = static_cast<POS>(line); |
145 | const POS lineStart = starts.PositionFromPartition(lineAsPos - 1) + 1; |
146 | for (POS l = 0; l < static_cast<POS>(lines); l++) { |
147 | starts.InsertPartition(lineAsPos + l, lineStart + l); |
148 | } |
149 | } |
150 | }; |
151 | |
152 | template <typename POS> |
153 | class LineVector : public ILineVector { |
154 | Partitioning<POS> starts; |
155 | PerLine *perLine; |
156 | LineStartIndex<POS> startsUTF16; |
157 | LineStartIndex<POS> startsUTF32; |
158 | LineCharacterIndexType activeIndices; |
159 | |
160 | void SetActiveIndices() noexcept { |
161 | activeIndices = |
162 | (startsUTF32.Active() ? LineCharacterIndexType::Utf32 : LineCharacterIndexType::None) |
163 | | (startsUTF16.Active() ? LineCharacterIndexType::Utf16 : LineCharacterIndexType::None); |
164 | } |
165 | |
166 | public: |
167 | LineVector() : starts(256), perLine(nullptr), activeIndices(LineCharacterIndexType::None) { |
168 | } |
169 | // Deleted so LineVector objects can not be copied. |
170 | LineVector(const LineVector &) = delete; |
171 | LineVector(LineVector &&) = delete; |
172 | LineVector &operator=(const LineVector &) = delete; |
173 | LineVector &operator=(LineVector &&) = delete; |
174 | ~LineVector() override { |
175 | } |
176 | void Init() override { |
177 | starts.DeleteAll(); |
178 | if (perLine) { |
179 | perLine->Init(); |
180 | } |
181 | startsUTF32.starts.DeleteAll(); |
182 | startsUTF16.starts.DeleteAll(); |
183 | } |
184 | void SetPerLine(PerLine *pl) noexcept override { |
185 | perLine = pl; |
186 | } |
187 | void InsertText(Sci::Line line, Sci::Position delta) noexcept override { |
188 | starts.InsertText(static_cast<POS>(line), static_cast<POS>(delta)); |
189 | } |
190 | void InsertLine(Sci::Line line, Sci::Position position, bool lineStart) override { |
191 | const POS lineAsPos = static_cast<POS>(line); |
192 | starts.InsertPartition(lineAsPos, static_cast<POS>(position)); |
193 | if (activeIndices != LineCharacterIndexType::None) { |
194 | if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { |
195 | startsUTF32.InsertLines(line, 1); |
196 | } |
197 | if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { |
198 | startsUTF16.InsertLines(line, 1); |
199 | } |
200 | } |
201 | if (perLine) { |
202 | if ((line > 0) && lineStart) |
203 | line--; |
204 | perLine->InsertLine(line); |
205 | } |
206 | } |
207 | void InsertLines(Sci::Line line, const Sci::Position *positions, size_t lines, bool lineStart) override { |
208 | const POS lineAsPos = static_cast<POS>(line); |
209 | if constexpr (sizeof(Sci::Position) == sizeof(POS)) { |
210 | starts.InsertPartitions(lineAsPos, positions, lines); |
211 | } else { |
212 | starts.InsertPartitionsWithCast(lineAsPos, positions, lines); |
213 | } |
214 | if (activeIndices != LineCharacterIndexType::None) { |
215 | if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { |
216 | startsUTF32.InsertLines(line, lines); |
217 | } |
218 | if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { |
219 | startsUTF16.InsertLines(line, lines); |
220 | } |
221 | } |
222 | if (perLine) { |
223 | if ((line > 0) && lineStart) |
224 | line--; |
225 | perLine->InsertLines(line, lines); |
226 | } |
227 | } |
228 | void SetLineStart(Sci::Line line, Sci::Position position) noexcept override { |
229 | starts.SetPartitionStartPosition(static_cast<POS>(line), static_cast<POS>(position)); |
230 | } |
231 | void RemoveLine(Sci::Line line) override { |
232 | starts.RemovePartition(static_cast<POS>(line)); |
233 | if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { |
234 | startsUTF32.starts.RemovePartition(static_cast<POS>(line)); |
235 | } |
236 | if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { |
237 | startsUTF16.starts.RemovePartition(static_cast<POS>(line)); |
238 | } |
239 | if (perLine) { |
240 | perLine->RemoveLine(line); |
241 | } |
242 | } |
243 | Sci::Line Lines() const noexcept override { |
244 | return static_cast<Sci::Line>(starts.Partitions()); |
245 | } |
246 | void AllocateLines(Sci::Line lines) override { |
247 | if (lines > Lines()) { |
248 | starts.ReAllocate(lines); |
249 | if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { |
250 | startsUTF32.AllocateLines(lines); |
251 | } |
252 | if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { |
253 | startsUTF16.AllocateLines(lines); |
254 | } |
255 | } |
256 | } |
257 | Sci::Line LineFromPosition(Sci::Position pos) const noexcept override { |
258 | return static_cast<Sci::Line>(starts.PartitionFromPosition(static_cast<POS>(pos))); |
259 | } |
260 | Sci::Position LineStart(Sci::Line line) const noexcept override { |
261 | return starts.PositionFromPartition(static_cast<POS>(line)); |
262 | } |
263 | void InsertCharacters(Sci::Line line, CountWidths delta) noexcept override { |
264 | if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { |
265 | startsUTF32.starts.InsertText(static_cast<POS>(line), static_cast<POS>(delta.WidthUTF32())); |
266 | } |
267 | if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { |
268 | startsUTF16.starts.InsertText(static_cast<POS>(line), static_cast<POS>(delta.WidthUTF16())); |
269 | } |
270 | } |
271 | void SetLineCharactersWidth(Sci::Line line, CountWidths width) noexcept override { |
272 | if (FlagSet(activeIndices, LineCharacterIndexType::Utf32)) { |
273 | assert(startsUTF32.starts.Partitions() == starts.Partitions()); |
274 | startsUTF32.SetLineWidth(line, width.WidthUTF32()); |
275 | } |
276 | if (FlagSet(activeIndices, LineCharacterIndexType::Utf16)) { |
277 | assert(startsUTF16.starts.Partitions() == starts.Partitions()); |
278 | startsUTF16.SetLineWidth(line, width.WidthUTF16()); |
279 | } |
280 | } |
281 | |
282 | LineCharacterIndexType LineCharacterIndex() const noexcept override { |
283 | return activeIndices; |
284 | } |
285 | bool AllocateLineCharacterIndex(LineCharacterIndexType lineCharacterIndex, Sci::Line lines) override { |
286 | const LineCharacterIndexType activeIndicesStart = activeIndices; |
287 | if (FlagSet(lineCharacterIndex, LineCharacterIndexType::Utf32)) { |
288 | startsUTF32.Allocate(lines); |
289 | assert(startsUTF32.starts.Partitions() == starts.Partitions()); |
290 | } |
291 | if (FlagSet(lineCharacterIndex, LineCharacterIndexType::Utf16)) { |
292 | startsUTF16.Allocate(lines); |
293 | assert(startsUTF16.starts.Partitions() == starts.Partitions()); |
294 | } |
295 | SetActiveIndices(); |
296 | return activeIndicesStart != activeIndices; |
297 | } |
298 | bool ReleaseLineCharacterIndex(LineCharacterIndexType lineCharacterIndex) override { |
299 | const LineCharacterIndexType activeIndicesStart = activeIndices; |
300 | if (FlagSet(lineCharacterIndex, LineCharacterIndexType::Utf32)) { |
301 | startsUTF32.Release(); |
302 | } |
303 | if (FlagSet(lineCharacterIndex, LineCharacterIndexType::Utf16)) { |
304 | startsUTF16.Release(); |
305 | } |
306 | SetActiveIndices(); |
307 | return activeIndicesStart != activeIndices; |
308 | } |
309 | Sci::Position IndexLineStart(Sci::Line line, LineCharacterIndexType lineCharacterIndex) const noexcept override { |
310 | if (lineCharacterIndex == LineCharacterIndexType::Utf32) { |
311 | return startsUTF32.starts.PositionFromPartition(static_cast<POS>(line)); |
312 | } else { |
313 | return startsUTF16.starts.PositionFromPartition(static_cast<POS>(line)); |
314 | } |
315 | } |
316 | Sci::Line LineFromPositionIndex(Sci::Position pos, LineCharacterIndexType lineCharacterIndex) const noexcept override { |
317 | if (lineCharacterIndex == LineCharacterIndexType::Utf32) { |
318 | return static_cast<Sci::Line>(startsUTF32.starts.PartitionFromPosition(static_cast<POS>(pos))); |
319 | } else { |
320 | return static_cast<Sci::Line>(startsUTF16.starts.PartitionFromPosition(static_cast<POS>(pos))); |
321 | } |
322 | } |
323 | }; |
324 | |
325 | Action::Action() noexcept { |
326 | at = ActionType::start; |
327 | position = 0; |
328 | lenData = 0; |
329 | mayCoalesce = false; |
330 | } |
331 | |
332 | Action::~Action() { |
333 | } |
334 | |
335 | void Action::Create(ActionType at_, Sci::Position position_, const char *data_, Sci::Position lenData_, bool mayCoalesce_) { |
336 | data = nullptr; |
337 | position = position_; |
338 | at = at_; |
339 | if (lenData_) { |
340 | data = std::make_unique<char[]>(lenData_); |
341 | memcpy(&data[0], data_, lenData_); |
342 | } |
343 | lenData = lenData_; |
344 | mayCoalesce = mayCoalesce_; |
345 | } |
346 | |
347 | void Action::Clear() noexcept { |
348 | data = nullptr; |
349 | lenData = 0; |
350 | } |
351 | |
352 | // The undo history stores a sequence of user operations that represent the user's view of the |
353 | // commands executed on the text. |
354 | // Each user operation contains a sequence of text insertion and text deletion actions. |
355 | // All the user operations are stored in a list of individual actions with 'start' actions used |
356 | // as delimiters between user operations. |
357 | // Initially there is one start action in the history. |
358 | // As each action is performed, it is recorded in the history. The action may either become |
359 | // part of the current user operation or may start a new user operation. If it is to be part of the |
360 | // current operation, then it overwrites the current last action. If it is to be part of a new |
361 | // operation, it is appended after the current last action. |
362 | // After writing the new action, a new start action is appended at the end of the history. |
363 | // The decision of whether to start a new user operation is based upon two factors. If a |
364 | // compound operation has been explicitly started by calling BeginUndoAction and no matching |
365 | // EndUndoAction (these calls nest) has been called, then the action is coalesced into the current |
366 | // operation. If there is no outstanding BeginUndoAction call then a new operation is started |
367 | // unless it looks as if the new action is caused by the user typing or deleting a stream of text. |
368 | // Sequences that look like typing or deletion are coalesced into a single user operation. |
369 | |
370 | UndoHistory::UndoHistory() { |
371 | |
372 | actions.resize(3); |
373 | maxAction = 0; |
374 | currentAction = 0; |
375 | undoSequenceDepth = 0; |
376 | savePoint = 0; |
377 | tentativePoint = -1; |
378 | |
379 | actions[currentAction].Create(ActionType::start); |
380 | } |
381 | |
382 | UndoHistory::~UndoHistory() { |
383 | } |
384 | |
385 | void UndoHistory::EnsureUndoRoom() { |
386 | // Have to test that there is room for 2 more actions in the array |
387 | // as two actions may be created by the calling function |
388 | if (static_cast<size_t>(currentAction) >= (actions.size() - 2)) { |
389 | // Run out of undo nodes so extend the array |
390 | actions.resize(actions.size() * 2); |
391 | } |
392 | } |
393 | |
394 | const char *UndoHistory::AppendAction(ActionType at, Sci::Position position, const char *data, Sci::Position lengthData, |
395 | bool &startSequence, bool mayCoalesce) { |
396 | EnsureUndoRoom(); |
397 | //Platform::DebugPrintf("%% %d action %d %d %d\n", at, position, lengthData, currentAction); |
398 | //Platform::DebugPrintf("^ %d action %d %d\n", actions[currentAction - 1].at, |
399 | // actions[currentAction - 1].position, actions[currentAction - 1].lenData); |
400 | if (currentAction < savePoint) { |
401 | savePoint = -1; |
402 | } |
403 | int oldCurrentAction = currentAction; |
404 | if (currentAction >= 1) { |
405 | if (0 == undoSequenceDepth) { |
406 | // Top level actions may not always be coalesced |
407 | int targetAct = -1; |
408 | const Action *actPrevious = &(actions[currentAction + targetAct]); |
409 | // Container actions may forward the coalesce state of Scintilla Actions. |
410 | while ((actPrevious->at == ActionType::container) && actPrevious->mayCoalesce) { |
411 | targetAct--; |
412 | actPrevious = &(actions[currentAction + targetAct]); |
413 | } |
414 | // See if current action can be coalesced into previous action |
415 | // Will work if both are inserts or deletes and position is same |
416 | if ((currentAction == savePoint) || (currentAction == tentativePoint)) { |
417 | currentAction++; |
418 | } else if (!actions[currentAction].mayCoalesce) { |
419 | // Not allowed to coalesce if this set |
420 | currentAction++; |
421 | } else if (!mayCoalesce || !actPrevious->mayCoalesce) { |
422 | currentAction++; |
423 | } else if (at == ActionType::container || actions[currentAction].at == ActionType::container) { |
424 | ; // A coalescible containerAction |
425 | } else if ((at != actPrevious->at) && (actPrevious->at != ActionType::start)) { |
426 | currentAction++; |
427 | } else if ((at == ActionType::insert) && |
428 | (position != (actPrevious->position + actPrevious->lenData))) { |
429 | // Insertions must be immediately after to coalesce |
430 | currentAction++; |
431 | } else if (at == ActionType::remove) { |
432 | if ((lengthData == 1) || (lengthData == 2)) { |
433 | if ((position + lengthData) == actPrevious->position) { |
434 | ; // Backspace -> OK |
435 | } else if (position == actPrevious->position) { |
436 | ; // Delete -> OK |
437 | } else { |
438 | // Removals must be at same position to coalesce |
439 | currentAction++; |
440 | } |
441 | } else { |
442 | // Removals must be of one character to coalesce |
443 | currentAction++; |
444 | } |
445 | } else { |
446 | // Action coalesced. |
447 | } |
448 | |
449 | } else { |
450 | // Actions not at top level are always coalesced unless this is after return to top level |
451 | if (!actions[currentAction].mayCoalesce) |
452 | currentAction++; |
453 | } |
454 | } else { |
455 | currentAction++; |
456 | } |
457 | startSequence = oldCurrentAction != currentAction; |
458 | const int actionWithData = currentAction; |
459 | actions[currentAction].Create(at, position, data, lengthData, mayCoalesce); |
460 | currentAction++; |
461 | actions[currentAction].Create(ActionType::start); |
462 | maxAction = currentAction; |
463 | return actions[actionWithData].data.get(); |
464 | } |
465 | |
466 | void UndoHistory::BeginUndoAction() { |
467 | EnsureUndoRoom(); |
468 | if (undoSequenceDepth == 0) { |
469 | if (actions[currentAction].at != ActionType::start) { |
470 | currentAction++; |
471 | actions[currentAction].Create(ActionType::start); |
472 | maxAction = currentAction; |
473 | } |
474 | actions[currentAction].mayCoalesce = false; |
475 | } |
476 | undoSequenceDepth++; |
477 | } |
478 | |
479 | void UndoHistory::EndUndoAction() { |
480 | PLATFORM_ASSERT(undoSequenceDepth > 0); |
481 | EnsureUndoRoom(); |
482 | undoSequenceDepth--; |
483 | if (0 == undoSequenceDepth) { |
484 | if (actions[currentAction].at != ActionType::start) { |
485 | currentAction++; |
486 | actions[currentAction].Create(ActionType::start); |
487 | maxAction = currentAction; |
488 | } |
489 | actions[currentAction].mayCoalesce = false; |
490 | } |
491 | } |
492 | |
493 | void UndoHistory::DropUndoSequence() { |
494 | undoSequenceDepth = 0; |
495 | } |
496 | |
497 | void UndoHistory::DeleteUndoHistory() { |
498 | for (int i = 1; i < maxAction; i++) |
499 | actions[i].Clear(); |
500 | maxAction = 0; |
501 | currentAction = 0; |
502 | actions[currentAction].Create(ActionType::start); |
503 | savePoint = 0; |
504 | tentativePoint = -1; |
505 | } |
506 | |
507 | void UndoHistory::SetSavePoint() noexcept { |
508 | savePoint = currentAction; |
509 | } |
510 | |
511 | bool UndoHistory::IsSavePoint() const noexcept { |
512 | return savePoint == currentAction; |
513 | } |
514 | |
515 | void UndoHistory::TentativeStart() { |
516 | tentativePoint = currentAction; |
517 | } |
518 | |
519 | void UndoHistory::TentativeCommit() { |
520 | tentativePoint = -1; |
521 | // Truncate undo history |
522 | maxAction = currentAction; |
523 | } |
524 | |
525 | bool UndoHistory::TentativeActive() const noexcept { |
526 | return tentativePoint >= 0; |
527 | } |
528 | |
529 | int UndoHistory::TentativeSteps() noexcept { |
530 | // Drop any trailing startAction |
531 | if (actions[currentAction].at == ActionType::start && currentAction > 0) |
532 | currentAction--; |
533 | if (tentativePoint >= 0) |
534 | return currentAction - tentativePoint; |
535 | else |
536 | return -1; |
537 | } |
538 | |
539 | bool UndoHistory::CanUndo() const noexcept { |
540 | return (currentAction > 0) && (maxAction > 0); |
541 | } |
542 | |
543 | int UndoHistory::StartUndo() { |
544 | // Drop any trailing startAction |
545 | if (actions[currentAction].at == ActionType::start && currentAction > 0) |
546 | currentAction--; |
547 | |
548 | // Count the steps in this action |
549 | int act = currentAction; |
550 | while (actions[act].at != ActionType::start && act > 0) { |
551 | act--; |
552 | } |
553 | return currentAction - act; |
554 | } |
555 | |
556 | const Action &UndoHistory::GetUndoStep() const { |
557 | return actions[currentAction]; |
558 | } |
559 | |
560 | void UndoHistory::CompletedUndoStep() { |
561 | currentAction--; |
562 | } |
563 | |
564 | bool UndoHistory::CanRedo() const noexcept { |
565 | return maxAction > currentAction; |
566 | } |
567 | |
568 | int UndoHistory::StartRedo() { |
569 | // Drop any leading startAction |
570 | if (currentAction < maxAction && actions[currentAction].at == ActionType::start) |
571 | currentAction++; |
572 | |
573 | // Count the steps in this action |
574 | int act = currentAction; |
575 | while (act < maxAction && actions[act].at != ActionType::start) { |
576 | act++; |
577 | } |
578 | return act - currentAction; |
579 | } |
580 | |
581 | const Action &UndoHistory::GetRedoStep() const { |
582 | return actions[currentAction]; |
583 | } |
584 | |
585 | void UndoHistory::CompletedRedoStep() { |
586 | currentAction++; |
587 | } |
588 | |
589 | CellBuffer::CellBuffer(bool hasStyles_, bool largeDocument_) : |
590 | hasStyles(hasStyles_), largeDocument(largeDocument_) { |
591 | readOnly = false; |
592 | utf8Substance = false; |
593 | utf8LineEnds = LineEndType::Default; |
594 | collectingUndo = true; |
595 | if (largeDocument) |
596 | plv = std::make_unique<LineVector<Sci::Position>>(); |
597 | else |
598 | plv = std::make_unique<LineVector<int>>(); |
599 | } |
600 | |
601 | CellBuffer::~CellBuffer() { |
602 | } |
603 | |
604 | char CellBuffer::CharAt(Sci::Position position) const noexcept { |
605 | return substance.ValueAt(position); |
606 | } |
607 | |
608 | unsigned char CellBuffer::UCharAt(Sci::Position position) const noexcept { |
609 | return substance.ValueAt(position); |
610 | } |
611 | |
612 | void CellBuffer::GetCharRange(char *buffer, Sci::Position position, Sci::Position lengthRetrieve) const { |
613 | if (lengthRetrieve <= 0) |
614 | return; |
615 | if (position < 0) |
616 | return; |
617 | if ((position + lengthRetrieve) > substance.Length()) { |
618 | Platform::DebugPrintf("Bad GetCharRange %.0f for %.0f of %.0f\n" , |
619 | static_cast<double>(position), |
620 | static_cast<double>(lengthRetrieve), |
621 | static_cast<double>(substance.Length())); |
622 | return; |
623 | } |
624 | substance.GetRange(buffer, position, lengthRetrieve); |
625 | } |
626 | |
627 | char CellBuffer::StyleAt(Sci::Position position) const noexcept { |
628 | return hasStyles ? style.ValueAt(position) : 0; |
629 | } |
630 | |
631 | void CellBuffer::GetStyleRange(unsigned char *buffer, Sci::Position position, Sci::Position lengthRetrieve) const { |
632 | if (lengthRetrieve < 0) |
633 | return; |
634 | if (position < 0) |
635 | return; |
636 | if (!hasStyles) { |
637 | std::fill(buffer, buffer + lengthRetrieve, static_cast<unsigned char>(0)); |
638 | return; |
639 | } |
640 | if ((position + lengthRetrieve) > style.Length()) { |
641 | Platform::DebugPrintf("Bad GetStyleRange %.0f for %.0f of %.0f\n" , |
642 | static_cast<double>(position), |
643 | static_cast<double>(lengthRetrieve), |
644 | static_cast<double>(style.Length())); |
645 | return; |
646 | } |
647 | style.GetRange(reinterpret_cast<char *>(buffer), position, lengthRetrieve); |
648 | } |
649 | |
650 | const char *CellBuffer::BufferPointer() { |
651 | return substance.BufferPointer(); |
652 | } |
653 | |
654 | const char *CellBuffer::RangePointer(Sci::Position position, Sci::Position rangeLength) noexcept { |
655 | return substance.RangePointer(position, rangeLength); |
656 | } |
657 | |
658 | Sci::Position CellBuffer::GapPosition() const noexcept { |
659 | return substance.GapPosition(); |
660 | } |
661 | |
662 | SplitView CellBuffer::AllView() const noexcept { |
663 | const size_t length = substance.Length(); |
664 | size_t length1 = substance.GapPosition(); |
665 | if (length1 == 0) { |
666 | // Assign segment2 to segment1 / length1 to avoid useless test against 0 length1 |
667 | length1 = length; |
668 | } |
669 | return SplitView { |
670 | substance.ElementPointer(0), |
671 | length1, |
672 | substance.ElementPointer(length1) - length1, |
673 | length |
674 | }; |
675 | } |
676 | |
677 | // The char* returned is to an allocation owned by the undo history |
678 | const char *CellBuffer::InsertString(Sci::Position position, const char *s, Sci::Position insertLength, bool &startSequence) { |
679 | // InsertString and DeleteChars are the bottleneck though which all changes occur |
680 | const char *data = s; |
681 | if (!readOnly) { |
682 | if (collectingUndo) { |
683 | // Save into the undo/redo stack, but only the characters - not the formatting |
684 | // This takes up about half load time |
685 | data = uh.AppendAction(ActionType::insert, position, s, insertLength, startSequence); |
686 | } |
687 | |
688 | BasicInsertString(position, s, insertLength); |
689 | } |
690 | return data; |
691 | } |
692 | |
693 | bool CellBuffer::SetStyleAt(Sci::Position position, char styleValue) noexcept { |
694 | if (!hasStyles) { |
695 | return false; |
696 | } |
697 | const char curVal = style.ValueAt(position); |
698 | if (curVal != styleValue) { |
699 | style.SetValueAt(position, styleValue); |
700 | return true; |
701 | } else { |
702 | return false; |
703 | } |
704 | } |
705 | |
706 | bool CellBuffer::SetStyleFor(Sci::Position position, Sci::Position lengthStyle, char styleValue) noexcept { |
707 | if (!hasStyles) { |
708 | return false; |
709 | } |
710 | bool changed = false; |
711 | PLATFORM_ASSERT(lengthStyle == 0 || |
712 | (lengthStyle > 0 && lengthStyle + position <= style.Length())); |
713 | while (lengthStyle--) { |
714 | const char curVal = style.ValueAt(position); |
715 | if (curVal != styleValue) { |
716 | style.SetValueAt(position, styleValue); |
717 | changed = true; |
718 | } |
719 | position++; |
720 | } |
721 | return changed; |
722 | } |
723 | |
724 | // The char* returned is to an allocation owned by the undo history |
725 | const char *CellBuffer::DeleteChars(Sci::Position position, Sci::Position deleteLength, bool &startSequence) { |
726 | // InsertString and DeleteChars are the bottleneck though which all changes occur |
727 | PLATFORM_ASSERT(deleteLength > 0); |
728 | const char *data = nullptr; |
729 | if (!readOnly) { |
730 | if (collectingUndo) { |
731 | // Save into the undo/redo stack, but only the characters - not the formatting |
732 | // The gap would be moved to position anyway for the deletion so this doesn't cost extra |
733 | data = substance.RangePointer(position, deleteLength); |
734 | data = uh.AppendAction(ActionType::remove, position, data, deleteLength, startSequence); |
735 | } |
736 | |
737 | BasicDeleteChars(position, deleteLength); |
738 | } |
739 | return data; |
740 | } |
741 | |
742 | Sci::Position CellBuffer::Length() const noexcept { |
743 | return substance.Length(); |
744 | } |
745 | |
746 | void CellBuffer::Allocate(Sci::Position newSize) { |
747 | substance.ReAllocate(newSize); |
748 | if (hasStyles) { |
749 | style.ReAllocate(newSize); |
750 | } |
751 | } |
752 | |
753 | void CellBuffer::SetUTF8Substance(bool utf8Substance_) noexcept { |
754 | utf8Substance = utf8Substance_; |
755 | } |
756 | |
757 | void CellBuffer::SetLineEndTypes(LineEndType utf8LineEnds_) { |
758 | if (utf8LineEnds != utf8LineEnds_) { |
759 | const LineCharacterIndexType indexes = plv->LineCharacterIndex(); |
760 | utf8LineEnds = utf8LineEnds_; |
761 | ResetLineEnds(); |
762 | AllocateLineCharacterIndex(indexes); |
763 | } |
764 | } |
765 | |
766 | bool CellBuffer::ContainsLineEnd(const char *s, Sci::Position length) const noexcept { |
767 | unsigned char chBeforePrev = 0; |
768 | unsigned char chPrev = 0; |
769 | for (Sci::Position i = 0; i < length; i++) { |
770 | const unsigned char ch = s[i]; |
771 | if ((ch == '\r') || (ch == '\n')) { |
772 | return true; |
773 | } else if (utf8LineEnds == LineEndType::Unicode) { |
774 | if (UTF8IsMultibyteLineEnd(chBeforePrev, chPrev, ch)) { |
775 | return true; |
776 | } |
777 | } |
778 | chBeforePrev = chPrev; |
779 | chPrev = ch; |
780 | } |
781 | return false; |
782 | } |
783 | |
784 | void CellBuffer::SetPerLine(PerLine *pl) noexcept { |
785 | plv->SetPerLine(pl); |
786 | } |
787 | |
788 | LineCharacterIndexType CellBuffer::LineCharacterIndex() const noexcept { |
789 | return plv->LineCharacterIndex(); |
790 | } |
791 | |
792 | void CellBuffer::AllocateLineCharacterIndex(LineCharacterIndexType lineCharacterIndex) { |
793 | if (utf8Substance) { |
794 | if (plv->AllocateLineCharacterIndex(lineCharacterIndex, Lines())) { |
795 | // Changed so recalculate whole file |
796 | RecalculateIndexLineStarts(0, Lines() - 1); |
797 | } |
798 | } |
799 | } |
800 | |
801 | void CellBuffer::ReleaseLineCharacterIndex(LineCharacterIndexType lineCharacterIndex) { |
802 | plv->ReleaseLineCharacterIndex(lineCharacterIndex); |
803 | } |
804 | |
805 | Sci::Line CellBuffer::Lines() const noexcept { |
806 | return plv->Lines(); |
807 | } |
808 | |
809 | void CellBuffer::AllocateLines(Sci::Line lines) { |
810 | plv->AllocateLines(lines); |
811 | } |
812 | |
813 | Sci::Position CellBuffer::LineStart(Sci::Line line) const noexcept { |
814 | if (line < 0) |
815 | return 0; |
816 | else if (line >= Lines()) |
817 | return Length(); |
818 | else |
819 | return plv->LineStart(line); |
820 | } |
821 | |
822 | Sci::Line CellBuffer::LineFromPosition(Sci::Position pos) const noexcept { |
823 | return plv->LineFromPosition(pos); |
824 | } |
825 | |
826 | Sci::Position CellBuffer::IndexLineStart(Sci::Line line, LineCharacterIndexType lineCharacterIndex) const noexcept { |
827 | return plv->IndexLineStart(line, lineCharacterIndex); |
828 | } |
829 | |
830 | Sci::Line CellBuffer::LineFromPositionIndex(Sci::Position pos, LineCharacterIndexType lineCharacterIndex) const noexcept { |
831 | return plv->LineFromPositionIndex(pos, lineCharacterIndex); |
832 | } |
833 | |
834 | bool CellBuffer::IsReadOnly() const noexcept { |
835 | return readOnly; |
836 | } |
837 | |
838 | void CellBuffer::SetReadOnly(bool set) noexcept { |
839 | readOnly = set; |
840 | } |
841 | |
842 | bool CellBuffer::IsLarge() const noexcept { |
843 | return largeDocument; |
844 | } |
845 | |
846 | bool CellBuffer::HasStyles() const noexcept { |
847 | return hasStyles; |
848 | } |
849 | |
850 | void CellBuffer::SetSavePoint() { |
851 | uh.SetSavePoint(); |
852 | } |
853 | |
854 | bool CellBuffer::IsSavePoint() const noexcept { |
855 | return uh.IsSavePoint(); |
856 | } |
857 | |
858 | void CellBuffer::TentativeStart() { |
859 | uh.TentativeStart(); |
860 | } |
861 | |
862 | void CellBuffer::TentativeCommit() { |
863 | uh.TentativeCommit(); |
864 | } |
865 | |
866 | int CellBuffer::TentativeSteps() noexcept { |
867 | return uh.TentativeSteps(); |
868 | } |
869 | |
870 | bool CellBuffer::TentativeActive() const noexcept { |
871 | return uh.TentativeActive(); |
872 | } |
873 | |
874 | // Without undo |
875 | |
876 | void CellBuffer::InsertLine(Sci::Line line, Sci::Position position, bool lineStart) { |
877 | plv->InsertLine(line, position, lineStart); |
878 | } |
879 | |
880 | void CellBuffer::RemoveLine(Sci::Line line) { |
881 | plv->RemoveLine(line); |
882 | } |
883 | |
884 | bool CellBuffer::UTF8LineEndOverlaps(Sci::Position position) const noexcept { |
885 | const unsigned char bytes[] = { |
886 | static_cast<unsigned char>(substance.ValueAt(position-2)), |
887 | static_cast<unsigned char>(substance.ValueAt(position-1)), |
888 | static_cast<unsigned char>(substance.ValueAt(position)), |
889 | static_cast<unsigned char>(substance.ValueAt(position+1)), |
890 | }; |
891 | return UTF8IsSeparator(bytes) || UTF8IsSeparator(bytes+1) || UTF8IsNEL(bytes+1); |
892 | } |
893 | |
894 | bool CellBuffer::UTF8IsCharacterBoundary(Sci::Position position) const { |
895 | assert(position >= 0 && position <= Length()); |
896 | if (position > 0) { |
897 | std::string back; |
898 | for (int i = 0; i < UTF8MaxBytes; i++) { |
899 | const Sci::Position posBack = position - i; |
900 | if (posBack < 0) { |
901 | return false; |
902 | } |
903 | back.insert(0, 1, substance.ValueAt(posBack)); |
904 | if (!UTF8IsTrailByte(back.front())) { |
905 | if (i > 0) { |
906 | // Have reached a non-trail |
907 | const int cla = UTF8Classify(back); |
908 | if ((cla & UTF8MaskInvalid) || (cla != i)) { |
909 | return false; |
910 | } |
911 | } |
912 | break; |
913 | } |
914 | } |
915 | } |
916 | if (position < Length()) { |
917 | const unsigned char fore = substance.ValueAt(position); |
918 | if (UTF8IsTrailByte(fore)) { |
919 | return false; |
920 | } |
921 | } |
922 | return true; |
923 | } |
924 | |
925 | void CellBuffer::ResetLineEnds() { |
926 | // Reinitialize line data -- too much work to preserve |
927 | const Sci::Line lines = plv->Lines(); |
928 | plv->Init(); |
929 | plv->AllocateLines(lines); |
930 | |
931 | constexpr Sci::Position position = 0; |
932 | const Sci::Position length = Length(); |
933 | plv->InsertText(0, length); |
934 | Sci::Line lineInsert = 1; |
935 | constexpr bool atLineStart = true; |
936 | unsigned char chBeforePrev = 0; |
937 | unsigned char chPrev = 0; |
938 | for (Sci::Position i = 0; i < length; i++) { |
939 | const unsigned char ch = substance.ValueAt(position + i); |
940 | if (ch == '\r') { |
941 | InsertLine(lineInsert, (position + i) + 1, atLineStart); |
942 | lineInsert++; |
943 | } else if (ch == '\n') { |
944 | if (chPrev == '\r') { |
945 | // Patch up what was end of line |
946 | plv->SetLineStart(lineInsert - 1, (position + i) + 1); |
947 | } else { |
948 | InsertLine(lineInsert, (position + i) + 1, atLineStart); |
949 | lineInsert++; |
950 | } |
951 | } else if (utf8LineEnds == LineEndType::Unicode) { |
952 | if (UTF8IsMultibyteLineEnd(chBeforePrev, chPrev, ch)) { |
953 | InsertLine(lineInsert, (position + i) + 1, atLineStart); |
954 | lineInsert++; |
955 | } |
956 | } |
957 | chBeforePrev = chPrev; |
958 | chPrev = ch; |
959 | } |
960 | } |
961 | |
962 | namespace { |
963 | |
964 | CountWidths CountCharacterWidthsUTF8(std::string_view sv) noexcept { |
965 | CountWidths cw; |
966 | size_t remaining = sv.length(); |
967 | while (remaining > 0) { |
968 | const int utf8Status = UTF8Classify(sv); |
969 | const int lenChar = utf8Status & UTF8MaskWidth; |
970 | cw.CountChar(lenChar); |
971 | sv.remove_prefix(lenChar); |
972 | remaining -= lenChar; |
973 | } |
974 | return cw; |
975 | } |
976 | |
977 | } |
978 | |
979 | bool CellBuffer::MaintainingLineCharacterIndex() const noexcept { |
980 | return plv->LineCharacterIndex() != LineCharacterIndexType::None; |
981 | } |
982 | |
983 | void CellBuffer::RecalculateIndexLineStarts(Sci::Line lineFirst, Sci::Line lineLast) { |
984 | std::string text; |
985 | Sci::Position posLineEnd = LineStart(lineFirst); |
986 | for (Sci::Line line = lineFirst; line <= lineLast; line++) { |
987 | // Find line start and end, retrieve text of line, count characters and update line width |
988 | const Sci::Position posLineStart = posLineEnd; |
989 | posLineEnd = LineStart(line+1); |
990 | const Sci::Position width = posLineEnd - posLineStart; |
991 | text.resize(width); |
992 | GetCharRange(text.data(), posLineStart, width); |
993 | const CountWidths cw = CountCharacterWidthsUTF8(text); |
994 | plv->SetLineCharactersWidth(line, cw); |
995 | } |
996 | } |
997 | |
998 | void CellBuffer::BasicInsertString(Sci::Position position, const char *s, Sci::Position insertLength) { |
999 | if (insertLength == 0) |
1000 | return; |
1001 | PLATFORM_ASSERT(insertLength > 0); |
1002 | |
1003 | const unsigned char chAfter = substance.ValueAt(position); |
1004 | bool breakingUTF8LineEnd = false; |
1005 | if (utf8LineEnds == LineEndType::Unicode && UTF8IsTrailByte(chAfter)) { |
1006 | breakingUTF8LineEnd = UTF8LineEndOverlaps(position); |
1007 | } |
1008 | |
1009 | const Sci::Line linePosition = plv->LineFromPosition(position); |
1010 | Sci::Line lineInsert = linePosition + 1; |
1011 | |
1012 | // A simple insertion is one that inserts valid text on a single line at a character boundary |
1013 | bool simpleInsertion = false; |
1014 | |
1015 | const bool maintainingIndex = MaintainingLineCharacterIndex(); |
1016 | |
1017 | // Check for breaking apart a UTF-8 sequence and inserting invalid UTF-8 |
1018 | if (utf8Substance && maintainingIndex) { |
1019 | // Actually, don't need to check that whole insertion is valid just that there |
1020 | // are no potential fragments at ends. |
1021 | simpleInsertion = UTF8IsCharacterBoundary(position) && |
1022 | UTF8IsValid(std::string_view(s, insertLength)); |
1023 | } |
1024 | |
1025 | substance.InsertFromArray(position, s, 0, insertLength); |
1026 | if (hasStyles) { |
1027 | style.InsertValue(position, insertLength, 0); |
1028 | } |
1029 | |
1030 | const bool atLineStart = plv->LineStart(lineInsert-1) == position; |
1031 | // Point all the lines after the insertion point further along in the buffer |
1032 | plv->InsertText(lineInsert-1, insertLength); |
1033 | unsigned char chBeforePrev = substance.ValueAt(position - 2); |
1034 | unsigned char chPrev = substance.ValueAt(position - 1); |
1035 | if (chPrev == '\r' && chAfter == '\n') { |
1036 | // Splitting up a crlf pair at position |
1037 | InsertLine(lineInsert, position, false); |
1038 | lineInsert++; |
1039 | } |
1040 | if (breakingUTF8LineEnd) { |
1041 | RemoveLine(lineInsert); |
1042 | } |
1043 | |
1044 | constexpr size_t PositionBlockSize = 128; |
1045 | Sci::Position positions[PositionBlockSize]{}; |
1046 | size_t nPositions = 0; |
1047 | const Sci::Line lineStart = lineInsert; |
1048 | |
1049 | // s may not NULL-terminated, ensure *ptr == '\n' or *next == '\n' is valid. |
1050 | const char * const end = s + insertLength - 1; |
1051 | const char *ptr = s; |
1052 | unsigned char ch = 0; |
1053 | |
1054 | if (chPrev == '\r' && *ptr == '\n') { |
1055 | ++ptr; |
1056 | // Patch up what was end of line |
1057 | plv->SetLineStart(lineInsert - 1, (position + ptr - s)); |
1058 | simpleInsertion = false; |
1059 | } |
1060 | |
1061 | if (ptr < end) { |
1062 | uint8_t eolTable[256]{}; |
1063 | eolTable[static_cast<uint8_t>('\n')] = 1; |
1064 | eolTable[static_cast<uint8_t>('\r')] = 2; |
1065 | if (utf8LineEnds == LineEndType::Unicode) { |
1066 | // see UniConversion.h for LS, PS and NEL |
1067 | eolTable[0x85] = 4; |
1068 | eolTable[0xa8] = 3; |
1069 | eolTable[0xa9] = 3; |
1070 | } |
1071 | |
1072 | do { |
1073 | // skip to line end |
1074 | ch = *ptr++; |
1075 | uint8_t type; |
1076 | while ((type = eolTable[ch]) == 0 && ptr < end) { |
1077 | chBeforePrev = chPrev; |
1078 | chPrev = ch; |
1079 | ch = *ptr++; |
1080 | } |
1081 | switch (type) { |
1082 | case 2: // '\r' |
1083 | if (*ptr == '\n') { |
1084 | ++ptr; |
1085 | } |
1086 | [[fallthrough]]; |
1087 | case 1: // '\n' |
1088 | positions[nPositions++] = position + ptr - s; |
1089 | if (nPositions == PositionBlockSize) { |
1090 | plv->InsertLines(lineInsert, positions, nPositions, atLineStart); |
1091 | lineInsert += nPositions; |
1092 | nPositions = 0; |
1093 | } |
1094 | break; |
1095 | case 3: |
1096 | case 4: |
1097 | // LS, PS and NEL |
1098 | if ((type == 3 && chPrev == 0x80 && chBeforePrev == 0xe2) || (type == 4 && chPrev == 0xc2)) { |
1099 | positions[nPositions++] = position + ptr - s; |
1100 | if (nPositions == PositionBlockSize) { |
1101 | plv->InsertLines(lineInsert, positions, nPositions, atLineStart); |
1102 | lineInsert += nPositions; |
1103 | nPositions = 0; |
1104 | } |
1105 | } |
1106 | break; |
1107 | } |
1108 | |
1109 | chBeforePrev = chPrev; |
1110 | chPrev = ch; |
1111 | } while (ptr < end); |
1112 | } |
1113 | |
1114 | if (nPositions != 0) { |
1115 | plv->InsertLines(lineInsert, positions, nPositions, atLineStart); |
1116 | lineInsert += nPositions; |
1117 | } |
1118 | |
1119 | ch = *end; |
1120 | if (ptr == end) { |
1121 | ++ptr; |
1122 | if (ch == '\r' || ch == '\n') { |
1123 | InsertLine(lineInsert, (position + ptr - s), atLineStart); |
1124 | lineInsert++; |
1125 | } else if (utf8LineEnds == LineEndType::Unicode && !UTF8IsAscii(ch)) { |
1126 | if (UTF8IsMultibyteLineEnd(chBeforePrev, chPrev, ch)) { |
1127 | InsertLine(lineInsert, (position + ptr - s), atLineStart); |
1128 | lineInsert++; |
1129 | } |
1130 | } |
1131 | } |
1132 | |
1133 | // Joining two lines where last insertion is cr and following substance starts with lf |
1134 | if (chAfter == '\n') { |
1135 | if (ch == '\r') { |
1136 | // End of line already in buffer so drop the newly created one |
1137 | RemoveLine(lineInsert - 1); |
1138 | simpleInsertion = false; |
1139 | } |
1140 | } else if (utf8LineEnds == LineEndType::Unicode && !UTF8IsAscii(chAfter)) { |
1141 | chBeforePrev = chPrev; |
1142 | chPrev = ch; |
1143 | // May have end of UTF-8 line end in buffer and start in insertion |
1144 | for (int j = 0; j < UTF8SeparatorLength-1; j++) { |
1145 | const unsigned char chAt = substance.ValueAt(position + insertLength + j); |
1146 | const unsigned char back3[3] = {chBeforePrev, chPrev, chAt}; |
1147 | if (UTF8IsSeparator(back3)) { |
1148 | InsertLine(lineInsert, (position + insertLength + j) + 1, atLineStart); |
1149 | lineInsert++; |
1150 | } |
1151 | if ((j == 0) && UTF8IsNEL(back3+1)) { |
1152 | InsertLine(lineInsert, (position + insertLength + j) + 1, atLineStart); |
1153 | lineInsert++; |
1154 | } |
1155 | chBeforePrev = chPrev; |
1156 | chPrev = chAt; |
1157 | } |
1158 | } |
1159 | if (maintainingIndex) { |
1160 | if (simpleInsertion && (lineInsert == lineStart)) { |
1161 | const CountWidths cw = CountCharacterWidthsUTF8(std::string_view(s, insertLength)); |
1162 | plv->InsertCharacters(linePosition, cw); |
1163 | } else { |
1164 | RecalculateIndexLineStarts(linePosition, lineInsert - 1); |
1165 | } |
1166 | } |
1167 | } |
1168 | |
1169 | void CellBuffer::BasicDeleteChars(Sci::Position position, Sci::Position deleteLength) { |
1170 | if (deleteLength == 0) |
1171 | return; |
1172 | |
1173 | Sci::Line lineRecalculateStart = Sci::invalidPosition; |
1174 | |
1175 | if ((position == 0) && (deleteLength == substance.Length())) { |
1176 | // If whole buffer is being deleted, faster to reinitialise lines data |
1177 | // than to delete each line. |
1178 | plv->Init(); |
1179 | } else { |
1180 | // Have to fix up line positions before doing deletion as looking at text in buffer |
1181 | // to work out which lines have been removed |
1182 | |
1183 | const Sci::Line linePosition = plv->LineFromPosition(position); |
1184 | Sci::Line lineRemove = linePosition + 1; |
1185 | |
1186 | plv->InsertText(lineRemove-1, - (deleteLength)); |
1187 | const unsigned char chPrev = substance.ValueAt(position - 1); |
1188 | const unsigned char chBefore = chPrev; |
1189 | unsigned char chNext = substance.ValueAt(position); |
1190 | |
1191 | // Check for breaking apart a UTF-8 sequence |
1192 | // Needs further checks that text is UTF-8 or that some other break apart is occurring |
1193 | if (utf8Substance && MaintainingLineCharacterIndex()) { |
1194 | const Sci::Position posEnd = position + deleteLength; |
1195 | const Sci::Line lineEndRemove = plv->LineFromPosition(posEnd); |
1196 | const bool simpleDeletion = |
1197 | (linePosition == lineEndRemove) && |
1198 | UTF8IsCharacterBoundary(position) && UTF8IsCharacterBoundary(posEnd); |
1199 | if (simpleDeletion) { |
1200 | std::string text(deleteLength, '\0'); |
1201 | GetCharRange(text.data(), position, deleteLength); |
1202 | if (UTF8IsValid(text)) { |
1203 | // Everything is good |
1204 | const CountWidths cw = CountCharacterWidthsUTF8(text); |
1205 | plv->InsertCharacters(linePosition, -cw); |
1206 | } else { |
1207 | lineRecalculateStart = linePosition; |
1208 | } |
1209 | } else { |
1210 | lineRecalculateStart = linePosition; |
1211 | } |
1212 | } |
1213 | |
1214 | bool ignoreNL = false; |
1215 | if (chPrev == '\r' && chNext == '\n') { |
1216 | // Move back one |
1217 | plv->SetLineStart(lineRemove, position); |
1218 | lineRemove++; |
1219 | ignoreNL = true; // First \n is not real deletion |
1220 | } |
1221 | if (utf8LineEnds == LineEndType::Unicode && UTF8IsTrailByte(chNext)) { |
1222 | if (UTF8LineEndOverlaps(position)) { |
1223 | RemoveLine(lineRemove); |
1224 | } |
1225 | } |
1226 | |
1227 | unsigned char ch = chNext; |
1228 | for (Sci::Position i = 0; i < deleteLength; i++) { |
1229 | chNext = substance.ValueAt(position + i + 1); |
1230 | if (ch == '\r') { |
1231 | if (chNext != '\n') { |
1232 | RemoveLine(lineRemove); |
1233 | } |
1234 | } else if (ch == '\n') { |
1235 | if (ignoreNL) { |
1236 | ignoreNL = false; // Further \n are real deletions |
1237 | } else { |
1238 | RemoveLine(lineRemove); |
1239 | } |
1240 | } else if (utf8LineEnds == LineEndType::Unicode) { |
1241 | if (!UTF8IsAscii(ch)) { |
1242 | const unsigned char next3[3] = {ch, chNext, |
1243 | static_cast<unsigned char>(substance.ValueAt(position + i + 2))}; |
1244 | if (UTF8IsSeparator(next3) || UTF8IsNEL(next3)) { |
1245 | RemoveLine(lineRemove); |
1246 | } |
1247 | } |
1248 | } |
1249 | |
1250 | ch = chNext; |
1251 | } |
1252 | // May have to fix up end if last deletion causes cr to be next to lf |
1253 | // or removes one of a crlf pair |
1254 | const char chAfter = substance.ValueAt(position + deleteLength); |
1255 | if (chBefore == '\r' && chAfter == '\n') { |
1256 | // Using lineRemove-1 as cr ended line before start of deletion |
1257 | RemoveLine(lineRemove - 1); |
1258 | plv->SetLineStart(lineRemove - 1, position + 1); |
1259 | } |
1260 | } |
1261 | substance.DeleteRange(position, deleteLength); |
1262 | if (lineRecalculateStart >= 0) { |
1263 | RecalculateIndexLineStarts(lineRecalculateStart, lineRecalculateStart); |
1264 | } |
1265 | if (hasStyles) { |
1266 | style.DeleteRange(position, deleteLength); |
1267 | } |
1268 | } |
1269 | |
1270 | bool CellBuffer::SetUndoCollection(bool collectUndo) { |
1271 | collectingUndo = collectUndo; |
1272 | uh.DropUndoSequence(); |
1273 | return collectingUndo; |
1274 | } |
1275 | |
1276 | bool CellBuffer::IsCollectingUndo() const noexcept { |
1277 | return collectingUndo; |
1278 | } |
1279 | |
1280 | void CellBuffer::BeginUndoAction() { |
1281 | uh.BeginUndoAction(); |
1282 | } |
1283 | |
1284 | void CellBuffer::EndUndoAction() { |
1285 | uh.EndUndoAction(); |
1286 | } |
1287 | |
1288 | void CellBuffer::AddUndoAction(Sci::Position token, bool mayCoalesce) { |
1289 | bool startSequence = false; |
1290 | uh.AppendAction(ActionType::container, token, nullptr, 0, startSequence, mayCoalesce); |
1291 | } |
1292 | |
1293 | void CellBuffer::DeleteUndoHistory() { |
1294 | uh.DeleteUndoHistory(); |
1295 | } |
1296 | |
1297 | bool CellBuffer::CanUndo() const noexcept { |
1298 | return uh.CanUndo(); |
1299 | } |
1300 | |
1301 | int CellBuffer::StartUndo() { |
1302 | return uh.StartUndo(); |
1303 | } |
1304 | |
1305 | const Action &CellBuffer::GetUndoStep() const { |
1306 | return uh.GetUndoStep(); |
1307 | } |
1308 | |
1309 | void CellBuffer::PerformUndoStep() { |
1310 | const Action &actionStep = uh.GetUndoStep(); |
1311 | if (actionStep.at == ActionType::insert) { |
1312 | if (substance.Length() < actionStep.lenData) { |
1313 | throw std::runtime_error( |
1314 | "CellBuffer::PerformUndoStep: deletion must be less than document length." ); |
1315 | } |
1316 | BasicDeleteChars(actionStep.position, actionStep.lenData); |
1317 | } else if (actionStep.at == ActionType::remove) { |
1318 | BasicInsertString(actionStep.position, actionStep.data.get(), actionStep.lenData); |
1319 | } |
1320 | uh.CompletedUndoStep(); |
1321 | } |
1322 | |
1323 | bool CellBuffer::CanRedo() const noexcept { |
1324 | return uh.CanRedo(); |
1325 | } |
1326 | |
1327 | int CellBuffer::StartRedo() { |
1328 | return uh.StartRedo(); |
1329 | } |
1330 | |
1331 | const Action &CellBuffer::GetRedoStep() const { |
1332 | return uh.GetRedoStep(); |
1333 | } |
1334 | |
1335 | void CellBuffer::PerformRedoStep() { |
1336 | const Action &actionStep = uh.GetRedoStep(); |
1337 | if (actionStep.at == ActionType::insert) { |
1338 | BasicInsertString(actionStep.position, actionStep.data.get(), actionStep.lenData); |
1339 | } else if (actionStep.at == ActionType::remove) { |
1340 | BasicDeleteChars(actionStep.position, actionStep.lenData); |
1341 | } |
1342 | uh.CompletedRedoStep(); |
1343 | } |
1344 | |
1345 | |