Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
TextStore.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Frameworks_Led_TextStore_h_
5#define _Stroika_Frameworks_Led_TextStore_h_ 1
6
7#include "Stroika/Frameworks/StroikaPreComp.h"
8
9#include <algorithm>
10#include <cstddef>
11#include <list>
12#include <vector>
13
14#include "Stroika/Foundation/Containers/Common.h"
17
18/*
19@MODULE: TextStore
20@DESCRIPTION:
21 <p>This module introduces one of Led's key abstractions - the @'TextStore'. A @'TextStore' - as the name suggests -
22 is where Led keeps track of text. It is abstract enuf to keep track in many different representations of the text.</p>
23 <p>A @'TextStore' also keeps track of @'Marker's associated with the text.</p>
24 <h4>Important Design Notes:</h4>
25 <ul>
26 <li> Note that Led uses @'Led_tChar's as the meaning for the indexes. This means for
27 wide character sets it means characters, and for mbyte or single character set
28 builds it means bytes. In either case you can use CharacterToTCharIndex/
29 TCharToCharacterIndex () to get whichever you want.
30 </li>
31 </ul>
32 */
33
34#include "Stroika/Frameworks/Led/Marker.h"
35#include "Stroika/Frameworks/Led/Support.h"
36#include "Stroika/Frameworks/Led/TextBreaks.h"
37
38namespace Stroika::Frameworks::Led {
39
40/*
41 @CONFIGVAR: qStroika_Frameworks_Led_UseWin32CompareStringCallForCaseInsensitiveSearch
42 @DESCRIPTION: <p>Produces better internationalized results - but - of course - is Win32 specific - and a bit slower.
43 Based on SPR#0864</p>
44 */
45#ifndef qStroika_Frameworks_Led_UseWin32CompareStringCallForCaseInsensitiveSearch
46#define qStroika_Frameworks_Led_UseWin32CompareStringCallForCaseInsensitiveSearch qStroika_Platform_Windows
47#endif
48
49 /*
50 @CLASS: TextStore
51 @BASES: @'MarkerOwner'
52 @DESCRIPTION: <p>An abstraction of something which contains text, and special objects
53 called markers, which can be used to represent embedded data, or
54 hypertext links, or font style runs. Markers have a left and right edge
55 (named by indexes into the text buffer), and conceptually stick to the
56 text they wrap as the text is modified (edited). </p>
57
58 <p>There are two concrete TextStore implementations you can use (with Led 3.0).
59 <ul style="margin-left: +6em">
60 <li>@'ChunkedArrayTextStore'</li>
61 <li>@'SimpleTextStore'</li>
62 </ul>
63 </p>
64
65 <p>Future versions of Led may include TextStore implementations which:
66 <ul style="margin-left: +6em">
67 <li>directly encode the markers into the stream of text</li><br>
68 Something along these lines is a common editor represention for styled text.
69 But in my case, Markers are arbitrary objects, stored by pointer.
70 So it's less clear how to accomplish that inline storage technique here.
71
72 <li>Store text on the disk, and then read in only the little bits it needed.</li><br>
73 This would be a sensible TextStore to use when implementing a 'programming'
74 style text editor, where you might reasonably want to edit many megabyte files,
75 and not really need to store many - if any - markers. By accessing the file
76 data on a demand-basis (memory mapped file?)- you could save a significant amount
77 of startup time, and time overall if not the entire file is viewed.
78
79 <li>Store text compactly, the way Stroika's String does (1, 2 or 4 bytes per character,
80 chosen per chunk), behind a Character-based API.</li><br>
81 See https://github.com/SophistSolutions/Stroika/issues/1191 (low priority).
82 </ul>
83 */
84 class TextStore : public virtual MarkerOwner {
85 protected:
86 TextStore ();
87
88 public:
89 virtual ~TextStore ();
90
91 /*
92 @METHOD: TextStore::ConstructNewTextStore
93 @DESCRIPTION: <p>Constructs a new instance of an object of THIS type (whatever dynamic type the object has). The
94 newly constructed object bears no (other) assosiation with the original (THIS) object. This function
95 is pure-virtual, and must be overriden in all subclasses.</p>
96 */
97 public:
98 virtual TextStore* ConstructNewTextStore () const = 0;
99
100 /*
101 * Register a markerOwner here, and it will be notified of text changes before
102 * all markers, and AFTER all markers. Never register more than once, and
103 * always delete iff registered.
104 */
105 public:
106 virtual void AddMarkerOwner (MarkerOwner* owner);
107 virtual void RemoveMarkerOwner (MarkerOwner* owner);
108 nonvirtual const vector<MarkerOwner*>& GetMarkerOwners () const noexcept;
109
110 private:
111 vector<MarkerOwner*> fMarkerOwners{};
112
113 // Retrieve the text
114 public:
115 /*
116 @METHOD: TextStore::GetLength
117 @DESCRIPTION: <p>Returns the number of @'Led_tChar's in this @'TextStore'.</p>
118 */
119 virtual size_t GetLength () const noexcept = 0;
120 /*
121 @METHOD: TextStore::CopyOut
122 @DESCRIPTION: <p>CopyOut does NOT null terminate. It is an error to call with 'count' causing
123 access past end of TextStore buffer.</p>
124 <p>Note that it IS NOT an error to call CopyOut for multibyte characters and split them.
125 This is one of the few API routines where that is so.</p>
126 */
127 virtual void CopyOut (size_t from, size_t count, Led_tChar* buffer) const noexcept = 0;
128
129 // utilities to simplify refering to beginning/end of buffer.
130 public:
131 static size_t GetStart ();
132 nonvirtual size_t GetEnd () const;
133
134 // Type of indexing conversion
135 //
136 // In Led - by default - we index in terms of Led_tChars. These are typically chars(bytes)
137 // but for wide-charactersets such as UNICODE, then can be larger. It is possibly useful to
138 // sometimes index in-terms of CHARACTERS instead of BYTES - even when using a multibyte
139 // character set. These routines allow you to always freely go back and forth between
140 // CHARACTER INDEXES and Led_tChar indexes (used throughout Led).
141 //
142 // NB: For MBYTE character sets - these CAN BE QUITE SLOW!
143 //
144 public:
145 nonvirtual size_t CharacterToTCharIndex (size_t i);
146 nonvirtual size_t TCharToCharacterIndex (size_t i);
147
148 // Update the text
149 public:
150 nonvirtual void Replace (size_t from, size_t to, const Led_tChar* withWhat, size_t withWhatCount);
151
152 /*
153 @METHOD: TextStore::ReplaceWithoutUpdate
154 @DESCRIPTION: <p>Similar to @'TextStore::Replace' except that this routine doesn't notify markers and markerowners about
155 the update. This should rarely be called directly - except in conjunction with @'TextStore::SimpleUpdater'</p>
156 <p>This method is pure-virtual, and implemented by a concrete subsclass. This method is new to Led 3.1</p>
157 */
158 virtual void ReplaceWithoutUpdate (size_t from, size_t to, const Led_tChar* withWhat, size_t withWhatCount) = 0;
159
160 /*
161 * Access the markers.
162 */
163 public:
164 /*
165 @METHOD: TextStore::AddMarker
166 @DESCRIPTION: <p>Add the given marker to this TextStore, starting at position lhs, and give the marker
167 length 'length'. Record that its owner is 'owner'.</p>
168 <p>NB: the @'MarkerOwner' - must be a valid @'MarkerOwner'. Either this TestStore,
169 or a class which has been added to this @'TextStore' by @'TextStore::AddMarkerOwner'. This requirement is
170 new to Led 2.3 (but unlikely ever violated before).</p>
171 <p>It is an error to add a marker which is already added to some TextStore (even this TextStore).</p>
172 <p>It is an error to destroy a TextStore without first removing all markers you had added.
173 It is in order to make that more efficient that we provide the RemoveMarkers () method.</p>
174 <p>We are strict about markers extenting outside valid index ranges - this is an prohibited.</p>
175 */
176 virtual void AddMarker (Marker* marker, size_t lhs, size_t length, MarkerOwner* owner) = 0;
177
178 /*
179 @METHOD: TextStore::RemoveMarker
180 @DESCRIPTION: <p>Remove the given marker from the TextStore. It is an error if the given marker is not already
181 in this TextStore. See @'TextStore::AddMarker' for more details.</p>
182 */
183
184 nonvirtual void RemoveMarker (Marker* marker);
185 /*
186 @METHOD: TextStore::RemoveMarkers
187 @DESCRIPTION: <p>Remove the given markers from the TextStore. This is essentially the same as doing multiple
188 @'TextStore::RemoveMarker' calls, except that it may be more efficient for removing large numbers of markers.</p>
189 <p>See also @'TextStore::PreRemoveMarker'.</p>
190 <p>See also @'TextStore::RemoveTypedMarkers'.</p>
191 */
192 virtual void RemoveMarkers (Marker* const markerArray[], size_t markerCount) = 0;
193
194 template <typename T>
195 /*
196 @METHOD: TextStore::RemoveTypedMarkers
197 @DESCRIPTION: <p>A variant of @'TextStore::RemoveMarkers' which can be called with an array of any time 'T' that publicly
198 subclasses from @'Marker'.</p>
199 */
200 nonvirtual void RemoveTypedMarkers (T* const ma[], size_t mc)
201 {
202 vector<Marker*> v;
203 for (size_t i = 0; i < mc; ++i) {
204 v.push_back (ma[i]);
205 }
206 RemoveMarkers (Foundation::Containers::Start (v), mc);
207 }
208
209 template <typename T>
210 /*
211 @METHOD: TextStore::RemoveAndDeleteMarkers
212 @DESCRIPTION: <p>Calls @'TextStore::RemoveTypedMarkers' and then deletes each marker.</p>
213 */
214 nonvirtual void RemoveAndDeleteMarkers (T* const ma[], size_t mc)
215 {
216 RemoveTypedMarkers (ma, mc);
217 for (size_t i = 0; i < mc; ++i) {
218 delete (ma[i]);
219 }
220 }
221
222 /*
223 @METHOD: TextStore::PreRemoveMarker
224 @DESCRIPTION: <p>Don't entirely remove the marker (so it can still be queried for
225 size etc). But mark it so it will not appear in future CollectAllXXX methods.</p>
226 <p>This is <em>NOT</em> required before calling @'TextStore::RemoveMarker',
227 but can be handy from classes like @'MarkerMortuary<MARKER>'.</p>
228 <p>This method was added to fix SPR#0822 - see for details.</p>
229 */
230 virtual void PreRemoveMarker (Marker* marker) = 0;
231
232 public:
233 /*
234 @METHOD: TextStore::SetMarkerRange
235 @DESCRIPTION: <p>Set the bounds of the given marker. The given marker must already
236 be in this TextStore (see @'TextStore::AddMarker').
237 And it is required that the start/end values be within the valid marker
238 range for this buffer.</p>
239 */
240 virtual void SetMarkerRange (Marker* marker, size_t start, size_t end) noexcept = 0;
241 nonvirtual void SetMarkerStart (Marker* marker, size_t start) noexcept;
242 nonvirtual void SetMarkerEnd (Marker* marker, size_t end) noexcept;
243 nonvirtual void SetMarkerLength (Marker* marker, size_t length) noexcept;
244
245 /*
246 * Family of routines to retreive markers of interest in a particular range of the text.
247 *
248 * The 'Overlap' method is what is used to see if a marker is considered to be in the
249 * given from/to range for the purpose of collection (does the obvious intersection test
250 * with the added caveat of not including markers which only overlap at one edge or the other
251 * - and not including any common characters).
252 *
253 * You can either specify a callback function/object to be called with each found marker.
254 * If you only need the first such, you can throw to terminate the search. There is a help class
255 * and helper fuctions to allow you to fill an array with the all the matching Markers.
256 *
257 * NB: this has changed somewhat since Led21 - see qSupportLed21CompatAPI, and SPRs#420,421,422
258 */
259 public:
260 /*
261 @CLASS: TextStore::MarkerSink
262 @DESCRIPTION:
263 <p>An abstract "callback class", used to be notified in calls to CollectAllMarkersInRangeInto.
264 To use this class, subclass, and OVERRIDE the Append () method. Pass an instance of your
265 subclass to TextStore::CollectAllMarkersInRangeInto (or some variant). Your classes Append
266 method will be called for each marker in the given range.</p>
267 */
268 class MarkerSink {
269 public:
270 /*
271 @METHOD: TextStore::MarkerSink::Append
272 @DESCRIPTION:
273 <p>Don't call directly. Called by TextStore::CollectAllMarkersInRangeInto (). Override this method
274 and pass an instance of your subclass to TextStore::CollectAllMarkersInRangeInto ().</p>
275 */
276 virtual void Append (Marker* m) = 0;
277 };
278 /*
279 @CLASS: TextStore::VectorMarkerSink
280 @BASES: @'TextStore::MarkerSink'
281 @DESCRIPTION:
282 <p>A utility class which gathers all the markers passed to it into an array (vector).</p>
283 */
284 class VectorMarkerSink : public MarkerSink {
285 public:
286 VectorMarkerSink (vector<Marker*>* markers);
287
288 virtual void Append (Marker* m) override;
289
290 private:
291 vector<Marker*>* fMarkers;
292 };
293
294 /*
295 @CLASS: TextStore::InlineBufferMarkerSink
296 @BASES: @'TextStore::MarkerSink'
297 @DESCRIPTION:
298 <p>A utility class which gathers all the markers passed to it into an array (vector).</p>
299 */
300 class InlineBufferMarkerSink : public MarkerSink {
301 public:
302 InlineBufferMarkerSink () = default;
303
304 virtual void Append (Marker* m) override;
305
306 public:
307 Foundation::Memory::InlineBuffer<Marker*> fMarkers;
308 };
309
310 public:
311 /*
312 @METHOD: TextStore::CollectAllMarkersInRangeInto
313 @DESCRIPTION: <p>Note - owner can be any valid MarkerOwner, or @'TextStore::kAnyMarkerOwner'.</p>
314 */
315 static const MarkerOwner* kAnyMarkerOwner;
316 virtual void CollectAllMarkersInRangeInto (size_t from, size_t to, const MarkerOwner* owner, MarkerSink& output) const = 0;
317
318 // Related helpers
319 // _OrSurroundings () versions include markers which overlapped just barely on an edge
320 public:
321 static bool Overlap (size_t mStart, size_t mEnd, size_t from, size_t to);
322 static bool Overlap (const Marker& m, size_t from, size_t to);
323
324 nonvirtual void CollectAllMarkersInRangeInto_OrSurroundings (size_t from, size_t to, const MarkerOwner* owner, MarkerSink& output) const;
325
326 nonvirtual void CollectAllMarkersInRangeInto (size_t from, size_t to, const MarkerOwner* owner, vector<Marker*>* markerList) const;
327 nonvirtual void CollectAllMarkersInRangeInto_OrSurroundings (size_t from, size_t to, const MarkerOwner* owner, vector<Marker*>* markerList) const;
328
329 nonvirtual vector<Marker*> CollectAllMarkersInRange (size_t from, size_t to, const MarkerOwner* owner = kAnyMarkerOwner) const;
330 nonvirtual vector<Marker*> CollectAllMarkersInRange_OrSurroundings (size_t from, size_t to, const MarkerOwner* owner = kAnyMarkerOwner) const;
331
332 /*
333 * NB: We consider that if there is NO text, there is still 1 line. We could the zero-length line
334 * at the end of the buffer as one line.
335 *
336 * GetLineContainingPosition () returns the lineNumber that fully contains the
337 * character at position charPosition. It is an error to call this if charPosition
338 * is not a valid position.
339 *
340 * GetEndOfLine () returns the position BEFORE the NL (or after the last character)
341 * if there is no NL. This corresponds to being AFTER the last displayed character.
342 */
343 public:
344 virtual size_t GetStartOfLine (size_t lineNumber) const;
345 virtual size_t GetStartOfLineContainingPosition (size_t charPosition) const;
346 virtual size_t GetEndOfLine (size_t lineNumber) const;
347 virtual size_t GetEndOfLineContainingPosition (size_t afterPos) const;
348 virtual size_t GetLineContainingPosition (size_t charPosition) const;
349 virtual size_t GetLineCount () const;
350 nonvirtual size_t GetLineLength (size_t lineNumber) const; // end-start
351
352 /*
353 * Char/Line navigating commands.
354 *
355 * These always return a valid character position. If they are pinned up against
356 * the beggining or end of the buffer, they just return that end position.
357 */
358 public:
359 nonvirtual size_t FindNextCharacter (size_t afterPos) const; // error to call in mid character - at end of buffer - we just return position after last character
360 nonvirtual size_t FindPreviousCharacter (size_t beforePos) const; // error to call in mid character - at start of buffer, we just return 1
361
362 public:
363 nonvirtual shared_ptr<TextBreaks> GetTextBreaker () const;
364 nonvirtual void SetTextBreaker (const shared_ptr<TextBreaks>& textBreaker);
365
366 private:
367 mutable shared_ptr<TextBreaks> fTextBreaker{};
368
369 public:
370 nonvirtual void FindWordBreaks (size_t afterPosition, size_t* wordStartResult, size_t* wordEndResult, bool* wordReal,
371 TextBreaks* useTextBreaker = nullptr);
372 nonvirtual void FindLineBreaks (size_t afterPosition, size_t* wordEndResult, bool* wordReal, TextBreaks* useTextBreaker = nullptr);
373
374 /*
375 * Some helpful word-break utility routines based on FindWordBreaks().
376 */
377 public:
378 nonvirtual size_t FindFirstWordStartBeforePosition (size_t position, bool wordMustBeReal = true);
379 // use for find-prev-word (can return position if position==1
380 // and if no previous word - return 1.
381 nonvirtual size_t FindFirstWordStartStrictlyBeforePosition (size_t position, bool wordMustBeReal = true);
382 // use for find-prev-word (can return position if position==1
383 // and if no previous word - return 1.
384 nonvirtual size_t FindFirstWordEndAfterPosition (size_t position, bool wordMustBeReal = true);
385 // Can return EndOfBuffer if no word-end after position.
386 // Also - might not be called in the context of a word!
387 // Can return position - if it is the end of a word.
388 nonvirtual size_t FindFirstWordStartAfterPosition (size_t position); // use for find-next-word.
389 // returns end-of-buffer if no following word-start
390 // Can return position - note we didn't say STRICTLY after
391
392 /*
393 * Search/Find/Replace support.
394 */
395 public:
396 struct SearchParameters {
397 SearchParameters (const Led_tString& searchString = LED_TCHAR_OF (""), bool wrap = true, bool wholeWord = false, bool caseSensative = false);
398
399 Led_tString fMatchString;
400 bool fWrapSearch;
401 bool fWholeWordSearch;
402 bool fCaseSensativeSearch;
403 };
404
405 enum {
406 eUseSearchParameters = kBadIndex
407 };
408 // return kBadIndex if no match found - otherwise, return index of first char in match.
409 // Start searching after markerPos searchFrom. if searchTo==eUseSearchParameters, then
410 // either search to end of buffer (fWrapSearch==false), or wrap search (fWrapSearch==true).
411 // If searchTo!=eUseSearchParameters, then consider IT to specify the end of the search (ignore
412 // fWrapSearch).
413 virtual size_t Find (const SearchParameters& params, size_t searchFrom, size_t searchTo = eUseSearchParameters);
414
415 // Helper functions, mainly for subclasses, but imagers may use too
416 public:
417 nonvirtual void DoAboutToUpdateCalls (const UpdateInfo& updateInfo, Marker* const* markersBegin, Marker* const* markersEnd);
418 nonvirtual void DoDidUpdateCalls (const UpdateInfo& updateInfo, Marker* const* markersBegin, Marker* const* markersEnd) noexcept;
419
420 public:
421 class SimpleUpdater;
422
423 public:
424 virtual TextStore* PeekAtTextStore () const override;
425
426 public:
427 /**
428 * Debugging support.
429 * Note that all these calls (either fail to compile with debug off,
430 * or produce NO-CODE when debug is off. So they can be called freely (though
431 * they might be QUITE expensive when debug on - so SOME conservatism may
432 * be in order).
433 */
434 nonvirtual void Invariant () const;
435
436#if qStroika_Foundation_Debug_AssertionsChecked
437 protected:
438 virtual void Invariant_ () const;
439#endif
440 };
441
442 /*
443 @CLASS: TextStore::SimpleUpdater
444 @DESCRIPTION: <p>Simple utility to make it a bit easier to make @'TextStore::DoAboutToUpdateCalls' /
445 @'TextStore::DoDidUpdateCalls'.</p>
446 <p>Constructing the object does the about-to-update call (collecting all the markers in
447 the range given by from/to or the explic updateInfo object), and then the destructor calls the
448 didUpdates. So you would write code like:
449 <code><pre>
450 TextStore::SimpleUpdater updater (*ts, from, to);
451 try {
452 Do_Some_Code ();
453 }
454 catch (...) {
455 updater.Cancel ();
456 throw;
457 }
458 </pre></code>
459 </p>
460 <p>The did-updates are done when the updater object goes out of scope; even if it goes out
461 of scope because of an exception - whcih MAY NOT be desired. Because of this, you should
462 call the cancel method to prevent the didUpdates from happening on destruction.
463 </p>
464 */
465 class TextStore::SimpleUpdater : public Foundation::Memory::UseBlockAllocationIfAppropriate<SimpleUpdater> {
466 public:
467 SimpleUpdater (TextStore& ts, const UpdateInfo& updateInfo);
468 SimpleUpdater (TextStore& ts, size_t from, size_t to, bool realContentUpdate = true);
469 ~SimpleUpdater ();
470
471 public:
472 nonvirtual void Cancel ();
473
474 private:
475 TextStore& fTextStore;
476 InlineBufferMarkerSink fMarkerSink;
477 UpdateInfo fUpdateInfo;
478 bool fCanceled;
479 };
480
481 // Helpers for MarkerSink classes
482
483 /*
484 @CLASS: MarkerOfATypeMarkerSink<T>
485 @BASES: @'TextStore::MarkerSink'
486 @DESCRIPTION: <p>A MarkerSink template which grabs only Markers of subtype 'T' (using dynamic_cast<>). Similar to
487 @'MarkersOfATypeMarkerSink2Vector<T>' except that this class only grabs a SINGLE marker in that range
488 (which is stored in fResult). It is an error (detected via an assertion) if this class is ever used
489 to sink more than one marker (though its OK if it gets none - fResult will be nullptr).</p>
490 */
491 template <typename T>
492 class MarkerOfATypeMarkerSink : public TextStore::MarkerSink {
493 public:
494 MarkerOfATypeMarkerSink ();
495
496 virtual void Append (Marker* m) override;
497
498 T* fResult;
499 };
500
501 /*
502 @CLASS: MarkersOfATypeMarkerSink2Vector<T>
503 @BASES: @'TextStore::MarkerSink'
504 @DESCRIPTION: <p>A MarkerSink template which grabs only Markers of subtype 'T' (using dynamic_cast<>).
505 Dumps results into a vector named 'fResult'.</p>
506 */
507 template <typename T>
508 class MarkersOfATypeMarkerSink2Vector : public TextStore::MarkerSink {
509 public:
510 MarkersOfATypeMarkerSink2Vector ();
511
512 virtual void Append (Marker* m) override;
513
514 vector<T*> fResult;
515 };
516
517 /*
518 @CLASS: MarkersOfATypeMarkerSink2SmallStackBuffer<T>
519 @BASES: @'TextStore::MarkerSink'
520 @DESCRIPTION: <p>A MarkerSink template which grabs only Markers of subtype 'T' (using dynamic_cast<>).
521 Dumps results into a @'Memory::StackBuffer<T>' named 'fResult'.</p>
522 */
523 template <typename T>
524 class MarkersOfATypeMarkerSink2SmallStackBuffer : public TextStore::MarkerSink {
525 public:
526 MarkersOfATypeMarkerSink2SmallStackBuffer ();
527
528 virtual void Append (Marker* m) override;
529
530 Foundation::Memory::InlineBuffer<T*> fResult;
531 };
532
533}
534
535/*
536 ********************************************************************************
537 ***************************** Implementation Details ***************************
538 ********************************************************************************
539 */
540#include "TextStore.inl"
541
542#endif /*_Stroika_Frameworks_Led_TextStore_h_*/
CONTAINER::value_type * Start(CONTAINER &c)
For a contiguous container (such as a vector or basic_string) - find the pointer to the start of the ...