1// Scintilla source code edit control
2/** @file ContractionState.cxx
3 ** Manages visibility of lines for folding and wrapping.
4 **/
5// Copyright 1998-2007 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 <cassert>
10#include <cstring>
11
12#include <stdexcept>
13#include <string_view>
14#include <vector>
15#include <optional>
16#include <algorithm>
17#include <memory>
18
19#include "Debugging.h"
20
21#include "Position.h"
22#include "UniqueString.h"
23#include "SplitVector.h"
24#include "Partitioning.h"
25#include "RunStyles.h"
26#include "SparseVector.h"
27#include "ContractionState.h"
28
29using namespace Scintilla::Internal;
30
31namespace {
32
33template <typename LINE>
34class ContractionState final : public IContractionState {
35 // These contain 1 element for every document line.
36 std::unique_ptr<RunStyles<LINE, char>> visible;
37 std::unique_ptr<RunStyles<LINE, char>> expanded;
38 std::unique_ptr<RunStyles<LINE, int>> heights;
39 std::unique_ptr<SparseVector<UniqueString>> foldDisplayTexts;
40 std::unique_ptr<Partitioning<LINE>> displayLines;
41 LINE linesInDocument;
42
43 void EnsureData();
44
45 bool OneToOne() const noexcept {
46 // True when each document line is exactly one display line so need for
47 // complex data structures.
48 return visible == nullptr;
49 }
50
51 void InsertLine(Sci::Line lineDoc);
52 void DeleteLine(Sci::Line lineDoc);
53
54public:
55 ContractionState() noexcept;
56 // Deleted so ContractionState objects can not be copied.
57 ContractionState(const ContractionState &) = delete;
58 void operator=(const ContractionState &) = delete;
59 ContractionState(ContractionState &&) = delete;
60 void operator=(ContractionState &&) = delete;
61 ~ContractionState() override;
62
63 void Clear() noexcept override;
64
65 Sci::Line LinesInDoc() const noexcept override;
66 Sci::Line LinesDisplayed() const noexcept override;
67 Sci::Line DisplayFromDoc(Sci::Line lineDoc) const noexcept override;
68 Sci::Line DisplayLastFromDoc(Sci::Line lineDoc) const noexcept override;
69 Sci::Line DocFromDisplay(Sci::Line lineDisplay) const noexcept override;
70
71 void InsertLines(Sci::Line lineDoc, Sci::Line lineCount) override;
72 void DeleteLines(Sci::Line lineDoc, Sci::Line lineCount) override;
73
74 bool GetVisible(Sci::Line lineDoc) const noexcept override;
75 bool SetVisible(Sci::Line lineDocStart, Sci::Line lineDocEnd, bool isVisible) override;
76 bool HiddenLines() const noexcept override;
77
78 const char *GetFoldDisplayText(Sci::Line lineDoc) const noexcept override;
79 bool SetFoldDisplayText(Sci::Line lineDoc, const char *text) override;
80
81 bool GetExpanded(Sci::Line lineDoc) const noexcept override;
82 bool SetExpanded(Sci::Line lineDoc, bool isExpanded) override;
83 Sci::Line ContractedNext(Sci::Line lineDocStart) const noexcept override;
84
85 int GetHeight(Sci::Line lineDoc) const noexcept override;
86 bool SetHeight(Sci::Line lineDoc, int height) override;
87
88 void ShowAll() noexcept override;
89
90 void Check() const noexcept;
91};
92
93template <typename LINE>
94ContractionState<LINE>::ContractionState() noexcept : linesInDocument(1) {
95}
96
97template <typename LINE>
98ContractionState<LINE>::~ContractionState() = default;
99
100template <typename LINE>
101void ContractionState<LINE>::EnsureData() {
102 if (OneToOne()) {
103 visible = std::make_unique<RunStyles<LINE, char>>();
104 expanded = std::make_unique<RunStyles<LINE, char>>();
105 heights = std::make_unique<RunStyles<LINE, int>>();
106 foldDisplayTexts = std::make_unique<SparseVector<UniqueString>>();
107 displayLines = std::make_unique<Partitioning<LINE>>(4);
108 InsertLines(0, linesInDocument);
109 }
110}
111
112template <typename LINE>
113void ContractionState<LINE>::InsertLine(Sci::Line lineDoc) {
114 if (OneToOne()) {
115 linesInDocument++;
116 } else {
117 const LINE lineDocCast = static_cast<LINE>(lineDoc);
118 visible->InsertSpace(lineDocCast, 1);
119 visible->SetValueAt(lineDocCast, 1);
120 expanded->InsertSpace(lineDocCast, 1);
121 expanded->SetValueAt(lineDocCast, 1);
122 heights->InsertSpace(lineDocCast, 1);
123 heights->SetValueAt(lineDocCast, 1);
124 foldDisplayTexts->InsertSpace(lineDocCast, 1);
125 foldDisplayTexts->SetValueAt(lineDocCast, nullptr);
126 const Sci::Line lineDisplay = DisplayFromDoc(lineDoc);
127 displayLines->InsertPartition(lineDocCast, static_cast<LINE>(lineDisplay));
128 displayLines->InsertText(lineDocCast, 1);
129 }
130}
131
132template <typename LINE>
133void ContractionState<LINE>::DeleteLine(Sci::Line lineDoc) {
134 if (OneToOne()) {
135 linesInDocument--;
136 } else {
137 const LINE lineDocCast = static_cast<LINE>(lineDoc);
138 if (GetVisible(lineDoc)) {
139 displayLines->InsertText(lineDocCast, -heights->ValueAt(lineDocCast));
140 }
141 displayLines->RemovePartition(lineDocCast);
142 visible->DeleteRange(lineDocCast, 1);
143 expanded->DeleteRange(lineDocCast, 1);
144 heights->DeleteRange(lineDocCast, 1);
145 foldDisplayTexts->DeletePosition(lineDocCast);
146 }
147}
148
149template <typename LINE>
150void ContractionState<LINE>::Clear() noexcept {
151 visible.reset();
152 expanded.reset();
153 heights.reset();
154 foldDisplayTexts.reset();
155 displayLines.reset();
156 linesInDocument = 1;
157}
158
159template <typename LINE>
160Sci::Line ContractionState<LINE>::LinesInDoc() const noexcept {
161 if (OneToOne()) {
162 return linesInDocument;
163 } else {
164 return displayLines->Partitions() - 1;
165 }
166}
167
168template <typename LINE>
169Sci::Line ContractionState<LINE>::LinesDisplayed() const noexcept {
170 if (OneToOne()) {
171 return linesInDocument;
172 } else {
173 return displayLines->PositionFromPartition(static_cast<LINE>(LinesInDoc()));
174 }
175}
176
177template <typename LINE>
178Sci::Line ContractionState<LINE>::DisplayFromDoc(Sci::Line lineDoc) const noexcept {
179 if (OneToOne()) {
180 return (lineDoc <= linesInDocument) ? lineDoc : linesInDocument;
181 } else {
182 if (lineDoc > displayLines->Partitions())
183 lineDoc = displayLines->Partitions();
184 return displayLines->PositionFromPartition(static_cast<LINE>(lineDoc));
185 }
186}
187
188template <typename LINE>
189Sci::Line ContractionState<LINE>::DisplayLastFromDoc(Sci::Line lineDoc) const noexcept {
190 return DisplayFromDoc(lineDoc) + GetHeight(lineDoc) - 1;
191}
192
193template <typename LINE>
194Sci::Line ContractionState<LINE>::DocFromDisplay(Sci::Line lineDisplay) const noexcept {
195 if (OneToOne()) {
196 return lineDisplay;
197 } else {
198 if (lineDisplay < 0) {
199 return 0;
200 }
201 if (lineDisplay > LinesDisplayed()) {
202 return displayLines->PartitionFromPosition(static_cast<LINE>(LinesDisplayed()));
203 }
204 const Sci::Line lineDoc = displayLines->PartitionFromPosition(static_cast<LINE>(lineDisplay));
205 PLATFORM_ASSERT(GetVisible(lineDoc));
206 return lineDoc;
207 }
208}
209
210template <typename LINE>
211void ContractionState<LINE>::InsertLines(Sci::Line lineDoc, Sci::Line lineCount) {
212 if (OneToOne()) {
213 linesInDocument += static_cast<LINE>(lineCount);
214 } else {
215 for (Sci::Line l = 0; l < lineCount; l++) {
216 InsertLine(lineDoc + l);
217 }
218 }
219 Check();
220}
221
222template <typename LINE>
223void ContractionState<LINE>::DeleteLines(Sci::Line lineDoc, Sci::Line lineCount) {
224 if (OneToOne()) {
225 linesInDocument -= static_cast<LINE>(lineCount);
226 } else {
227 for (Sci::Line l = 0; l < lineCount; l++) {
228 DeleteLine(lineDoc);
229 }
230 }
231 Check();
232}
233
234template <typename LINE>
235bool ContractionState<LINE>::GetVisible(Sci::Line lineDoc) const noexcept {
236 if (OneToOne()) {
237 return true;
238 } else {
239 if (lineDoc >= visible->Length())
240 return true;
241 return visible->ValueAt(static_cast<LINE>(lineDoc)) == 1;
242 }
243}
244
245template <typename LINE>
246bool ContractionState<LINE>::SetVisible(Sci::Line lineDocStart, Sci::Line lineDocEnd, bool isVisible) {
247 if (OneToOne() && isVisible) {
248 return false;
249 } else {
250 EnsureData();
251 Sci::Line delta = 0;
252 Check();
253 if ((lineDocStart <= lineDocEnd) && (lineDocStart >= 0) && (lineDocEnd < LinesInDoc())) {
254 for (Sci::Line line = lineDocStart; line <= lineDocEnd; line++) {
255 if (GetVisible(line) != isVisible) {
256 const int heightLine = heights->ValueAt(static_cast<LINE>(line));
257 const int difference = isVisible ? heightLine : -heightLine;
258 visible->SetValueAt(static_cast<LINE>(line), isVisible ? 1 : 0);
259 displayLines->InsertText(static_cast<LINE>(line), difference);
260 delta += difference;
261 }
262 }
263 } else {
264 return false;
265 }
266 Check();
267 return delta != 0;
268 }
269}
270
271template <typename LINE>
272bool ContractionState<LINE>::HiddenLines() const noexcept {
273 if (OneToOne()) {
274 return false;
275 } else {
276 return !visible->AllSameAs(1);
277 }
278}
279
280template <typename LINE>
281const char *ContractionState<LINE>::GetFoldDisplayText(Sci::Line lineDoc) const noexcept {
282 Check();
283 return foldDisplayTexts->ValueAt(lineDoc).get();
284}
285
286template <typename LINE>
287bool ContractionState<LINE>::SetFoldDisplayText(Sci::Line lineDoc, const char *text) {
288 EnsureData();
289 const char *foldText = foldDisplayTexts->ValueAt(lineDoc).get();
290 if (!foldText || !text || 0 != strcmp(text, foldText)) {
291 UniqueString uns = IsNullOrEmpty(text) ? UniqueString() : UniqueStringCopy(text);
292 foldDisplayTexts->SetValueAt(lineDoc, std::move(uns));
293 Check();
294 return true;
295 } else {
296 Check();
297 return false;
298 }
299}
300
301template <typename LINE>
302bool ContractionState<LINE>::GetExpanded(Sci::Line lineDoc) const noexcept {
303 if (OneToOne()) {
304 return true;
305 } else {
306 Check();
307 return expanded->ValueAt(static_cast<LINE>(lineDoc)) == 1;
308 }
309}
310
311template <typename LINE>
312bool ContractionState<LINE>::SetExpanded(Sci::Line lineDoc, bool isExpanded) {
313 if (OneToOne() && isExpanded) {
314 return false;
315 } else {
316 EnsureData();
317 if (isExpanded != (expanded->ValueAt(static_cast<LINE>(lineDoc)) == 1)) {
318 expanded->SetValueAt(static_cast<LINE>(lineDoc), isExpanded ? 1 : 0);
319 Check();
320 return true;
321 } else {
322 Check();
323 return false;
324 }
325 }
326}
327
328template <typename LINE>
329Sci::Line ContractionState<LINE>::ContractedNext(Sci::Line lineDocStart) const noexcept {
330 if (OneToOne()) {
331 return -1;
332 } else {
333 Check();
334 if (!expanded->ValueAt(static_cast<LINE>(lineDocStart))) {
335 return lineDocStart;
336 } else {
337 const Sci::Line lineDocNextChange = expanded->EndRun(static_cast<LINE>(lineDocStart));
338 if (lineDocNextChange < LinesInDoc())
339 return lineDocNextChange;
340 else
341 return -1;
342 }
343 }
344}
345
346template <typename LINE>
347int ContractionState<LINE>::GetHeight(Sci::Line lineDoc) const noexcept {
348 if (OneToOne()) {
349 return 1;
350 } else {
351 return heights->ValueAt(static_cast<LINE>(lineDoc));
352 }
353}
354
355// Set the number of display lines needed for this line.
356// Return true if this is a change.
357template <typename LINE>
358bool ContractionState<LINE>::SetHeight(Sci::Line lineDoc, int height) {
359 if (OneToOne() && (height == 1)) {
360 return false;
361 } else if (lineDoc < LinesInDoc()) {
362 EnsureData();
363 if (GetHeight(lineDoc) != height) {
364 if (GetVisible(lineDoc)) {
365 displayLines->InsertText(static_cast<LINE>(lineDoc), height - GetHeight(lineDoc));
366 }
367 heights->SetValueAt(static_cast<LINE>(lineDoc), height);
368 Check();
369 return true;
370 } else {
371 Check();
372 return false;
373 }
374 } else {
375 return false;
376 }
377}
378
379template <typename LINE>
380void ContractionState<LINE>::ShowAll() noexcept {
381 const LINE lines = static_cast<LINE>(LinesInDoc());
382 Clear();
383 linesInDocument = lines;
384}
385
386// Debugging checks
387
388template <typename LINE>
389void ContractionState<LINE>::Check() const noexcept {
390#ifdef CHECK_CORRECTNESS
391 for (Sci::Line vline = 0; vline < LinesDisplayed(); vline++) {
392 const Sci::Line lineDoc = DocFromDisplay(vline);
393 PLATFORM_ASSERT(GetVisible(lineDoc));
394 }
395 for (Sci::Line lineDoc = 0; lineDoc < LinesInDoc(); lineDoc++) {
396 const Sci::Line displayThis = DisplayFromDoc(lineDoc);
397 const Sci::Line displayNext = DisplayFromDoc(lineDoc + 1);
398 const Sci::Line height = displayNext - displayThis;
399 PLATFORM_ASSERT(height >= 0);
400 if (GetVisible(lineDoc)) {
401 PLATFORM_ASSERT(GetHeight(lineDoc) == height);
402 } else {
403 PLATFORM_ASSERT(0 == height);
404 }
405 }
406#endif
407}
408
409}
410
411namespace Scintilla::Internal {
412
413std::unique_ptr<IContractionState> ContractionStateCreate(bool largeDocument) {
414 if (largeDocument)
415 return std::make_unique<ContractionState<Sci::Line>>();
416 else
417 return std::make_unique<ContractionState<int>>();
418}
419
420}
421