1 // Lowest level Text handling functions
2 // Copyright (C) 2000 Core Technologies.
3 
4 // This file is part of e93.
5 //
6 // e93 is free software; you can redistribute it and/or modify
7 // it under the terms of the e93 LICENSE AGREEMENT.
8 //
9 // e93 is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // e93 LICENSE AGREEMENT for more details.
13 //
14 // You should have received a copy of the e93 LICENSE AGREEMENT
15 // along with e93; see the file "LICENSE.TXT".
16 
17 #include	"includes.h"
18 
19 // NOTE: text is managed using a "all in memory" model
20 // so, as far as most functions are concerned, the entire
21 // text data structure is kept in memory while it is being edited
22 // The OS swapping functions are charged with mapping the data in and out
23 //
24 // To manage the text data efficiently, and to keep memory
25 // from fragmenting, all text is kept in "Chunks"
26 // Each chunk allocates CHUNK_SIZE bytes of space from the free memory pool
27 // and the text of the document is split among them. The chunks are linked
28 // to each other in order. When text is inserted, additional chunks are allocated
29 // as necessary, and linked into the list at the appropriate location.
30 // Chunks keep track of how many bytes are currently used within them.
31 // Lines are allowed to span chunks.
32 // Chunks are not allowed to contain 0 bytes.
33 
34 
DisposeChunk(CHUNK_HEADER * chunk)35 static void DisposeChunk(CHUNK_HEADER *chunk)
36 // deallocate the memory used by chunk
37 {
38 	MDisposePtr(chunk->data);
39 	MDisposePtr(chunk);
40 }
41 
CreateChunk()42 static CHUNK_HEADER *CreateChunk()
43 // create a chunk header, and its corresponding data
44 // if there is a problem, SetError, and return NULL
45 {
46 	CHUNK_HEADER
47 		*chunk;
48 
49 	if((chunk=(CHUNK_HEADER *)MNewPtrClr(sizeof(CHUNK_HEADER))))
50 	{
51 		if((chunk->data=(UINT8 *)MNewPtr(CHUNK_SIZE)))
52 		{
53 			return(chunk);
54 		}
55 		MDisposePtr(chunk);
56 	}
57 	return((CHUNK_HEADER *)NULL);
58 }
59 
UniverseSanityCheck(TEXT_UNIVERSE * universe)60 bool UniverseSanityCheck(TEXT_UNIVERSE *universe)
61 // check the sanity of the text universe, report result
62 // if there is something wrong, crash or SetError, and return false
63 {
64 	CHUNK_HEADER
65 		*previousChunk,
66 		*currentChunk;
67 	UINT32
68 		totalChunks,
69 		actualTotalLines,
70 		chunkTotalBytes,
71 		chunkTotalLines;
72 	UINT32
73 		i;
74 	bool
75 		sawCachedChunk,
76 		fail;
77 
78 	fail=sawCachedChunk=false;
79 	chunkTotalBytes=0;
80 	chunkTotalLines=0;
81 	totalChunks=0;
82 	previousChunk=NULL;
83 	currentChunk=universe->firstChunkHeader;
84 	while(currentChunk)
85 	{
86 		totalChunks++;
87 		if(currentChunk==universe->cacheChunkHeader)		// see if this is the cached chunk
88 		{
89 			sawCachedChunk=true;
90 			if(chunkTotalLines!=universe->cacheLines)
91 			{
92 				SetError("Cache lines %d does not agree with %d\n",universe->cacheLines,chunkTotalLines);
93 				fail=true;
94 			}
95 			if(chunkTotalBytes!=universe->cacheBytes)
96 			{
97 				SetError("Cache bytes %d does not agree with actual %d\n",universe->cacheBytes,chunkTotalBytes);
98 				fail=true;
99 			}
100 		}
101 		if(currentChunk->previousHeader==previousChunk)
102 		{
103 			if(currentChunk->totalBytes)
104 			{
105 				actualTotalLines=0;
106 				for(i=0;i<currentChunk->totalBytes;i++)
107 				{
108 					if(currentChunk->data[i]=='\n')
109 					{
110 						actualTotalLines++;
111 					}
112 				}
113 				if(actualTotalLines==currentChunk->totalLines)
114 				{
115 					chunkTotalBytes+=currentChunk->totalBytes;
116 					chunkTotalLines+=currentChunk->totalLines;
117 					previousChunk=currentChunk;
118 					currentChunk=currentChunk->nextHeader;
119 				}
120 				else
121 				{
122 					SetError("Incorrect number of lines @ %X (he said there were %d, I saw %d)\n",currentChunk,currentChunk->totalLines,actualTotalLines);
123 					fail=true;
124 				}
125 			}
126 			else
127 			{
128 				SetError("Empty chunk @ %X\n",currentChunk);
129 				fail=true;
130 			}
131 		}
132 		else
133 		{
134 			SetError("Incorrect previous header %X @ %X\n",currentChunk->previousHeader,currentChunk);
135 			fail=true;
136 		}
137 	}
138 	if(!fail)
139 	{
140 		if(universe->lastChunkHeader==previousChunk)
141 		{
142 			if(chunkTotalBytes==universe->totalBytes)
143 			{
144 				if(chunkTotalLines==universe->totalLines)
145 				{
146 					if(!universe->cacheChunkHeader||sawCachedChunk)
147 					{
148 						if(totalChunks)
149 						{
150 							SetError("Total chunks %d, efficiency: %d%%\n",totalChunks,((universe->totalBytes/CHUNK_SIZE)*100)/(totalChunks));
151 						}
152 						else
153 						{
154 							SetError("Total chunks %d\n",totalChunks);
155 						}
156 					}
157 					else
158 					{
159 						SetError("Invalid cache chunk header: %X\n",universe->cacheChunkHeader);
160 						fail=true;
161 					}
162 				}
163 				else
164 				{
165 					SetError("Total lines mismatch: chunks=%d, universe=%d\n",chunkTotalLines,universe->totalLines);
166 					fail=true;
167 				}
168 			}
169 			else
170 			{
171 				SetError("Total bytes mismatch: chunks=%d, universe=%d\n",chunkTotalBytes,universe->totalBytes);
172 				fail=true;
173 			}
174 		}
175 		else
176 		{
177 			SetError("The universe's last chunk did not match last one found\n");
178 			fail=true;
179 		}
180 	}
181 	return(!fail);
182 }
183 
GetPosStartChunkAndDirection(TEXT_UNIVERSE * universe,UINT32 position,CHUNK_HEADER ** chunk,UINT32 * currentBytes,UINT32 * currentLines,bool * backwards)184 static void GetPosStartChunkAndDirection(TEXT_UNIVERSE *universe,UINT32 position,CHUNK_HEADER **chunk,UINT32 *currentBytes,UINT32 *currentLines,bool *backwards)
185 // return a chunk, and a direction to start searching,
186 // given a position that is desired
187 // NOTE: this uses the current cache information to help speed things up
188 // NOTE ALSO: position must not be out of range
189 {
190 	if(universe->cacheChunkHeader)					// if there is a cached chunk, see which direction we should go from it
191 	{
192 		*backwards=(position<universe->cacheBytes);	// see which direction from the cache we need to go
193 		*currentBytes=universe->cacheBytes;
194 		*currentLines=universe->cacheLines;
195 		*chunk=universe->cacheChunkHeader;
196 	}
197 	else
198 	{
199 		*backwards=false;							// work from the start
200 		*currentBytes=0;
201 		*currentLines=0;
202 		*chunk=universe->firstChunkHeader;
203 	}
204 }
205 
GetLineStartChunkAndDirection(TEXT_UNIVERSE * universe,UINT32 line,CHUNK_HEADER ** chunk,UINT32 * currentBytes,UINT32 * currentLines,bool * backwards)206 static void GetLineStartChunkAndDirection(TEXT_UNIVERSE *universe,UINT32 line,CHUNK_HEADER **chunk,UINT32 *currentBytes,UINT32 *currentLines,bool *backwards)
207 // return a chunk, and a direction to start searching,
208 // given a line number that is desired
209 // NOTE: this uses the current cache information to help speed things up
210 // NOTE ALSO: line must not be out of range
211 {
212 	if(universe->cacheChunkHeader)					// if there is a cached chunk, see which direction we should go from it
213 	{
214 		*backwards=(line<=universe->cacheLines);	// see which direction from the cache we need to go
215 		*currentBytes=universe->cacheBytes;
216 		*currentLines=universe->cacheLines;
217 		*chunk=universe->cacheChunkHeader;
218 	}
219 	else
220 	{
221 		*backwards=false;							// work from the start
222 		*currentBytes=0;
223 		*currentLines=0;
224 		*chunk=universe->firstChunkHeader;
225 	}
226 }
227 
PositionToChunkPositionPastEnd(TEXT_UNIVERSE * universe,UINT32 position,CHUNK_HEADER ** chunk,UINT32 * offset)228 void PositionToChunkPositionPastEnd(TEXT_UNIVERSE *universe,UINT32 position,CHUNK_HEADER **chunk,UINT32 *offset)
229 // locate the chunk which contains the byte at position within universe
230 // if there are no chunks in the universe, return chunk set to NULL, offset set to 0
231 // if the given position is past the end of the universe, return with chunk pointed to the
232 // last chunk of the universe, and offset as the last chunk's total-bytes
233 // NOTE: this will update the cache position to the chunk which is returned
234 {
235 	CHUNK_HEADER
236 		*currentChunk;
237 	UINT32
238 		currentPosition,
239 		currentLine;
240 	bool
241 		backwards;
242 
243 	if(position>=universe->totalBytes)							// quick test to get us to the end faster (and avoid some checks in the loop below)
244 	{
245 		if((universe->cacheChunkHeader=(*chunk)=universe->lastChunkHeader))	// point to the last chunk (if there is one)
246 		{
247 			universe->cacheBytes=universe->totalBytes-universe->lastChunkHeader->totalBytes;
248 			universe->cacheLines=universe->totalLines-universe->lastChunkHeader->totalLines;
249 			(*offset)=universe->lastChunkHeader->totalBytes;	// pass back max offset
250 		}
251 		else
252 		{
253 			(*offset)=0;
254 		}
255 	}
256 	else
257 	{
258 		GetPosStartChunkAndDirection(universe,position,&currentChunk,&currentPosition,&currentLine,&backwards);
259 		if(backwards)
260 		{
261 			while(currentPosition>position)
262 			{
263 				currentChunk=currentChunk->previousHeader;
264 				currentPosition-=currentChunk->totalBytes;
265 				currentLine-=currentChunk->totalLines;
266 			}
267 		}
268 		else
269 		{
270 			while((currentPosition+=currentChunk->totalBytes)<=position)
271 			{
272 				currentLine+=currentChunk->totalLines;
273 				currentChunk=currentChunk->nextHeader;
274 			}
275 			currentPosition-=currentChunk->totalBytes;
276 		}
277 		universe->cacheChunkHeader=(*chunk)=currentChunk;
278 		universe->cacheBytes=currentPosition;
279 		universe->cacheLines=currentLine;
280 		(*offset)=position-currentPosition;
281 	}
282 }
283 
PositionToChunkPosition(TEXT_UNIVERSE * universe,UINT32 position,CHUNK_HEADER ** chunk,UINT32 * offset)284 void PositionToChunkPosition(TEXT_UNIVERSE *universe,UINT32 position,CHUNK_HEADER **chunk,UINT32 *offset)
285 // locate the chunk which contains the byte at position within universe
286 // if there is no chunk in universe at position, return NULL/0
287 // NOTE: this will update the cache position to the chunk which is returned
288 {
289 	PositionToChunkPositionPastEnd(universe,position,chunk,offset);
290 	if((*chunk)&&((*offset)>=(*chunk)->totalBytes))			// if past end, then return NULL/0
291 	{
292 		*chunk=NULL;
293 		*offset=0;
294 	}
295 }
296 
PositionToLinePosition(TEXT_UNIVERSE * universe,UINT32 position,UINT32 * line,UINT32 * lineOffset,CHUNK_HEADER ** chunk,UINT32 * chunkOffset)297 void PositionToLinePosition(TEXT_UNIVERSE *universe,UINT32 position,UINT32 *line,UINT32 *lineOffset,CHUNK_HEADER **chunk,UINT32 *chunkOffset)
298 // locate the line which contains the byte at position within universe
299 // also return chunk, and chunkOffset that point to the START of line
300 // if position is past the end of universe, line will be the last line
301 // and lineOffset will be one past the last character of line
302 // chunk/chunkOffset will point to the start of the last line (if there is one, NULL/0 if not)
303 // NOTE: this will update the cache position to the chunk which contained position
304 {
305 	CHUNK_HEADER
306 		*currentChunk;
307 	UINT32
308 		currentOffset,
309 		currentPosition,
310 		currentLine,
311 		lineStartOffset,
312 		numNewLines;
313 	bool
314 		backwards;
315 
316 	currentOffset=0;
317 	if(position>=universe->totalBytes)						// quick test to get us to the end faster (and avoid some checks in the loop below)
318 	{
319 		if((universe->cacheChunkHeader=currentChunk=universe->lastChunkHeader))	// point to the last chunk (if there is one)
320 		{
321 			universe->cacheBytes=universe->totalBytes-universe->lastChunkHeader->totalBytes;
322 			universe->cacheLines=currentLine=universe->totalLines-universe->lastChunkHeader->totalLines;
323 			currentOffset=universe->lastChunkHeader->totalBytes;	// max offset to begin searching backwards from
324 		}
325 	}
326 	else
327 	{
328 		GetPosStartChunkAndDirection(universe,position,&currentChunk,&currentPosition,&currentLine,&backwards);
329 		if(backwards)
330 		{
331 			while(currentPosition>position)
332 			{
333 				currentChunk=currentChunk->previousHeader;
334 				currentPosition-=currentChunk->totalBytes;
335 				currentLine-=currentChunk->totalLines;
336 			}
337 		}
338 		else
339 		{
340 			while((currentPosition+=currentChunk->totalBytes)<=position)
341 			{
342 				currentLine+=currentChunk->totalLines;
343 				currentChunk=currentChunk->nextHeader;
344 			}
345 			currentPosition-=currentChunk->totalBytes;
346 		}
347 		universe->cacheChunkHeader=currentChunk;
348 		universe->cacheBytes=currentPosition;
349 		universe->cacheLines=currentLine;
350 		currentOffset=position-currentPosition;					// offset to the given position
351 	}
352 
353 	*lineOffset=0;												// at the moment, no offset
354 	if(currentChunk)											// if this is valid, then there were some bytes in the universe
355 	{
356 		// walk backwards from the current position until the beginning of the current chunk, and then until the first newline encountered (if there are any)
357 		numNewLines=0;											// tells how many new lines seen in this chunk backwards from current position
358 		lineStartOffset=0;
359 		while(currentOffset)
360 		{
361 			if(currentChunk->data[--currentOffset]=='\n')
362 			{
363 				if(!numNewLines)
364 				{
365 					lineStartOffset=currentOffset+1;			// offset into the chunk that points to the start of the line
366 				}
367 				numNewLines++;
368 			}
369 			if(!numNewLines)									// increment length into the line until a newline is seen
370 			{
371 				(*lineOffset)++;
372 			}
373 		}
374 		*line=currentLine+numNewLines;							// this tells which line the position fell into
375 		if(!numNewLines)
376 		{
377 			while(currentChunk->previousHeader&&!(currentChunk->previousHeader->totalLines))	// push back through all chunks which do not contain new lines
378 			{
379 				currentChunk=currentChunk->previousHeader;
380 				(*lineOffset)+=currentChunk->totalBytes;		// this many more bytes into the line
381 			}
382 			if(currentChunk->previousHeader)					// step back into one which contains newlines
383 			{
384 				currentChunk=currentChunk->previousHeader;
385 				currentOffset=lineStartOffset=currentChunk->totalBytes;
386 			}
387 			while(currentOffset&&currentChunk->data[--currentOffset]!='\n')
388 			{
389 				(*lineOffset)++;								// keep moving back
390 				lineStartOffset=currentOffset;					// offset in chunk to start of line
391 			}
392 		}
393 		if(lineStartOffset>=currentChunk->totalBytes)
394 		{
395 			*chunk=currentChunk->nextHeader;					// move one past (possibly off the end)
396 			*chunkOffset=0;
397 		}
398 		else
399 		{
400 			*chunk=currentChunk;
401 			*chunkOffset=lineStartOffset;
402 		}
403 	}
404 	else
405 	{
406 		*chunk=NULL;
407 		*chunkOffset=0;
408 		*line=universe->totalLines;								// since it is past the end, the line is this, work backwards to get the offset
409 	}
410 }
411 
LineToChunkPosition(TEXT_UNIVERSE * universe,UINT32 line,CHUNK_HEADER ** chunk,UINT32 * offset,UINT32 * position)412 void LineToChunkPosition(TEXT_UNIVERSE *universe,UINT32 line,CHUNK_HEADER **chunk,UINT32 *offset,UINT32 *position)
413 // locate the chunk which contains the byte at the start of line within universe
414 // position is the absolute offset within the text to the start of line
415 // if no chunk contains a byte that starts line, chunk will be NULL, offset will be 0
416 // position will be the number of bytes in the text buffer
417 // NOTE: this will update the cache position to the chunk which is returned
418 {
419 	CHUNK_HEADER
420 		*currentChunk;
421 	UINT32
422 		currentByte,
423 		currentLine;
424 	bool
425 		backwards;
426 
427 	if(line>universe->totalLines||!(universe->firstChunkHeader))
428 	{
429 		if((universe->cacheChunkHeader=universe->lastChunkHeader))	// point to the last chunk (if there is one)
430 		{
431 			universe->cacheBytes=universe->totalBytes-universe->lastChunkHeader->totalBytes;
432 			universe->cacheLines=currentLine=universe->totalLines-universe->lastChunkHeader->totalLines;
433 		}
434 		(*chunk)=NULL;
435 		(*offset)=0;
436 		(*position)=universe->totalBytes;
437 	}
438 	else
439 	{
440 		GetLineStartChunkAndDirection(universe,line,&currentChunk,&currentByte,&currentLine,&backwards);	// find out where to start searching from
441 		if(backwards)
442 		{
443 			while(currentChunk->previousHeader&&currentLine>=line)		// run through the chunks until we get to the one BEFORE or during which this line starts
444 			{
445 				currentChunk=currentChunk->previousHeader;
446 				currentLine-=currentChunk->totalLines;
447 				currentByte-=currentChunk->totalBytes;
448 			}
449 		}
450 		else
451 		{
452 			while((currentLine+=currentChunk->totalLines)<line)			// run through the chunks looking for the start of the given line
453 			{
454 				currentByte+=currentChunk->totalBytes;
455 				currentChunk=currentChunk->nextHeader;					// keep going
456 			}
457 			currentLine-=currentChunk->totalLines;						// back up, to start of chunk which contains the requested line
458 		}
459 		*offset=0;
460 		while(currentLine<line)											// move through until enough lines have been counted
461 		{
462 			if(currentChunk->data[(*offset)++]=='\n')
463 			{
464 				currentLine++;
465 			}
466 			currentByte++;
467 		}
468 		(*position)=currentByte;
469 		(*chunk)=currentChunk;
470 		if((*offset)>=currentChunk->totalBytes)							// if needed, move to the next chunk
471 		{
472 			(*chunk)=currentChunk->nextHeader;
473 			(*offset)=0;
474 		}
475 	}
476 }
477 
478 
AddToChunkPosition(TEXT_UNIVERSE * universe,CHUNK_HEADER * chunk,UINT32 offset,CHUNK_HEADER ** newChunk,UINT32 * newOffset,UINT32 distanceToMove)479 void AddToChunkPosition(TEXT_UNIVERSE *universe,CHUNK_HEADER *chunk,UINT32 offset,CHUNK_HEADER **newChunk,UINT32 *newOffset,UINT32 distanceToMove)
480 // move forward from chunk/offset by distanceToMove
481 // if distanceToMove pushes us off the end, return NULL, 0
482 // It is ok to pass newChunk as a pointer to chunk
483 // NOTE: this does not update the cache position
484 {
485 	while(chunk&&(distanceToMove>=(chunk->totalBytes-offset)))
486 	{
487 		distanceToMove-=(chunk->totalBytes-offset);
488 		chunk=chunk->nextHeader;
489 		offset=0;
490 	}
491 	if((*newChunk=chunk))
492 	{
493 		*newOffset=offset+distanceToMove;
494 	}
495 	else
496 	{
497 		*newOffset=0;
498 	}
499 }
500 
ChunkPositionToNextLine(TEXT_UNIVERSE * universe,CHUNK_HEADER * chunk,UINT32 offset,CHUNK_HEADER ** newChunk,UINT32 * newOffset,UINT32 * distanceMoved)501 void ChunkPositionToNextLine(TEXT_UNIVERSE *universe,CHUNK_HEADER *chunk,UINT32 offset,CHUNK_HEADER **newChunk,UINT32 *newOffset,UINT32 *distanceMoved)
502 // locate the line that begins after chunk, and offset
503 // if chunk is NULL, return NULL, and distanceMoved=0, otherwise
504 // attempt to locate the start of the next line, returning chunk, and offset.
505 // if there is no next line start, return NULL, and distance moved as the amount we traversed before
506 // discovering there was no next line
507 // It is ok to pass newChunk as a pointer to chunk
508 // NOTE: this does not update the cache position
509 {
510 	bool
511 		found;
512 
513 	(*distanceMoved)=0;
514 
515 	if(chunk)
516 	{
517 		found=false;
518 		if(chunk->totalLines)									// see if there are any lines in this chunk (if not, dont bother looking for next new line)
519 		{
520 			while(!found&&offset<chunk->totalBytes)
521 			{
522 				if(chunk->data[offset]=='\n')
523 				{
524 					found=true;
525 				}
526 				else
527 				{
528 					(*distanceMoved)++;
529 					offset++;
530 				}
531 			}
532 			if(!found)
533 			{
534 				chunk=chunk->nextHeader;
535 				offset=0;
536 			}
537 		}
538 		else
539 		{
540 			(*distanceMoved)=chunk->totalBytes-offset;			// if no lines, skip the rest of the first chunk
541 			chunk=chunk->nextHeader;
542 			offset=0;
543 		}
544 
545 		if(!found)
546 		{
547 			while(chunk&&!chunk->totalLines)					// run through the chunks looking for one that contains a line
548 			{
549 				(*distanceMoved)+=chunk->totalBytes;
550 				chunk=chunk->nextHeader;						// skip all chunks with no lines in them
551 			}
552 			if(chunk)											// this is the chunk that contains the new line we were looking for, so locate it
553 			{
554 				while(chunk->data[offset]!='\n')
555 				{
556 					offset++;
557 				}
558 				(*distanceMoved)+=offset;
559 				found=true;
560 			}
561 		}
562 		if(found)												// at this point, we have found the new line, chunk, and offset point to the new line character, so skip over it, and return what's next
563 		{
564 			(*distanceMoved)++;
565 			offset++;
566 			if(offset>=chunk->totalBytes)						// push through to the next chunk
567 			{
568 				chunk=chunk->nextHeader;
569 				offset=0;
570 			}
571 		}
572 	}
573 	(*newChunk)=chunk;
574 	(*newOffset)=offset;
575 }
576 
CopyTextToChunk(CHUNK_HEADER * chunk,UINT32 offset,UINT8 * text,UINT32 numBytes,UINT32 * numLines)577 static void CopyTextToChunk(CHUNK_HEADER *chunk,UINT32 offset,UINT8 *text,UINT32 numBytes,UINT32 *numLines)
578 // copy text into chunk at offset for numBytes
579 // also count up how many new-lines were seen during the copy
580 // return numLines as the total number of new-line characters that were copied
581 {
582 	UINT8
583 		*dst;
584 	UINT32
585 		i;
586 
587 	dst=&(chunk->data[offset]);
588 	(*numLines)=0;
589 	for(i=0;i<numBytes;i++)
590 	{
591 		if((dst[i]=text[i])=='\n')
592 		{
593 			(*numLines)++;
594 		}
595 	}
596 }
597 
CopyChunkToChunk(CHUNK_HEADER * chunk,UINT32 offset,CHUNK_HEADER ** sourceChunk,UINT32 * sourceOffset,UINT32 numBytes,UINT32 * numLines)598 static void CopyChunkToChunk(CHUNK_HEADER *chunk,UINT32 offset,CHUNK_HEADER **sourceChunk,UINT32 *sourceOffset,UINT32 numBytes,UINT32 *numLines)
599 // copy the text starting at sourceChunk/sourceOffset into chunk at offset for numBytes
600 // also count up how many new-lines were seen during the copy
601 // return numLines as the total number of new-line characters that were copied
602 // also return with sourceChunk/sourceOffset pointing to the byte just after the last one copied
603 {
604 	CHUNK_HEADER
605 		*sChunk;
606 	UINT8
607 		*src,
608 		*dst;
609 	UINT32
610 		i,
611 		sOffset,
612 		sourceLimit;
613 
614 	dst=&(chunk->data[offset]);
615 	(*numLines)=0;
616 	if((sChunk=(*sourceChunk)))
617 	{
618 		src=sChunk->data;
619 		sOffset=*sourceOffset;
620 		sourceLimit=sChunk->totalBytes;
621 		for(i=0;i<numBytes&&sChunk;i++)
622 		{
623 			if((dst[i]=src[sOffset])=='\n')
624 			{
625 				(*numLines)++;
626 			}
627 			if(++sOffset>=sourceLimit)
628 			{
629 				sOffset=0;
630 				if((sChunk=sChunk->nextHeader))
631 				{
632 					src=sChunk->data;
633 					sourceLimit=sChunk->totalBytes;
634 				}
635 			}
636 		}
637 		(*sourceChunk)=sChunk;
638 		(*sourceOffset)=sOffset;
639 	}
640 }
641 
DetermineNewLines(CHUNK_HEADER * chunk,UINT32 offset,UINT32 numBytes,UINT32 * numLines)642 static void DetermineNewLines(CHUNK_HEADER *chunk,UINT32 offset,UINT32 numBytes,UINT32 *numLines)
643 // count the number of new-line characters at offset of chunk, for numBytes
644 // return numLines as the total number of new-line characters that were counted
645 {
646 	UINT8
647 		*dst;
648 	UINT32
649 		i;
650 
651 	dst=&(chunk->data[offset]);
652 	(*numLines)=0;
653 	for(i=0;i<numBytes;i++)
654 	{
655 		if((dst[i])=='\n')
656 		{
657 			(*numLines)++;
658 		}
659 	}
660 }
661 
DisposeChunkList(CHUNK_HEADER * startChunk)662 static void DisposeChunkList(CHUNK_HEADER *startChunk)
663 // dispose of a linked list of chunks
664 {
665 	CHUNK_HEADER
666 		*nextChunk;
667 
668 	while(startChunk)
669 	{
670 		nextChunk=startChunk->nextHeader;
671 		DisposeChunk(startChunk);
672 		startChunk=nextChunk;
673 	}
674 }
675 
CreateChunkList(CHUNK_HEADER ** startChunk,CHUNK_HEADER ** endChunk,UINT32 numChunks)676 static bool CreateChunkList(CHUNK_HEADER **startChunk,CHUNK_HEADER **endChunk,UINT32 numChunks)
677 // create a linked list of empty chunks
678 // if there is a problem, SetError, free anything allocated, and return false
679 {
680 	bool
681 		fail;
682 	CHUNK_HEADER
683 		*chunk;
684 
685 	(*startChunk)=(*endChunk)=NULL;
686 	fail=false;
687 	while(numChunks&&!fail)
688 	{
689 		if((chunk=CreateChunk()))
690 		{
691 			if(*endChunk)
692 			{
693 				(*endChunk)->nextHeader=chunk;
694 				chunk->previousHeader=(*endChunk);
695 				(*endChunk)=chunk;
696 			}
697 			else
698 			{
699 				(*startChunk)=(*endChunk)=chunk;
700 			}
701 			numChunks--;
702 		}
703 		else
704 		{
705 			DisposeChunkList(*startChunk);					// remove the partially created chunk list
706 			fail=true;
707 		}
708 	}
709 	return(!fail);
710 }
711 
712 // Text insertion cases
713 // these routines handle all of the possible cases that can occur while
714 // inserting text
715 
InsertWithRoomInChunk(TEXT_UNIVERSE * universe,CHUNK_HEADER * chunk,UINT32 offset,CHUNK_HEADER ** textChunk,UINT32 * textOffset,UINT32 numBytes)716 static bool InsertWithRoomInChunk(TEXT_UNIVERSE *universe,CHUNK_HEADER *chunk,UINT32 offset,CHUNK_HEADER **textChunk,UINT32 *textOffset,UINT32 numBytes)
717 // the chunk that the text is going to be inserted into
718 // contains enough free space to accept the entire inserted text
719 {
720 	UINT32
721 		linesInserted;
722 
723 	MMoveMem(&(chunk->data[offset]),&(chunk->data[offset+numBytes]),chunk->totalBytes-offset);	// move data within the chunk to make room
724 	CopyChunkToChunk(chunk,offset,textChunk,textOffset,numBytes,&linesInserted);
725 	chunk->totalBytes+=numBytes;							// bump number of bytes in this chunk
726 	chunk->totalLines+=linesInserted;						// bump number of lines in this chunk
727 	universe->totalBytes+=numBytes;							// adjust universal understanding
728 	universe->totalLines+=linesInserted;
729 	return(true);											// this cannot fail
730 }
731 
InsertWithRoomInTwoChunks(TEXT_UNIVERSE * universe,CHUNK_HEADER * chunk,UINT32 offset,CHUNK_HEADER ** textChunk,UINT32 * textOffset,UINT32 numBytes)732 static bool InsertWithRoomInTwoChunks(TEXT_UNIVERSE *universe,CHUNK_HEADER *chunk,UINT32 offset,CHUNK_HEADER **textChunk,UINT32 *textOffset,UINT32 numBytes)
733 // the chunk where the text insertion starts, and the next chunk contain enough free space
734 // to allow the text to be inserted
735 {
736 	CHUNK_HEADER
737 		*secondChunk;
738 	UINT32
739 		linesInserted,
740 		tempInserted;
741 	UINT32
742 		amountToMove,
743 		distanceToMove;
744 
745 	secondChunk=chunk->nextHeader;
746 	if(numBytes+offset<=CHUNK_SIZE)								// see if the added text can be contained completely within the first chunk
747 	{
748 		distanceToMove=numBytes-(CHUNK_SIZE-chunk->totalBytes);	// for speed, we would like to move as much as possible to the second chunk, but for memory efficiency, we would like to move as little as possible! (memory efficiency turns out to be better)
749 
750 		MMoveMem(secondChunk->data,&(secondChunk->data[distanceToMove]),secondChunk->totalBytes);	// move second chunk's data out of the way for the add
751 		CopyTextToChunk(secondChunk,0,&(chunk->data[chunk->totalBytes-distanceToMove]),distanceToMove,&tempInserted);	// move data from first chunk to second chunk
752 		chunk->totalBytes-=distanceToMove;
753 		secondChunk->totalBytes+=distanceToMove;				// this many more bytes now in use in second chunk
754 		chunk->totalLines-=tempInserted;
755 		secondChunk->totalLines+=tempInserted;
756 
757 		MMoveMem(&(chunk->data[offset]),&(chunk->data[offset+numBytes]),chunk->totalBytes-offset);	// move data within the chunk to make room
758 		CopyChunkToChunk(chunk,offset,textChunk,textOffset,numBytes,&linesInserted);	// copy the data into the chunk
759 		chunk->totalBytes+=numBytes;							// bump number of bytes in this chunk
760 		chunk->totalLines+=linesInserted;						// bump number of lines in this chunk
761 		universe->totalBytes+=numBytes;							// adjust universal understanding
762 		universe->totalLines+=linesInserted;
763 	}
764 	else
765 	{
766 		distanceToMove=numBytes-(CHUNK_SIZE-chunk->totalBytes);	// need to push second chunk data forward this distance
767 		MMoveMem(secondChunk->data,&(secondChunk->data[distanceToMove]),secondChunk->totalBytes);	// move second chunk's data out of the way for the add
768 		secondChunk->totalBytes+=distanceToMove;				// this many more bytes now in use in second chunk
769 		amountToMove=chunk->totalBytes-offset;					// number of bytes to move from first to second
770 
771 		CopyTextToChunk(secondChunk,offset+numBytes-CHUNK_SIZE,&(chunk->data[offset]),amountToMove,&tempInserted);	// move data from first chunk to second chunk
772 		chunk->totalLines-=tempInserted;
773 		secondChunk->totalLines+=tempInserted;
774 
775 		CopyChunkToChunk(chunk,offset,textChunk,textOffset,CHUNK_SIZE-offset,&tempInserted);	// copy source text into first chunk
776 		chunk->totalBytes=CHUNK_SIZE;
777 		chunk->totalLines+=tempInserted;
778 
779 		CopyChunkToChunk(secondChunk,0,textChunk,textOffset,numBytes-(CHUNK_SIZE-offset),&linesInserted);	// copy remaining text into second chunk
780 		secondChunk->totalLines+=linesInserted;
781 		universe->totalLines+=linesInserted+tempInserted;		// add number of lines added in first chunk
782 
783 		universe->totalBytes+=numBytes;							// adjust universal understanding
784 	}
785 	return(true);												// this cannot fail
786 }
787 
InsertWithNoRoom(TEXT_UNIVERSE * universe,CHUNK_HEADER * chunk,UINT32 offset,CHUNK_HEADER ** textChunk,UINT32 * textOffset,UINT32 numBytes)788 static bool InsertWithNoRoom(TEXT_UNIVERSE *universe,CHUNK_HEADER *chunk,UINT32 offset,CHUNK_HEADER **textChunk,UINT32 *textOffset,UINT32 numBytes)
789 // the text to be inserted cannot fit within its two adjacent chunks
790 // so, chunks will have to be created, and the text put into them
791 {
792 	UINT32
793 		linesInserted,
794 		tempInserted;
795 	UINT32
796 		numChunks,
797 		maxToMove,
798 		bytesToMove;
799 	CHUNK_HEADER
800 		*startChunk,
801 		*endChunk;
802 
803 	numChunks=(numBytes-(CHUNK_SIZE-(chunk->totalBytes))+CHUNK_SIZE-1)/CHUNK_SIZE;
804 	if(CreateChunkList(&startChunk,&endChunk,numChunks))					// create just enough chunks to hold the data
805 	{
806 		chunk->nextHeader->previousHeader=endChunk;							// link new chunks onto the list
807 		endChunk->nextHeader=chunk->nextHeader;
808 		chunk->nextHeader=startChunk;
809 		startChunk->previousHeader=chunk;
810 		universe->totalBytes+=numBytes;										// more bytes in the universe now
811 
812 		if(numBytes<(CHUNK_SIZE-offset))									// see if the bytes we are adding can be added into the first chunk
813 		{
814 			bytesToMove=chunk->totalBytes+numBytes-CHUNK_SIZE;
815 			CopyTextToChunk(endChunk,0,&(chunk->data[chunk->totalBytes-bytesToMove]),bytesToMove,&tempInserted);	// move data from old last chunk to new last chunk
816 			endChunk->totalBytes=bytesToMove;								// this many bytes in end chunk
817 			endChunk->totalLines=tempInserted;
818 			chunk->totalLines-=tempInserted;
819 			MMoveMem(&(chunk->data[offset]),&(chunk->data[offset+numBytes]),chunk->totalBytes-bytesToMove-offset);	// move data within the original chunk to make room
820 
821 			CopyChunkToChunk(chunk,offset,textChunk,textOffset,numBytes,&tempInserted);	// copy text into this chunk
822 			chunk->totalBytes=CHUNK_SIZE;									// this is now completely full
823 			chunk->totalLines+=tempInserted;								// added this many lines
824 			universe->totalLines+=tempInserted;								// add to the universe too
825 		}
826 		else
827 		{
828 			if((numBytes+offset)/CHUNK_SIZE<numChunks)						// see if we can move the stuff after the given offset all the way into the end chunk
829 			{
830 				CopyTextToChunk(endChunk,0,&(chunk->data[offset]),chunk->totalBytes-offset,&tempInserted);	// move data from old last chunk to new last chunk
831 			}
832 			else
833 			{
834 				if(chunk->totalBytes==offset)								// minor kludge to make sure we are left pointing to the correct spot
835 				{
836 					tempInserted=0;
837 				}
838 				else
839 				{
840 					CopyTextToChunk(endChunk,(numBytes+offset)%CHUNK_SIZE,&(chunk->data[offset]),chunk->totalBytes-offset,&tempInserted);	// move data from old last chunk to new last chunk
841 				}
842 			}
843 			endChunk->totalBytes=chunk->totalBytes-offset;					// this many bytes in end chunk at the moment
844 			chunk->totalBytes=offset;										// this many bytes in here
845 			chunk->totalLines-=tempInserted;								// removed lines from here
846 			endChunk->totalLines=tempInserted;								// placed them here
847 			linesInserted=0;
848 			while(numBytes&&chunk)
849 			{
850 				maxToMove=CHUNK_SIZE-offset;								// can move this many bytes
851 				if(numBytes>maxToMove)
852 				{
853 					bytesToMove=maxToMove;
854 				}
855 				else
856 				{
857 					bytesToMove=numBytes;
858 				}
859 				CopyChunkToChunk(chunk,offset,textChunk,textOffset,bytesToMove,&tempInserted);	// copy text into this chunk
860 				chunk->totalBytes+=bytesToMove;								// bump number of bytes in this chunk
861 				chunk->totalLines+=tempInserted;							// bump number of lines in this chunk
862 				linesInserted+=tempInserted;								// keep track of total number of lines inserted so far
863 				numBytes-=bytesToMove;										// this many less bytes to move now
864 				chunk=chunk->nextHeader;									// point to next chunk to fill
865 				offset=0;													// from this point on, the offset is always 0
866 			}
867 			universe->totalLines+=linesInserted;							// increment the number of lines in the universe
868 		}
869 		return(true);														// done
870 	}
871 	return(false);
872 }
873 
InsertAtLastChunk(TEXT_UNIVERSE * universe,CHUNK_HEADER * chunk,UINT32 offset,CHUNK_HEADER ** textChunk,UINT32 * textOffset,UINT32 numBytes)874 static bool InsertAtLastChunk(TEXT_UNIVERSE *universe,CHUNK_HEADER *chunk,UINT32 offset,CHUNK_HEADER **textChunk,UINT32 *textOffset,UINT32 numBytes)
875 // text is being inserted into the last chunk of the universe, and
876 // the chunk does not contain enough free space to hold it
877 // NOTE: this code must handle the case where offset is CHUNK_SIZE
878 {
879 	UINT32
880 		linesInserted,
881 		tempInserted;
882 	UINT32
883 		numChunks,
884 		maxToMove,
885 		bytesToMove;
886 	CHUNK_HEADER
887 		*startChunk,
888 		*endChunk;
889 
890 	numChunks=(numBytes-(CHUNK_SIZE-(chunk->totalBytes))+CHUNK_SIZE-1)/CHUNK_SIZE;
891 	if(CreateChunkList(&startChunk,&endChunk,numChunks))					// create just enough chunks to hold the data
892 	{
893 		chunk->nextHeader=startChunk;										// link new chunks onto the list
894 		startChunk->previousHeader=chunk;
895 		universe->lastChunkHeader=endChunk;									// update the universe
896 		universe->totalBytes+=numBytes;										// more bytes in the universe now
897 		if((numBytes+offset)/CHUNK_SIZE<numChunks)
898 		{
899 			CopyTextToChunk(endChunk,0,&(chunk->data[offset]),chunk->totalBytes-offset,&tempInserted);	// move data from old last chunk to new last chunk
900 		}
901 		else
902 		{
903 			if(chunk->totalBytes==offset)									// minor kludge to make sure we are left pointing to the correct spot
904 			{
905 				tempInserted=0;
906 			}
907 			else
908 			{
909 				CopyTextToChunk(endChunk,(numBytes+offset)%CHUNK_SIZE,&(chunk->data[offset]),chunk->totalBytes-offset,&tempInserted);	// move data from old last chunk to new last chunk
910 			}
911 		}
912 		endChunk->totalBytes=chunk->totalBytes-offset;						// this many bytes in end chunk at the moment
913 		chunk->totalBytes=offset;											// this many bytes in here
914 		chunk->totalLines-=tempInserted;									// removed lines from here
915 		endChunk->totalLines=tempInserted;									// placed them here
916 		linesInserted=0;
917 		while(numBytes&&chunk)
918 		{
919 			maxToMove=CHUNK_SIZE-offset;									// can move this many bytes
920 			if(numBytes>maxToMove)
921 			{
922 				bytesToMove=maxToMove;
923 			}
924 			else
925 			{
926 				bytesToMove=numBytes;
927 			}
928 			CopyChunkToChunk(chunk,offset,textChunk,textOffset,bytesToMove,&tempInserted);	// copy text into this chunk
929 			chunk->totalBytes+=bytesToMove;									// bump number of bytes in this chunk
930 			chunk->totalLines+=tempInserted;								// bump number of lines in this chunk
931 			linesInserted+=tempInserted;									// keep track of total number of lines inserted so far
932 			numBytes-=bytesToMove;											// this many less bytes to move now
933 			chunk=chunk->nextHeader;										// point to next chunk to fill
934 			offset=0;														// from this point on, the offset is always 0
935 		}
936 		universe->totalLines+=linesInserted;								// increment the number of lines in the universe
937 		return(true);														// done
938 	}
939 	return(false);
940 }
941 
InsertWithNoChunks(TEXT_UNIVERSE * universe,CHUNK_HEADER ** textChunk,UINT32 * textOffset,UINT32 numBytes)942 static bool InsertWithNoChunks(TEXT_UNIVERSE *universe,CHUNK_HEADER **textChunk,UINT32 *textOffset,UINT32 numBytes)
943 // text insertion is happening in an empty text universe
944 {
945 	UINT32
946 		linesInserted,
947 		tempInserted;
948 	UINT32
949 		bytesToMove;
950 	CHUNK_HEADER
951 		*chunk,
952 		*startChunk,
953 		*endChunk;
954 
955 	if(CreateChunkList(&startChunk,&endChunk,(numBytes+CHUNK_SIZE-1)/CHUNK_SIZE))	// create just enough chunks to hold the data
956 	{
957 		universe->firstChunkHeader=startChunk;
958 		universe->lastChunkHeader=endChunk;
959 		universe->totalBytes=numBytes;
960 		chunk=startChunk;
961 		linesInserted=0;
962 		while(chunk&&numBytes)
963 		{
964 			if(numBytes>CHUNK_SIZE)
965 			{
966 				bytesToMove=CHUNK_SIZE;
967 			}
968 			else
969 			{
970 				bytesToMove=numBytes;
971 			}
972 			CopyChunkToChunk(chunk,0,textChunk,textOffset,bytesToMove,&tempInserted);	// copy the data into the chunk
973 			chunk->totalBytes=bytesToMove;									// set number of bytes in this chunk
974 			chunk->totalLines=tempInserted;									// set number of lines in this chunk
975 			linesInserted+=tempInserted;									// keep track of total number of lines inserted so far
976 			numBytes-=bytesToMove;											// this many less bytes to move now
977 			chunk=chunk->nextHeader;										// point to next chunk to fill
978 		}
979 		universe->totalLines=linesInserted;									// remember the number of lines in the universe
980 		return(true);														// done
981 	}
982 	return(false);
983 }
984 
InsertUniverseChunks(TEXT_UNIVERSE * universe,UINT32 position,CHUNK_HEADER * textChunk,UINT32 textOffset,UINT32 numBytes)985 bool InsertUniverseChunks(TEXT_UNIVERSE *universe,UINT32 position,CHUNK_HEADER *textChunk,UINT32 textOffset,UINT32 numBytes)
986 // insert numBytes of text from textChunk/textOffset into universe starting at position
987 // if there is a problem, SetError, and return false
988 // this routine is allowed to either succeed, or fail completely, under no circumstance
989 // is it allowed to insert only part of the given text
990 // NOTE: it is illegal for textChunk/textOffset to point to less data than numBytes
991 // NOTE ALSO: to accommodate inserting unchunked text, textChunk is allowed to be
992 // larger than CHUNK_SIZE, and textChunk's totalLines field does not have to be accurate
993 // Finally, this is does not guarantee that it will not alter the chunk list of
994 // universe before the point of insertion.
995 // NOTE: if bytes are inserted, this will update the cache to point to the chunk that the insertion started in
996 {
997 	bool
998 		fail;
999 	CHUNK_HEADER
1000 		*chunk;
1001 	UINT32
1002 		offset;
1003 
1004 	fail=false;
1005 	if(numBytes)														// only do more work if bytes to insert
1006 	{
1007 		PositionToChunkPosition(universe,position,&chunk,&offset);		// locate the position, move the cache there
1008 		if(chunk)														// see if inserting within the text
1009 		{
1010 			if(offset>chunk->totalBytes)								// if this is out of range, fudge it back!
1011 			{
1012 				offset=chunk->totalBytes;
1013 			}
1014 			if((offset==0)&&(chunk->previousHeader)&&(chunk->previousHeader->totalBytes<CHUNK_SIZE))	// if inserting at the very start of a chunk, it is more efficient to look one back (if possible)
1015 			{
1016 				universe->cacheChunkHeader=chunk=chunk->previousHeader;
1017 				universe->cacheBytes-=chunk->totalBytes;				// move the cache back one, so it will not be effected by the insert
1018 				universe->cacheLines-=chunk->totalLines;
1019 				offset=chunk->totalBytes;
1020 			}
1021 			if(numBytes<=CHUNK_SIZE-(chunk->totalBytes))				// see if the bytes can be placed in empty space of this chunk
1022 			{
1023 				fail=!InsertWithRoomInChunk(universe,chunk,offset,&textChunk,&textOffset,numBytes);
1024 			}
1025 			else
1026 			{
1027 				if(chunk->nextHeader)									// is there another chunk after this one?
1028 				{
1029 					if(numBytes<=(2*CHUNK_SIZE)-(chunk->totalBytes+chunk->nextHeader->totalBytes))	// is there room in the combined free space of this chunk, and the next one?
1030 					{
1031 						fail=!InsertWithRoomInTwoChunks(universe,chunk,offset,&textChunk,&textOffset,numBytes);
1032 					}
1033 					else
1034 					{
1035 						fail=!InsertWithNoRoom(universe,chunk,offset,&textChunk,&textOffset,numBytes);
1036 					}
1037 				}
1038 				else	// last chunk does not have enough space to hold the text
1039 				{
1040 					fail=!InsertAtLastChunk(universe,chunk,offset,&textChunk,&textOffset,numBytes);
1041 				}
1042 			}
1043 		}
1044 		else
1045 		{
1046 			// inserting text at the very end (cache is set to NULL in this case)
1047 			if((chunk=universe->lastChunkHeader))
1048 			{
1049 				if(numBytes<=CHUNK_SIZE-(chunk->totalBytes))			// see if the bytes can be placed in empty space of this chunk
1050 				{
1051 					fail=!InsertWithRoomInChunk(universe,chunk,chunk->totalBytes,&textChunk,&textOffset,numBytes);
1052 				}
1053 				else
1054 				{
1055 					fail=!InsertAtLastChunk(universe,chunk,chunk->totalBytes,&textChunk,&textOffset,numBytes);
1056 				}
1057 			}
1058 			else
1059 			{
1060 				fail=!InsertWithNoChunks(universe,&textChunk,&textOffset,numBytes);
1061 			}
1062 		}
1063 	}
1064 	return(!fail);
1065 }
1066 
InsertUniverseText(TEXT_UNIVERSE * universe,UINT32 position,UINT8 * text,UINT32 numBytes)1067 bool InsertUniverseText(TEXT_UNIVERSE *universe,UINT32 position,UINT8 *text,UINT32 numBytes)
1068 // insert numBytes of text into universe starting at position
1069 // if there is a problem, SetError, and return false
1070 // this routine is allowed to either succeed, or fail completely, under no circumstance
1071 // is it allowed to insert only part of the given text
1072 // if there is a problem, SetError, and return false
1073 // NOTE: this will update the cache to point to the chunk that the insertion started in
1074 {
1075 	CHUNK_HEADER
1076 		psuedoChunk;														// used to create a chunk that points to text
1077 
1078 	psuedoChunk.previousHeader=psuedoChunk.nextHeader=NULL;					// set up fake chunk header, so we can call insert chunk routine
1079 	psuedoChunk.data=text;
1080 	psuedoChunk.totalBytes=numBytes;
1081 	psuedoChunk.totalLines=0;												// this does not have to be set accurately
1082 	return(InsertUniverseChunks(universe,position,&psuedoChunk,0,numBytes));
1083 }
1084 
ExtractUniverseText(TEXT_UNIVERSE * universe,CHUNK_HEADER * chunk,UINT32 offset,UINT8 * text,UINT32 numBytes,CHUNK_HEADER ** nextChunk,UINT32 * nextOffset)1085 bool ExtractUniverseText(TEXT_UNIVERSE *universe,CHUNK_HEADER *chunk,UINT32 offset,UINT8 *text,UINT32 numBytes,CHUNK_HEADER **nextChunk,UINT32 *nextOffset)
1086 // extract numBytes of text from universe at chunk/offset
1087 // if there are not numBytes of text in universe at chunk/offset, then
1088 // as many as possible will be returned
1089 // it is acceptable to pass chunk as NULL, in which case, no bytes will ever be returned
1090 // text must be large enough to accept numBytes or bad things will happen
1091 // if there is a problem, SetError, and return false
1092 {
1093 	UINT32
1094 		numToExtract;
1095 
1096 	while(numBytes&&chunk)
1097 	{
1098 		if((chunk->totalBytes-offset)<numBytes)
1099 		{
1100 			numToExtract=chunk->totalBytes-offset;
1101 		}
1102 		else
1103 		{
1104 			numToExtract=numBytes;
1105 		}
1106 		MMoveMem(&chunk->data[offset],text,numToExtract);
1107 		numBytes-=numToExtract;
1108 		text+=numToExtract;
1109 		offset+=numToExtract;
1110 		if(offset>=chunk->totalBytes)
1111 		{
1112 			chunk=chunk->nextHeader;
1113 			offset=0;
1114 		}
1115 	}
1116 	(*nextChunk)=chunk;
1117 	(*nextOffset)=offset;
1118 	return(true);
1119 }
1120 
UnlinkChunkList(TEXT_UNIVERSE * universe,CHUNK_HEADER * startChunk,CHUNK_HEADER * endChunk)1121 static void UnlinkChunkList(TEXT_UNIVERSE *universe,CHUNK_HEADER *startChunk,CHUNK_HEADER *endChunk)
1122 // unlink the chunks from startChunk to endChunk from universe
1123 {
1124 	if(startChunk->previousHeader)
1125 	{
1126 		startChunk->previousHeader->nextHeader=endChunk->nextHeader;
1127 	}
1128 	else
1129 	{
1130 		universe->firstChunkHeader=endChunk->nextHeader;
1131 	}
1132 
1133 	if(endChunk->nextHeader)
1134 	{
1135 		endChunk->nextHeader->previousHeader=startChunk->previousHeader;
1136 	}
1137 	else
1138 	{
1139 		universe->lastChunkHeader=startChunk->previousHeader;
1140 	}
1141 	startChunk->previousHeader=endChunk->nextHeader=NULL;			// terminate the list
1142 }
1143 
DeleteWithinChunk(TEXT_UNIVERSE * universe,CHUNK_HEADER * chunk,UINT32 offset,UINT32 numBytes,CHUNK_HEADER ** nextChunk,UINT32 * nextOffset)1144 static void DeleteWithinChunk(TEXT_UNIVERSE *universe,CHUNK_HEADER *chunk,UINT32 offset,UINT32 numBytes,CHUNK_HEADER **nextChunk,UINT32 *nextOffset)
1145 // data within one chunk is being deleted
1146 // NOTE: if the cache points to the given chunk, and it is completely deleted,
1147 // the cache will be moved forward to the next chunk in the list
1148 {
1149 	UINT32
1150 		linesDeleted;
1151 
1152 	if(numBytes<chunk->totalBytes)	// see if something will remain after the deletion
1153 	{
1154 		DetermineNewLines(chunk,offset,numBytes,&linesDeleted);
1155 		MMoveMem(&(chunk->data[offset+numBytes]),&(chunk->data[offset]),chunk->totalBytes-(offset+numBytes));	// move back any bytes needed
1156 		chunk->totalBytes-=numBytes;
1157 		chunk->totalLines-=linesDeleted;
1158 		universe->totalBytes-=numBytes;
1159 		universe->totalLines-=linesDeleted;
1160 		if(offset>=chunk->totalBytes)
1161 		{
1162 			(*nextChunk)=chunk->nextHeader;
1163 			(*nextOffset)=0;
1164 		}
1165 		else
1166 		{
1167 			(*nextChunk)=chunk;
1168 			(*nextOffset)=offset;
1169 		}
1170 	}
1171 	else
1172 	{
1173 		if(universe->cacheChunkHeader==chunk)		// if cached chunk is going away, then just move it to the next one
1174 		{
1175 			universe->cacheChunkHeader=chunk->nextHeader;
1176 		}
1177 		(*nextChunk)=chunk->nextHeader;
1178 		(*nextOffset)=0;
1179 		UnlinkChunkList(universe,chunk,chunk);
1180 		universe->totalBytes-=chunk->totalBytes;
1181 		universe->totalLines-=chunk->totalLines;
1182 		DisposeChunkList(chunk);
1183 	}
1184 }
1185 
DeleteUniverseText(TEXT_UNIVERSE * universe,UINT32 position,UINT32 numBytes)1186 bool DeleteUniverseText(TEXT_UNIVERSE *universe,UINT32 position,UINT32 numBytes)
1187 // delete numBytes of text from universe starting at position
1188 // if there is a problem, SetError, and return false
1189 // this routine is allowed to either succeed, or fail completely, under no circumstance
1190 // is it allowed to delete only part of the given text
1191 // NOTE: this will move the cache position to the chunk where the deletion started
1192 {
1193 	UINT32
1194 		numToDelete;
1195 	CHUNK_HEADER
1196 		*chunk;
1197 	UINT32
1198 		offset;
1199 	bool
1200 		fail;
1201 
1202 	fail=false;
1203 	if(numBytes)
1204 	{
1205 		PositionToChunkPosition(universe,position,&chunk,&offset);	// locate the position, move the cache there
1206 		while(numBytes&&chunk)
1207 		{
1208 			if((chunk->totalBytes-offset)<numBytes)
1209 			{
1210 				numToDelete=chunk->totalBytes-offset;
1211 			}
1212 			else
1213 			{
1214 				numToDelete=numBytes;
1215 			}
1216 			DeleteWithinChunk(universe,chunk,offset,numToDelete,&chunk,&offset);
1217 			numBytes-=numToDelete;
1218 		}
1219 	}
1220 	return(!fail);
1221 }
1222 
OpenTextUniverse()1223 TEXT_UNIVERSE *OpenTextUniverse()
1224 // create the data structures necessary to manage
1225 // a universe of text
1226 // if there is a problem, SetError, and return NULL
1227 {
1228 	TEXT_UNIVERSE
1229 		*universe;
1230 
1231 	if((universe=(TEXT_UNIVERSE *)MNewPtr(sizeof(TEXT_UNIVERSE))))	// create an empty universe
1232 	{
1233 		universe->firstChunkHeader=universe->lastChunkHeader=universe->cacheChunkHeader=(CHUNK_HEADER *)NULL;
1234 		universe->totalBytes=0;
1235 		universe->totalLines=0;
1236 		universe->cacheBytes=0;
1237 		universe->cacheLines=0;
1238 		return(universe);
1239 	}
1240 	return((TEXT_UNIVERSE *)NULL);
1241 }
1242 
CloseTextUniverse(TEXT_UNIVERSE * universe)1243 void CloseTextUniverse(TEXT_UNIVERSE *universe)
1244 // dispose of a text universe
1245 {
1246 	DisposeChunkList(universe->firstChunkHeader);
1247 	MDisposePtr(universe);
1248 }
1249 
1250