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