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 <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