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 44 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> 93 ContractionState<LINE>::ContractionState() noexcept : linesInDocument(1) { 94 } 95 96 template <typename LINE> 97 ContractionState<LINE>::~ContractionState() { 98 Clear(); 99 } 100 101 template <typename LINE> 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> 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> 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> 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> 161 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> 170 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> 179 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> 190 Sci::Line ContractionState<LINE>::DisplayLastFromDoc(Sci::Line lineDoc) const noexcept { 191 return DisplayFromDoc(lineDoc) + GetHeight(lineDoc) - 1; 192 } 193 194 template <typename LINE> 195 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> 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> 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> 236 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> 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> 273 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> 282 const char *ContractionState<LINE>::GetFoldDisplayText(Sci::Line lineDoc) const noexcept { 283 Check(); 284 return foldDisplayTexts->ValueAt(lineDoc).get(); 285 } 286 287 template <typename LINE> 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> 303 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> 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> 330 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> 348 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> 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> 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> 390 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 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