Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Marker.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Frameworks_Led_Marker_h_
5#define _Stroika_Frameworks_Led_Marker_h_ 1
6
7#include "Stroika/Frameworks/StroikaPreComp.h"
8
9/*
10@MODULE: Marker
11@DESCRIPTION:
12 <p>Marker positions are measured in units of @'Led_tChar's - which don't
13 necessarily correspond exactly to bytes or characters.</p>
14 <p>A TextStore contains an array of character (Led_tChar) cells numbered from 0..n-1,
15 where n is the length of the buffer. But we will rarely refer to these,
16 and more often refer to Marker positions.</p>
17 <p>A Marker is an abstract entity which is used to keep track of portions
18 of the text. It maintains "pointers" just before, and just after character (Led_tChar)
19 cells.</p>
20 <p>It is IMPORTANT not to confuse Marker positions with character (Led_tChar) positions.
21 They are closely related, but not identical. Marker positions are
22 numbered from 0..n, where n is the length of the buffer. The marker position ALPHA,
23 comes just before the character (Led_tChar) position ALPHA. In other words, the character ALPHA
24 lies between marker positions ALPHA, and ALPHA+1.
25<pre><code>
26 (Marker m)
27 / |
28 0 1 2 3 4 5 6 7.... (marker positions)
29 |H|E|L|L|O| |W|O|R|L|D|
30 0 1 2 3 4 5 6 7.... (character positions)
31</code></pre>
32 </p>
33 <p>In this example, Marker m extends from marker positions 0..4, and has a
34 length of 4-0=4, and bounds the characters "HELL" (which were at character (Led_tChar)
35 positions 0..3).</p>
36 <p>Since there is the potential for confusion between charater positions and
37 marker positions, we will opt to ALWAYS refer to Marker positions - and NEVER
38 refer to character positions. This will make things much clearer (I hope).</p>
39 <p>So our insert routines will, for example insert not AT a particular location, but
40 just AFTER a particular marker. Similarly for deletes, etc. When you ask for
41 text to be copied out of the editor into a 'C' array of characters, you will specify
42 the marker position just before the first character (Led_tChar) you want copied out - and so on.</p>
43 <p>The main point of markers is to keep track of particular bits of text, and to
44 attatch logical attributes to them. Some of these attributes might be visible (e.g.
45 Bolding) and some may not (say a hyperlink, or dictionary source marker). But these
46 markers are intended to be ATTACHED to particular words (or sequences of bytes) in
47 the text, and NOT to positions in the text buffer. Therefore, it is one of the principle
48 functions of a marker to adjust its position values so that it can keep track of a
49 particular bit of text.</p>
50 <p>Here is how we define the behavior of markers and their tracking of text in the
51 presence of edit operations. If text is simply changed within a marker, the marker
52 doesn't move (though notification methods are called). If text is inserted to
53 the left (before) a given marker, its left and right sides are adjusted by the same
54 amount to the right. If text is removed from the left (before) the marker, then
55 the left and right sides are adjusted the same amount to the left. If the modification -
56 addition or removal - takes place to the right (after) the right side of the marker, it
57 has no effect. If an insertion or removal takes place INSIDE the bounds of the left
58 and right sides of the marker, the left side remains unchanged, and the right side
59 is incremented by the size of the insertion or decremented by the size of the removal.
60 Note: this specification counts heavily on our definition that the text changes must
61 happen before or after marker positions - and cannot happen AT those positions.
62 A markers size can never collapse below zero.</p>
63 <p>One implication of this definition of marker updating is that once a
64 markers size becomes zero, it will never be automatically increased.
65 Once they contain no text, the marker can only increase in size through programatic
66 intervention. For this reason (at least partly) it is likely that many classes of
67 markers will destroy themselves when their size decreses to zero.</p>
68 <p>Markers can also be used to do things other than mark particular bits of text. They
69 can be used to insert other sorts of graphix into the flow of the text. One particular
70 marker subclass might display a picture. Since this marker really has nothing todo with
71 any text, it might be a zero-length marker. Even zero length markers CAN end
72 up being displayed. They participate in the TextImager layout, and display.
73 But, more typically, it would be a marker of length 1 (with a sentinel character in the
74 character position marked by that marker), so that cursoring in the editor the image
75 is treated as a single item (like a character of text).</p>
76 <p>It is for this reason (and others) that we care about the ordering of markers with
77 zero length.</p>
78 <p>Markers have a natural ordering. They are ordered by their left hand sides start
79 positions. This ordering could be further refined to take into account the
80 markers right-hand sides - but that ordering would still not be a well-ordering (we
81 can easily built two markers with the same left and right sides).</p>
82 <p>Since we care for display purposes about the relative ordering of markers - even
83 those with zero length - we require a further constraint on the ordering of markers to
84 make them well-ordered. We call this the "marker sub-position order". Each marker within
85 the buffer which starts at a particular location has a sub-position index from 0..m-1,
86 where m is the number of other markers which start at the same position. This sub-position
87 order defines precedence at display time when markers overlap, and compete in the
88 TextLayout and imaging process described in the TextImager header file.</p>
89 */
90
91#include "Support.h"
92
93namespace Stroika::Frameworks::Led {
94
95 class Marker;
96 class TextStore;
97
98 /**
99 * Object which owns Markers. You register this with a @'TextStore'. And then when those markers are changed
100 * (contents within the marker), you are updated via the @'MarkerOwner::AboutToUpdateText' and @'MarkerOwner::DidUpdateText'.
101 * methods.
102 *
103 * Note that these objects should not be copied with X(X&) CTORs etc and are generally handled by pointer - because pointers
104 * to particular instances are stored in TextStore objects
105 */
107 protected:
108 MarkerOwner () = default;
109
110 public:
111 virtual ~MarkerOwner () = default;
112
113 // Methods private and not actually implemented. Just declared to prevent users from accidentally copying instances of this class. Not intended to be
114 // used that way.
115 private:
116 MarkerOwner (const MarkerOwner&) = delete;
117 const MarkerOwner& operator= (const MarkerOwner&) = delete;
118
119 public:
120 class UpdateInfo;
121
122 public:
123 virtual void AboutToUpdateText (const UpdateInfo& updateInfo);
124 virtual void EarlyDidUpdateText (const UpdateInfo& updateInfo) noexcept;
125 virtual void DidUpdateText (const UpdateInfo& updateInfo) noexcept;
126
127 public:
128 /*
129 @METHOD: MarkerOwner::PeekAtTextStore
130 @DESCRIPTION: <p>Returns the currently associated TextStore for this @'MarkerOwner'.
131 This method can return nullptr only if owns no markers!</p>
132 */
133 virtual TextStore* PeekAtTextStore () const = 0;
134
135 public:
136 nonvirtual TextStore& GetTextStore () const;
137
138 public:
139 /*
140 * Trivial wrappers on owned TextStore
141 */
142 nonvirtual size_t FindNextCharacter (size_t afterPos) const;
143 nonvirtual size_t FindPreviousCharacter (size_t beforePos) const;
144 nonvirtual size_t GetLength () const;
145 nonvirtual size_t GetEnd () const;
146 nonvirtual void CopyOut (size_t from, size_t count, Led_tChar* buffer) const;
147
148 // this field is managed by the TextStore subclass which
149 // keeps track of these markers. NO-ONE ELSE SHOULD TOUCH!!!
150 // The only reason this is public is cuz it can be used by
151 // ANY subclass of TextStore which implements the marker store.
152 public:
153 class HookData;
154 HookData* fTextStoreHook = nullptr;
155 };
156
157 /*
158 @CLASS: MarkerOwner::HookData
159 @DESCRIPTION: <p>An implementation detail of implementing a @'TextStore'. This class is used
160 as an abstract interface for the data that must be kept track of by a @'TextStore' about
161 a @'Marker' when it is added to that @'TextStore'.</p>
162 <p>This class should only be of interest to those implementing a new @'TextStore' subclass.</p>
163 */
164 class MarkerOwner::HookData {
165 protected:
166 HookData () = default;
167
168 public:
169 virtual ~HookData () = default;
170 };
171
172 /*
173 @CLASS: MarkerOwner::UpdateInfo
174 @DESCRIPTION: <p>A packaging up of information about an update, for @'MarkerOwner::AboutToUpdateText' or
175 @'MarkerOwner::DidUpdateText' methods.</p>
176 */
177 class MarkerOwner::UpdateInfo {
178 public:
179 UpdateInfo (size_t from, size_t to, const Led_tChar* withWhat, size_t withWhatCharCount, bool textModified, bool realContentUpdate);
180
181 size_t fReplaceFrom;
182 size_t fReplaceTo;
183 const Led_tChar* fTextInserted; // text that was/is to be inserted
184 size_t fTextLength;
185 bool fTextModified; // if true, fTextInserted contains any inserted text.
186 // if false, may have just been a font/style or some such change
187 bool fRealContentUpdate; // true if change is an action which needs to be 'saved'
188 // and should be interpretted as 'dirtying' the document.
189
190 public:
191 nonvirtual size_t GetResultingRHS () const;
192 };
193
194 /*
195 @CLASS: Marker
196 @DESCRIPTION: <p>A basic building-block of Led. This class marks a region of text, and sticks to that text, even as
197 text before or after is updated. Also, Markers can be notified when any attempted update happens
198 within their bounds.</p>
199 <p>Note that these objects should not be copied with X(X&) CTORs etc and are generally handled by pointer - because pointers
200 to particular instances are stored in TextStore objects</p>
201 */
202 class Marker {
203 public:
204 Marker () = default;
205 virtual ~Marker () = default;
206 Marker (const Marker&) = delete;
207 Marker& operator= (const Marker&) = delete;
208
209 // These methods are only legal to call when the marker has been added to a marker owner...
210 public:
211 nonvirtual size_t GetStart () const;
212 nonvirtual size_t GetEnd () const;
213 nonvirtual size_t GetLength () const;
214 nonvirtual MarkerOwner* GetOwner () const;
215 nonvirtual void GetRange (size_t* start, size_t* end) const;
216
217 /*
218 * Considered having one notification method since this would be more efficient - but
219 * using TWO like this is necessary to allow for cooperative transaction processing.
220 * Need to be able to say NO to this transaction - and then need to be notified and
221 * update our internals if it goes through.
222 *
223 * So AboutToUpdateText () may or may not be balanced with a call to DidUpdateText (),
224 * but DidUpdateText () is ALWAYS proceeded by a call to AboutToUpdateText () (for this
225 * update).
226 *
227 * Another interesting note on updates. The question is - what sorts of modifications
228 * produce update calls. I had considered having markers notified when text was changed
229 * INSIDE of them. This is clearly needed. But it is also - very often - quite convenient
230 * to be notified when text is changed one character to the left or right of our boundaries.
231 * For example - when we use markers to define boundaries of words - and someone types
232 * a non-space character just before or just after the word - we might want to re-consider
233 * whether we still have a valid word marker.
234 */
235 public:
236 using UpdateInfo = MarkerOwner::UpdateInfo;
237 virtual void AboutToUpdateText (const UpdateInfo& updateInfo); // throw to avoid actual update
238 virtual void DidUpdateText (const UpdateInfo& updateInfo) noexcept;
239
240 // this field is managed by the TextStore subclass which
241 // keeps track of these markers. NO-ONE ELSE SHOULD TOUCH!!!
242 // The only reason this is public is cuz it can be used by
243 // ANY subclass of TextStore which implements the marker store.
244 public:
245 class HookData;
246 HookData* fTextStoreHook{nullptr};
247 };
248
249 /*
250 @CLASS: Marker::HookData
251 @DESCRIPTION: <p>An implementation detail of implementing a @'TextStore'. This class is used
252 as an abstract interface for the data that must be kept track of by a @'TextStore' about
253 a @'Marker' when it is added to that @'TextStore'.</p>
254 <p>This class should only be of interest to those implementing a new @'TextStore' subclass.</p>
255 */
256 class Marker::HookData {
257 protected:
258 HookData () = default;
259
260 public:
261 virtual ~HookData () = default;
262
263 public:
264 virtual MarkerOwner* GetOwner () const = 0;
265 virtual size_t GetStart () const = 0;
266 virtual size_t GetEnd () const = 0;
267 virtual size_t GetLength () const = 0;
268 virtual void GetStartEnd (size_t* start, size_t* end) const = 0;
269 };
270
271 /*
272 @CLASS: MarkerMortuary<MARKER>
273 @BASES:
274 @DESCRIPTION: <p>MarkerMortuary is a template used to help delete markers from a TextStore, in a situation where
275 the markers might still be refered to someplace.</p>
276 <p>A typical use would be to accomulate them for deletion - marking them as uninteresting. Then calling
277 FinalizeMarkerDeletions () when there are certain to be no more outstanding pointers.</p>
278 <p>NB: We require that the markers added to a MarkerMortuary all share a common TextStore.</p>
279 */
280 template <typename MARKER>
281 class MarkerMortuary {
282 public:
283 MarkerMortuary () = default;
284 ~MarkerMortuary ();
285
286 public:
287 nonvirtual void AccumulateMarkerForDeletion (MARKER* m);
288 nonvirtual void SafeAccumulateMarkerForDeletion (MARKER* m);
289 nonvirtual void FinalizeMarkerDeletions () noexcept;
290 nonvirtual bool IsEmpty () const noexcept;
291
292 private:
293 vector<MARKER*> fMarkersToBeDeleted;
294 };
295
296 bool Contains (const Marker& containedMarker, const Marker& containerMarker);
297 bool Contains (size_t containedMarkerStart, size_t containedMarkerEnd, const Marker& containerMarker);
298 bool Contains (const Marker& marker, size_t charPos);
299 bool Contains (size_t containedMarkerStart, size_t containedMarkerEnd, size_t charPos);
300
301 /*
302 @CLASS: LessThan<MARKER>
303 @BASES:
304 @DESCRIPTION:
305 <p>Use this class when you have a vector of some marker subclass and want to sort it.</p>
306 <p>As in:
307 </p>
308 <code><pre>
309 vector&lt;MyMarker*&gt; markers = get_em ();
310 sort (markers.begin (), markers.end (), LessThan&lt;MyMarker&gt; ())
311 </pre></code>
312 <p>Note that as of Led 3.0d6, this allows for where two markers have
313 the same start, and then measures less
314 according to where they end.</p>
315 */
316 template <typename MARKER>
317 struct LessThan {
318 bool operator() (const MARKER* lhs, const MARKER* rhs) const
319 {
320 RequireNotNull (lhs);
321 RequireNotNull (rhs);
322 int diff = int (lhs->GetStart ()) - int (rhs->GetStart ());
323 if (diff == 0) {
324 return (lhs->GetEnd () < rhs->GetEnd ());
325 }
326 else {
327 return (diff < 0);
328 }
329 }
330 };
331
332 /*
333 @CLASS: TempMarker
334 @BASES: @'MarkerOwner'
335 @DESCRIPTION: <p>A trivial helper class which can be used in a 'stack based' fashion to
336 keep temporary track of a region of text, without all the bookkeeping of
337 having to add/remove the marker and marker owner, etc.</p>
338 */
339 class TempMarker : public MarkerOwner {
340 private:
341 using inherited = MarkerOwner;
342
343 public:
344 TempMarker (TextStore& ts, size_t start, size_t end);
345 ~TempMarker ();
346
347 public:
348 nonvirtual size_t GetStart () const;
349 nonvirtual size_t GetEnd () const;
350 nonvirtual size_t GetLength () const;
351 nonvirtual void GetLocation (size_t* from, size_t* to) const;
352
353 public:
354 virtual TextStore* PeekAtTextStore () const override;
355
356 private:
357 TextStore& fTextStore;
358 Marker fMarker{};
359 };
360
361 /*
362 @CLASS: TemporaryMarkerSlideDown<MARKER>
363 @DESCRIPTION: <p>A simple helper class to take a vector of markers, and slide them one way or the other, and then restore them.
364 This can occasionally be helpful when you are about to do some operation which you don't want to affect these markers - you can slide
365 them out of the way - so they don't get updated - and then slide them back.</p>
366 */
367 template <typename MARKER>
368 class TemporaryMarkerSlideDown {
369 public:
370 TemporaryMarkerSlideDown (TextStore& ts, const vector<MARKER*>& m, ptrdiff_t slideBy = 1);
371 ~TemporaryMarkerSlideDown ();
372
373 private:
374 TextStore& fTextStore;
375 vector<MARKER*> fMarkers;
376 ptrdiff_t fSlideBy;
377 };
378
379}
380
381/*
382 ********************************************************************************
383 ***************************** Implementation Details ***************************
384 ********************************************************************************
385 */
386#include "Marker.inl"
387
388#endif /*_Stroika_Frameworks_Led_Marker_h_*/
#define RequireNotNull(p)
Definition Assertions.h:347