1 /*  Part of XPCE --- The SWI-Prolog GUI toolkit
2 
3     Author:        Jan Wielemaker and Anjo Anjewierden
4     E-mail:        jan@swi.psy.uva.nl
5     WWW:           http://www.swi.psy.uva.nl/projects/xpce/
6     Copyright (c)  1985-2005, University of Amsterdam
7     All rights reserved.
8 
9     Redistribution and use in source and binary forms, with or without
10     modification, are permitted provided that the following conditions
11     are met:
12 
13     1. Redistributions of source code must retain the above copyright
14        notice, this list of conditions and the following disclaimer.
15 
16     2. Redistributions in binary form must reproduce the above copyright
17        notice, this list of conditions and the following disclaimer in
18        the documentation and/or other materials provided with the
19        distribution.
20 
21     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22     "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23     LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25     COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26     INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29     CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30     LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31     ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32     POSSIBILITY OF SUCH DAMAGE.
33 */
34 
35 #include <h/kernel.h>
36 #include <h/graphics.h>
37 #include <h/text.h>
38 #include <h/unix.h>
39 
40 forwards Int	getMatchingQuoteTextBuffer(TextBuffer, Int, Name);
41 forwards int	room(TextBuffer, intptr_t, intptr_t);
42 forwards status capitalise_textbuffer(TextBuffer, intptr_t, intptr_t);
43 forwards status clear_textbuffer(TextBuffer);
44 forwards status downcase_textbuffer(TextBuffer, intptr_t, intptr_t);
45 forwards void	end_change(TextBuffer, intptr_t);
46 forwards Int    getSizeTextBuffer(TextBuffer);
47 forwards status store_textbuffer(TextBuffer, intptr_t, wint_t);
48 forwards status transpose_textbuffer(TextBuffer, intptr_t, intptr_t, intptr_t, intptr_t);
49 forwards status upcase_textbuffer(TextBuffer, intptr_t, intptr_t);
50 forwards status save_textbuffer(TextBuffer, intptr_t, intptr_t, SourceSink);
51 forwards status insert_file_textbuffer(TextBuffer, intptr_t, intptr_t, SourceSink);
52 forwards status shift_fragments(TextBuffer, intptr_t, intptr_t);
53 forwards void	start_change(TextBuffer, intptr_t);
54 forwards status insert_textbuffer_shift(TextBuffer, intptr_t, intptr_t, PceString, int);
55 forwards status promoteTextBuffer(TextBuffer tb);
56 
57 #define ALLOC (256)		/* increment allocation by this amount */
58 #define ROUND(n, r)		( (((n) + (r)-1) / (r)) * (r) )
59 #define NormaliseIndex(tb, i)	( i < 0 ? 0 : i > tb->size ? tb->size : i)
60 #define Swap(a, b)		{ intptr_t _tmp = (a); (a) = (b); (b) = _tmp; }
61 #define Before(i1, i2)		{ if ( i1 > i2 ) Swap(i1, i2); }
62 #define Before_i(x, y)		if ( x > y ) { intptr_t _z = x; x=y; y=_z; }
63 #define fetch(i)		fetch_textbuffer(tb, i)
64 #define istbA(tb)		isstrA(&(tb)->buffer)
65 #define Address(tb, i)		(istbA(tb) ? &(tb)->tb_bufferA[(i)] \
66 					   : (charA *)&(tb)->tb_bufferW[(i)])
67 #define Index(tb, p) ((tb)->gap_start <= (p) ? \
68 		      (tb)->gap_end + ((p) - (tb)->gap_start) : (p) )
69 
70 
71 static status
initialiseTextBuffer(TextBuffer tb,CharArray ca)72 initialiseTextBuffer(TextBuffer tb, CharArray ca)
73 { initialiseSourceSink((SourceSink)tb);
74 
75   assign(tb, first_fragment, NIL);
76   assign(tb, last_fragment,  NIL);
77   assign(tb, editors,	     newObject(ClassChain, EAV));
78   assign(tb, generation,     ZERO);
79   obtainClassVariablesObject(tb);	/* dubious: subclassing? */
80 
81   tb->undo_buffer = NULL;
82   tb->tb_bufferA  = NULL;
83   if ( notDefault(ca) )
84     str_cphdr(&tb->buffer, &ca->data);
85   else
86     str_cphdr(&tb->buffer, str_nl(NULL)); /* ASCII */
87 
88   clear_textbuffer(tb);			/* (re)initialise buffer */
89 
90   if ( notDefault(ca) )
91     insertTextBuffer(tb, ZERO, ca, ONE);
92   assign(tb, modified, OFF);
93 
94   succeed;
95 }
96 
97 
98 static status
unlinkTextBuffer(TextBuffer tb)99 unlinkTextBuffer(TextBuffer tb)
100 { Any editor;
101 
102   for_chain(tb->editors, editor,
103 	    send(ReceiverOfEditor(editor), NAME_lostTextBuffer, EAV));
104   clearChain(tb->editors);
105 
106   while( notNil(tb->first_fragment) )	/* destroy fragments */
107     freeObject(tb->first_fragment);
108 
109   if ( tb->tb_bufferA != NULL )		/* deallocate the buffer */
110   { pceFree(tb->tb_bufferA);
111     tb->tb_bufferA = NULL;
112   }
113 
114   if ( tb->undo_buffer != NULL )
115   { destroyUndoBuffer(tb->undo_buffer);
116     tb->undo_buffer = NULL;
117   }
118 
119   succeed;
120 }
121 
122 
123 static TextBuffer
getConvertTextBuffer(Any ctx,Editor e)124 getConvertTextBuffer(Any ctx, Editor e)
125 { answer(e->text_buffer);
126 }
127 
128 
129 static status
storeTextBuffer(TextBuffer tb,FileObj file)130 storeTextBuffer(TextBuffer tb, FileObj file)
131 { IOENC oenc = file->fd->encoding;
132   int i;
133 
134   TRY(storeSlotsObject(tb, file));
135   storeIntFile(file, toInt(tb->size));
136 
137   file->fd->encoding = ENC_UTF8;
138   for(i=0; i<tb->size; i++)
139     Sputcode(fetch_textbuffer(tb, i), file->fd);
140   file->fd->encoding = oenc;
141 
142   return checkErrorFile(file);
143 }
144 
145 
146 static status
loadTextBuffer(TextBuffer tb,IOSTREAM * fd,ClassDef def)147 loadTextBuffer(TextBuffer tb, IOSTREAM *fd, ClassDef def)
148 { IOENC oenc = fd->encoding;
149   size_t i;
150   int chr;
151 
152   TRY( loadSlotsObject(tb, fd, def) );
153 
154   if ( isNil(tb->syntax) )
155     assign(tb, syntax, getClassVariableValueObject(tb, NAME_syntax));
156   if ( !isName(tb->encoding) )
157     assign(tb, encoding, NAME_text);
158 
159   assign(tb, editors, newObject(ClassChain, EAV));
160   tb->size = loadWord(fd);
161   tb->allocated = ROUND(tb->size, ALLOC);
162 
163   str_cphdr(&tb->buffer, str_nl(NULL));
164   tb->tb_bufferA = pceMalloc(tb->allocated);
165 
166   if ( restoreVersion <= 17 )		/* PRE Unicode */
167   { Sfread(Address(tb, 0), sizeof(char), tb->size, fd);
168   } else
169   { size_t end = (size_t)tb->size;
170 
171     fd->encoding = ENC_UTF8;
172 
173     for(i=0; i<end; i++)
174     { chr = Sgetcode(fd);
175 
176       if ( chr <= 0xff )
177 	tb->tb_bufferA[i] = chr;
178       else
179 	break;
180     }
181 
182     if ( i < end )			/* non-ISO characters: promote */
183     { charW *w = pceMalloc(tb->allocated * sizeof(charW));
184       const charA *f = Address(tb, 0);
185       const charA *e = &f[i];
186       charW *t = w;
187 
188       while(f<e)
189 	*t++ = *f++;
190 
191       pceFree(tb->tb_bufferA);
192       tb->tb_bufferW = w;
193       tb->buffer.s_iswide = TRUE;
194       tb->tb_bufferW[i++] = chr;
195 
196       for(; i<end; i++)
197       { chr = Sgetcode(fd);
198 
199 	tb->tb_bufferW[i] = chr;
200       }
201     }
202     fd->encoding = oenc;
203   }
204 
205   tb->gap_start = tb->size;
206   tb->gap_end = tb->allocated;
207 
208   if ( tb->lines == 0 )
209   { tb->lines = -1;			/* indicate invalid */
210     tb->lines = count_lines_textbuffer(tb, 0, tb->size);
211   }
212 
213   tb->changed_start = tb->size;
214   tb->changed_end = 0;
215   CmodifiedTextBuffer(tb, OFF);
216   assign(tb, generation, ZERO);
217 
218   succeed;
219 }
220 
221 
222 static status
cloneTextBuffer(TextBuffer tb,TextBuffer clone)223 cloneTextBuffer(TextBuffer tb, TextBuffer clone)
224 { size_t bytes;
225 
226   clonePceSlots(tb, clone);
227 
228   bytes = istbA(tb) ? (size_t)clone->allocated
229 		    : (size_t)clone->allocated*sizeof(charW);
230 
231   clone->undo_buffer = NULL;
232   clone->tb_bufferA = pceMalloc(bytes);
233   memcpy(clone->tb_bufferA, tb->tb_bufferA, bytes);
234   clone->changed_start = clone->size;
235   clone->changed_end = 0;
236 
237   succeed;
238 }
239 
240 
241 		 /*******************************
242 		 *	      EDITOR		*
243 		 *******************************/
244 
245 static status
attachTextBuffer(TextBuffer tb,Editor e)246 attachTextBuffer(TextBuffer tb, Editor e)
247 { return appendChain(tb->editors, e);
248 }
249 
250 
251 static status
detachTextBuffer(TextBuffer tb,Editor e)252 detachTextBuffer(TextBuffer tb, Editor e)
253 { deleteChain(tb->editors, e);
254 
255   succeed;
256 }
257 
258 
259 		/********************************
260 		*         REPORT ERRORS		*
261 		********************************/
262 
263 static status
reportTextBuffer(TextBuffer tb,Name kind,CharArray fmt,int argc,Any * argv)264 reportTextBuffer(TextBuffer tb, Name kind, CharArray fmt, int argc, Any *argv)
265 { Any to;
266 
267   if ( (to = get(tb, NAME_reportTo, EAV)) && notNil(to) )
268   { Any editor;
269 
270     ArgVector(av, argc + 2);
271 
272     av[0] = kind;
273     av[1] = fmt;
274     copyArgs(argc, argv, &av[2]);
275 
276     if ( (editor = get(to, NAME_container, ClassEditor, EAV)) )
277     { sendv(editor, NAME_report, argc+2, av);
278     } else
279     { sendv(to, NAME_report, argc+2, av);
280     }
281 
282     succeed;
283   }
284 
285   return printReportObject(tb, kind, fmt, argc, argv);
286 }
287 
288 
289 		/********************************
290 		*       FORWARDING CHANGES      *
291 		*********************************/
292 
293 status
changedTextBuffer(TextBuffer tb)294 changedTextBuffer(TextBuffer tb)
295 { Cell cell;
296 
297   if ( tb->changed_start <= tb->changed_end )
298   { Any av[2];
299 
300     av[0] = toInt(tb->changed_start);
301     av[1] = toInt(tb->changed_end);
302 
303     for_cell(cell, tb->editors)
304       qadSendv(cell->value, NAME_ChangedRegion, 2, av);
305   }
306 
307   tb->changed_start = tb->size;
308   tb->changed_end = 0;
309 
310   succeed;
311 }
312 
313 
314 status
ChangedRegionTextBuffer(TextBuffer tb,Int start,Int end)315 ChangedRegionTextBuffer(TextBuffer tb, Int start, Int end)
316 { intptr_t s = valInt(start);
317   intptr_t e = valInt(end);
318 
319   if ( s > e )
320   { intptr_t tmp = s;
321     s = e; e = tmp;
322   }
323   start_change(tb, s);
324   end_change(tb, e);
325 
326   return changedTextBuffer(tb);
327 }
328 
329 
330 status
ChangedFragmentListTextBuffer(TextBuffer tb)331 ChangedFragmentListTextBuffer(TextBuffer tb)
332 { Cell cell;
333 
334   for_cell(cell, tb->editors)
335     qadSendv(cell->value, NAME_ChangedFragmentList, 0, NULL);
336 
337   succeed;
338 }
339 
340 
341 		/********************************
342 		*    OBJECT LEVEL OPERATIONS    *
343 		*********************************/
344 
345 status
clearTextBuffer(TextBuffer tb)346 clearTextBuffer(TextBuffer tb)
347 { clear_textbuffer(tb);
348 
349   return changedTextBuffer(tb);
350 }
351 
352 
353 status
insertFileTextBuffer(TextBuffer tb,Int where,SourceSink file,Int times)354 insertFileTextBuffer(TextBuffer tb, Int where, SourceSink file, Int times)
355 { if ( isDefault(times) )
356     times = ONE;
357 
358   if ( valInt(times) <= 0 )
359     succeed;
360 
361   if ( insert_file_textbuffer(tb, valInt(where), valInt(times), file) )
362     return changedTextBuffer(tb);
363 
364   fail;
365 }
366 
367 
368 status
insertTextBuffer(TextBuffer tb,Int where,CharArray ca,Int times)369 insertTextBuffer(TextBuffer tb, Int where, CharArray ca, Int times)
370 { if ( isDefault(times) )
371     times = ONE;
372 
373   insert_textbuffer(tb,
374 		    valInt(where),
375 		    valInt(times),
376 		    &ca->data);
377 
378   return changedTextBuffer(tb);
379 }
380 
381 
382 status
CAppendTextBuffer(TextBuffer tb,const char * text)383 CAppendTextBuffer(TextBuffer tb, const char *text)
384 { string s;
385 
386   str_set_ascii(&s, (char*)text);
387   insert_textbuffer_shift(tb,
388 			  tb->size,
389 			  1,
390 			  &s,
391 			  FALSE);
392 
393   return changedTextBuffer(tb);
394 
395 }
396 
397 
398 status
appendTextBuffer(TextBuffer tb,CharArray ca,Int times)399 appendTextBuffer(TextBuffer tb, CharArray ca, Int times)
400 { if ( isDefault(times) )
401     times = ONE;
402 
403   insert_textbuffer_shift(tb,
404 			  tb->size,
405 			  valInt(times),
406 			  &ca->data,
407 			  FALSE);	/* don't shift fragments! */
408 
409   return changedTextBuffer(tb);
410 }
411 
412 
413 static status
formatTextBuffer(TextBuffer tb,CharArray fmt,int argc,Any * argv)414 formatTextBuffer(TextBuffer tb, CharArray fmt, int argc, Any *argv)
415 { string s;
416 
417   TRY(str_writefv(&s, fmt, argc, argv));
418   insert_textbuffer_shift(tb, tb->size, 1, &s, FALSE);
419 					/* don't shift fragments! */
420   str_unalloc(&s);
421 
422   return changedTextBuffer(tb);
423 }
424 
425 
426 status
deleteTextBuffer(TextBuffer tb,Int where,Int times)427 deleteTextBuffer(TextBuffer tb, Int where, Int times)
428 { if ( isDefault(times) )
429     times = ONE;
430 
431   delete_textbuffer(tb, valInt(where), valInt(times));
432 
433   return changedTextBuffer(tb);
434 }
435 
436 
437 status
saveTextBuffer(TextBuffer tb,SourceSink file,Int from,Int len)438 saveTextBuffer(TextBuffer tb, SourceSink file, Int from, Int len)
439 { int clear_modified = (isDefault(from) && isDefault(len));
440 
441   if ( isDefault(from) )
442     from = ZERO;
443   if ( isDefault(len) )
444     len = toInt(tb->size);
445 
446   if ( save_textbuffer(tb, valInt(from), valInt(len), file) )
447   { if ( clear_modified )
448       CmodifiedTextBuffer(tb, OFF);
449     succeed;
450   }
451 
452   fail;
453 }
454 
455 
456 static Int
getSizeTextBuffer(TextBuffer tb)457 getSizeTextBuffer(TextBuffer tb)
458 { return toInt(tb->size);
459 }
460 
461 
462 status
CmodifiedTextBuffer(TextBuffer tb,BoolObj val)463 CmodifiedTextBuffer(TextBuffer tb, BoolObj val)
464 { if ( tb->modified != val )
465     sendv(tb, NAME_modified, 1, (Any *) &val);
466 
467   if ( val == ON )
468     tb->generation = toInt(valInt(tb->generation)+1);
469 
470   succeed;
471 }
472 
473 
474 static status
modifiedTextBuffer(TextBuffer tb,BoolObj val)475 modifiedTextBuffer(TextBuffer tb, BoolObj val)
476 { if ( tb->modified != val )
477   { Cell cell;
478     assign(tb, modified, val);
479 
480     if ( val == OFF )
481       checkpointUndoTextBuffer(tb);
482 
483     for_cell(cell, tb->editors)
484       forwardModifiedEditor(cell->value, val);
485   }
486 
487   succeed;
488 }
489 
490 
491 static Int
getCharacterTextBuffer(TextBuffer tb,Int where)492 getCharacterTextBuffer(TextBuffer tb, Int where)
493 { int c = fetch_textbuffer(tb, valInt(where));
494 
495   if ( c >= 0 )
496     answer(toInt(c));
497 
498   fail;
499 }
500 
501 
502 status
characterTextBuffer(TextBuffer tb,Int where,Int c)503 characterTextBuffer(TextBuffer tb, Int where, Int c)
504 { TRY(store_textbuffer(tb, valInt(where), (wint_t)valInt(c)));
505 
506   return changedTextBuffer(tb);
507 }
508 
509 
510 status
transposeTextBuffer(TextBuffer tb,Int f1,Int t1,Int f2,Int t2)511 transposeTextBuffer(TextBuffer tb, Int f1, Int t1, Int f2, Int t2)
512 { transpose_textbuffer(tb, valInt(f1), valInt(t1), valInt(f2), valInt(t2));
513 
514   return changedTextBuffer(tb);
515 }
516 
517 
518 status
downcaseTextBuffer(TextBuffer tb,Int from,Int len)519 downcaseTextBuffer(TextBuffer tb, Int from, Int len)
520 { downcase_textbuffer(tb, valInt(from), valInt(len));
521 
522   return changedTextBuffer(tb);
523 }
524 
525 
526 status
upcaseTextBuffer(TextBuffer tb,Int from,Int len)527 upcaseTextBuffer(TextBuffer tb, Int from, Int len)
528 { upcase_textbuffer(tb, valInt(from), valInt(len));
529 
530   return changedTextBuffer(tb);
531 }
532 
533 
534 status
capitaliseTextBuffer(TextBuffer tb,Int from,Int len)535 capitaliseTextBuffer(TextBuffer tb, Int from, Int len)
536 { capitalise_textbuffer(tb, valInt(from), valInt(len));
537 
538   return changedTextBuffer(tb);
539 }
540 
541 
542 Int
getScanTextBuffer(TextBuffer tb,Int from,Name unit,Int amount,Name az)543 getScanTextBuffer(TextBuffer tb, Int from, Name unit, Int amount, Name az)
544 { if ( isDefault(amount) )
545     amount = ONE;
546   if ( isDefault(az) )
547     az = (valInt(amount) >= 0 ? NAME_end : NAME_start);
548 
549   return toInt(scan_textbuffer(tb,
550 			       valInt(from),
551 			       unit,
552 			       valInt(amount),
553 			       az == NAME_start ? 'a' : 'z'));
554 }
555 
556 
557 static status
contentsTextBuffer(TextBuffer tb,CharArray ca)558 contentsTextBuffer(TextBuffer tb, CharArray ca)
559 { clearTextBuffer(tb);
560 
561   return appendTextBuffer(tb, ca, ONE);
562 }
563 
564 
565 static StringObj
getSubTextBuffer(TextBuffer tb,Int from,Int to)566 getSubTextBuffer(TextBuffer tb, Int from, Int to)
567 { string s;
568   intptr_t f = (isDefault(from) ? 0 : valInt(from));
569   intptr_t t = (isDefault(to) ? tb->size : valInt(to));
570 
571   str_sub_text_buffer(tb, &s, f, t-f);
572   answer(StringToString(&s));
573 }
574 
575 
576 StringObj
getContentsTextBuffer(TextBuffer tb,Int from,Int length)577 getContentsTextBuffer(TextBuffer tb, Int from, Int length)
578 { if ( isDefault(from) )
579     from = ZERO;
580 
581   return getSubTextBuffer(tb, from,
582 			  isDefault(length) ? DEFAULT : add(from, length));
583 }
584 
585 
586 static status
forAllFragmentsTextBuffer(TextBuffer tb,Code msg)587 forAllFragmentsTextBuffer(TextBuffer tb, Code msg)
588 { int size = 0;
589   Fragment f;
590   Fragment *argv;
591   int alloced = FALSE;
592   int i;
593   int rc = SUCCEED;
594 
595   for(f = tb->first_fragment; notNil(f); f = f->next)
596     size++;
597 
598   if ( size > 1024 || !(argv = alloca(size*sizeof(Fragment))) )
599   { argv = pceMalloc(size*sizeof(Fragment));
600     alloced = TRUE;
601   }
602 
603 
604   for(i=0, f = tb->first_fragment; notNil(f); f = f->next)
605     argv[i++] = f;
606 
607   for(i=0; i<size; i++)
608   { if ( !isFreedObj(argv[i]) )
609     { if ( !forwardCodev(msg, 1, (Any *)&argv[i]) )
610       { rc = FAIL;
611 	break;
612       }
613     }
614   }
615 
616   if ( alloced )
617     pceFree(argv);
618 
619   return rc;
620 }
621 
622 
623 static Chain
getFindAllFragmentsTextBuffer(TextBuffer tb,Code msg)624 getFindAllFragmentsTextBuffer(TextBuffer tb, Code msg)
625 { Chain result = answerObject(ClassChain, EAV);
626   Fragment f;
627 
628   for(f = tb->first_fragment; notNil(f); f = f->next)
629   { if ( isDefault(msg) || forwardCodev(msg, 1, (Any *)&f) )
630       appendChain(result, f);
631   }
632 
633   answer(result);
634 }
635 
636 
637 static Fragment
getFindFragmentTextBuffer(TextBuffer tb,Code msg)638 getFindFragmentTextBuffer(TextBuffer tb, Code msg)
639 { Fragment f;
640 
641   for(f = tb->first_fragment; notNil(f); f = f->next)
642   { if ( forwardCodev(msg, 1, (Any *)&f) )
643       answer(f);
644   }
645 
646   fail;
647 }
648 
649 
650 static Int
getFindTextBuffer(TextBuffer tb,Int from,StringObj str,Int times,Name start,BoolObj exactcase,BoolObj wordmode)651 getFindTextBuffer(TextBuffer tb, Int from, StringObj str,
652 		  Int times, Name start, BoolObj exactcase, BoolObj wordmode)
653 { char az;
654   int result;
655   int ec, wm;
656 
657   if ( isDefault(times) )
658     times = ONE;
659   az = (isDefault(start) ? (valInt(times) >= 0 ? 'z' : 'a')
660 			 : start == NAME_start ? 'a' : 'z');
661   ec = (exactcase == ON || isDefault(exactcase) ? TRUE : FALSE);
662   wm = (wordmode == OFF || isDefault(wordmode)  ? FALSE : TRUE);
663 
664   result = find_textbuffer(tb,
665 			   valInt(from),
666 			   &str->data,
667 			   valInt(times),
668 			   az, ec, wm);
669   if ( result < 0 )
670     fail;
671 
672   answer(toInt(result));
673 }
674 
675 		/********************************
676 		*           SCANNING            *
677 		*********************************/
678 
679 /*  Find a position by scanning the text. The scan starts at `from' and
680     returns the index resulting from skipping `amount' `units'.  A unit
681     consists of the unit itself and a separator (with the exception of
682     characters that have a zero length separator between them). One can
683     scan for the start of a unit or for the end of the unit. The start
684     of the unit returns the first index of the unit. The end returns the
685     first index of the separator between this unit and the next. Below
686     are the definitions of the units recognised currently.
687 
688     NAME_character
689 	A character unit has no start, nor an end.
690     NAME_word
691 	A word consists of a contiguous string of `alNum' characters
692 	(digits + letters + _). A separator is a contiguous string of
693 	'non-alNum' characters.
694     NAME_line
695         A line consists of a (possible empty) string of non-'\n'
696         characters. A separator is exactly one '\n'.
697     NAME_sentence
698         A sentence separator is a seqence [.!?], followed by a non-empty
699         sequence of blank characters.  The sentence separator is a sequence
700         of any character except for the sentence separator sequence.
701     NAME_paragraph
702 	A paragraph separator is sequence of blank lines.
703     NAME_term
704         A term if either:
705 	    a) a series of alnum characters
706 	    b) a quoted string
707 	    c) a string with matching brackets at its start end end
708 	    d) case a), immediately followed by case c).
709 
710     Negative amount scans backwards.  The returned index is in the range
711     [0, size).
712 */
713 
714 
715 static status
ends_sentence(TextBuffer tb,intptr_t here)716 ends_sentence(TextBuffer tb, intptr_t here)
717 { return matchRegex(tb->syntax->sentence_end, tb, toInt(here), DEFAULT);
718 }
719 
720 
721 status
parsep_line_textbuffer(TextBuffer tb,intptr_t here)722 parsep_line_textbuffer(TextBuffer tb, intptr_t here)
723 { int rval = matchRegex(tb->syntax->paragraph_end, tb, toInt(here), DEFAULT);
724 
725   DEBUG(NAME_paragraph,
726 	Cprintf("parsep_line_textbuffer(%s, %d) --> %s\n",
727 		pp(tb), here, rval ? "yes" : "no"));
728 
729   return rval;
730 }
731 
732 
733 static int
all_layout(TextBuffer tb,intptr_t from,intptr_t to)734 all_layout(TextBuffer tb, intptr_t from, intptr_t to)
735 { SyntaxTable syntax = tb->syntax;
736 
737   while( from < to && tislayout(syntax, fetch(from)) )
738     from++;
739 
740   return from == to;
741 }
742 
743 
744 static intptr_t
forward_skip_par_textbuffer(TextBuffer tb,intptr_t here)745 forward_skip_par_textbuffer(TextBuffer tb, intptr_t here)
746 { intptr_t size = tb->size;
747 
748   while( here < size && parsep_line_textbuffer(tb, here) )
749   { intptr_t h = scan_textbuffer(tb, here, NAME_line, 1, 'a');
750     if ( !all_layout(tb, here, h) )
751       return h;
752     here = h;
753   }
754   while( here < size && !parsep_line_textbuffer(tb, here) )
755     here = scan_textbuffer(tb, here, NAME_line, 1, 'a');
756 
757   return here;
758 }
759 
760 
761 static intptr_t
backward_skip_par_textbuffer(TextBuffer tb,intptr_t here)762 backward_skip_par_textbuffer(TextBuffer tb, intptr_t here)
763 { here = scan_textbuffer(tb, here, NAME_line, -1, 'a');
764 
765   while( here > 0 && parsep_line_textbuffer(tb, here) )
766   { intptr_t h = scan_textbuffer(tb, here, NAME_line, -1, 'a');
767     if ( !all_layout(tb, h, here) )
768       return h;
769     here = h;
770   }
771   while( here > 0 && !parsep_line_textbuffer(tb, here) )
772     here = scan_textbuffer(tb, here, NAME_line, -1, 'a');
773 
774   return here;
775 }
776 
777 
778 intptr_t
scan_textbuffer(TextBuffer tb,intptr_t from,Name unit,intptr_t amount,int az)779 scan_textbuffer(TextBuffer tb, intptr_t from, Name unit, intptr_t amount, int az)
780 { intptr_t here;
781   intptr_t size = tb->size;
782   SyntaxTable syntax = tb->syntax;
783 
784   DEBUG(NAME_scan, Cprintf("scan_textbuffer(%s, %d, %s, %d, %c)\n",
785 			   pp(tb), from, pp(unit), amount, az));
786 
787   here = from;
788 
789   if ( unit == NAME_character )
790   { here = from + amount;	/* 'az' does not count (no separator) */
791     return NormaliseIndex(tb, here);
792   } else if ( unit == NAME_word )
793   { if ( az == 'a' )
794     { if ( amount <= 0 )
795       { for( ; here > 0 && amount <= 0; amount++ )
796 	{ while( !tisalnum(syntax, fetch(here)) && here > 0 ) here--;
797 	  while( tisalnum(syntax, fetch(here)) && here > 0 ) here--;
798 	}
799 	return (here == 0 ? here : here+1);
800       } else
801       { for( ; here < size && amount > 0; amount-- )
802 	{ while( tisalnum(syntax, fetch(here)) && here < size ) here++;
803 	  while( !tisalnum(syntax, fetch(here)) && here < size ) here++;
804 	}
805 	return here;
806       }
807     } else	/* 'z' mode */
808     { if ( amount >= 0 )
809       { for( ; here < size && amount >= 0; amount-- )
810 	{ while( !tisalnum(syntax, fetch(here)) && here < size ) here++;
811 	  while( tisalnum(syntax, fetch(here)) && here < size ) here++;
812 	}
813 	return here;
814       } else
815       { for( ; here > 0 && amount < 0; amount-- )
816 	{ while( tisalnum(syntax, fetch(here)) && here > 0 ) here--;
817 	  while( !tisalnum(syntax, fetch(here)) && here > 0 ) here--;
818 	}
819 	return here == 0 ? here : here+1;
820       }
821     }
822   } else if ( unit == NAME_line )
823   { if ( az == 'a' )		/* return first char of line */
824     { if ( amount <= 0 )
825       { for( ; here >= 0 && amount <= 0; amount++ )
826 	{ if ( tisendsline(syntax, fetch(here)) )
827 	    here--;
828 	  while( here >= 0 && !tisendsline(syntax, fetch(here)) )
829 	    here--;
830 	}
831 	return (here < 0 ? 0 : here + 1);
832       } else
833       { for( ; here <= size && amount > 0; amount--)
834 	{ while( here <= size && !tisendsline(syntax, fetch(here)) )
835 	    here++;
836 	  here++;
837 	}
838 	return (here >= size ? size : here);
839       }
840     } else /* 'z' case */
841     { if ( amount >= 0 )
842       { for( ; ; amount-- )
843 	{ while( here <= size && !tisendsline(syntax, fetch(here)) )
844 	    here++;
845 	  if ( here >= size || amount == 0 )
846 	    return here > size ? size : here;
847 	  here++;
848 	}
849       } else
850       { for( ; ; amount++ )
851 	{ while( here > 0 && !tisendsline(syntax, fetch(here)) )
852 	    here--;
853 	  if ( here <= 0 || amount == 0 )
854 	    return here < 0 ? 0 : here;
855 	  here--;
856 	}
857       }
858     }
859   } else if ( unit == NAME_sentence )
860   { if ( az == 'z' )
861     { if ( amount >= 0 )
862       { for( ; here < size && amount >= 0; amount-- )
863 	{ here++;
864 	  while( here < size && !ends_sentence(tb, here) ) here++;
865 	}
866 	return here;
867       } else
868       { while(here > 0 && !ends_sentence(tb, here) ) here--;
869 	for( ; here > 0 && amount < 0; amount++ )
870 	{ here--;
871 	  while(here > 0 && !ends_sentence(tb, here) ) here--;
872 	}
873 	return here;
874       }
875     } else		/* 'a' case */
876     { if ( amount <= 0 )
877       { for( ; here > 0 && amount <= 0; amount++ )
878 	{ here--;
879 	  while(here > 0 && tislayout(syntax, fetch(here)) ) here--;
880 	  while(here > 0 && !ends_sentence(tb, here) ) here--;
881 	}
882 	while( here < size && tislayout(syntax, fetch(here)) ) here++;
883 	return here;
884       } else
885       { for( ; here < size && amount > 0; amount-- )
886 	{ here++;
887 	  while( here < size && !ends_sentence(tb, here) ) here++;
888 	}
889 	while( here < size && tislayout(syntax, fetch(here)) ) here++;
890 	return here;
891       }
892     }
893   } else if ( unit == NAME_paragraph )
894   { if ( az == 'z' )
895     { if ( amount >= 0 )
896       { here = scan_textbuffer(tb, here, NAME_line, 0, 'a');
897 	for( ; here < size && amount >= 0; amount-- )
898 	  here = forward_skip_par_textbuffer(tb, here);
899 	return here;
900       } else
901       { for( ; here > 0 && amount < 0; amount++ )
902 	{ here--;
903 	  here = backward_skip_par_textbuffer(tb, here);
904 	}
905 	return here;
906       }
907     } else		/* the 'a' case */
908     { if ( amount > 0 )
909       { for( ; here < size && amount > 0; amount-- )
910 	  here = forward_skip_par_textbuffer(tb, here);
911 	return here;
912       } else
913       { for( ; here > 0 && amount <= 0; amount++ )
914 	{ here = backward_skip_par_textbuffer(tb, here);
915 	}
916 	if ( parsep_line_textbuffer(tb, here) )
917 	  here = scan_textbuffer(tb, here, NAME_line, 1, 'a');
918 	return here;
919       }
920     }
921   } else if ( unit == NAME_term )
922   { if ( amount > 0 )	/* forwards */
923     { for( ; here < size && amount > 0; amount-- )
924       { while( here < size && !tischtype(syntax, fetch(here), AN|OB|QT) )
925 	  here++;
926 	if ( amount == 1 && az == 'a' )
927 	  return here;
928 	while( here < size && tisalnum(syntax, fetch(here)) )
929 	  here++;
930 	if ( tisquote(syntax, fetch(here)) )
931 	{ Int h = getMatchingQuoteTextBuffer(tb, toInt(here), NAME_forward);
932 	  if ( h == FAIL ) return here;
933 	  here = valInt(h) + 1;
934 	} else if ( tisopenbrace(syntax, fetch(here)) )
935 	{ Int h = getMatchingBracketTextBuffer(tb, toInt(here), DEFAULT);
936 	  if ( h == FAIL ) return here;
937 	  here = valInt(h) + 1;
938 	}
939       }
940       return here;
941     } else if ( amount < 0 )	/* backwards */
942     { for( ; here > 0 && amount < 0; amount++ )
943       { here--;
944 	while( here > 0 && !tischtype(syntax, fetch(here), AN|CB|QT) )
945 	  here--;
946 	if ( amount == -1 && az == 'z' )
947 	  return here+1;
948 	if ( tisquote(syntax, fetch(here)) )
949 	{ Int h = getMatchingQuoteTextBuffer(tb, toInt(here), NAME_backward);
950 	  if ( h == FAIL ) return here;
951 	  here = valInt(h);
952 	} else if ( tisclosebrace(syntax, fetch(here)) )
953 	{ Int h = getMatchingBracketTextBuffer(tb, toInt(here), DEFAULT);
954 	  if ( h == FAIL ) return here;
955 	  here = valInt(h);
956 	}
957 	if ( tisalnum(syntax, fetch(here-1)) )
958 	  while( here > 0 && tisalnum(syntax, fetch(here-1)) )
959 	    here--;
960       }
961       return here;
962     }
963     return here;
964   } else
965     fail;
966 }
967 
968 
969 static Int
getMatchingQuoteTextBuffer(TextBuffer tb,Int idx,Name direction)970 getMatchingQuoteTextBuffer(TextBuffer tb, Int idx, Name direction)
971 { intptr_t i = valInt(idx);
972   int c = fetch(i);
973   SyntaxTable syntax = tb->syntax;
974 
975   if ( !tisquote(tb->syntax, c) )	/* must start on quote */
976     fail;
977 
978   if ( direction == NAME_forward )
979   { intptr_t i0 = i;
980     int quoteisescape = tisstringescape(syntax, c, c);
981 
982     for(i++; i<tb->size; i++)
983     { int c2 = fetch(i);
984 
985       if ( c2 == c )
986       { if ( quoteisescape && i+1 < tb->size && fetch(i+1) == c )
987 	{ i++;				/* Prolog 'Can''t' syntax! */
988 	  continue;
989 	}
990 	if ( i-1 > i0 &&
991 	     (c2=fetch(i-1)) != c &&
992 	     tisstringescape(syntax, c, c2) )
993 	  continue;
994 
995 	answer(toInt(i));
996       }
997     }
998   } else /* if ( direction == NAME_backward ) */
999   { for(i--; i>=0; i--)
1000     { int c2 = fetch(i);
1001 
1002       if ( c2 == c )
1003       { if ( i == 0 )
1004 	  answer(toInt(i));
1005 	if ( tisstringescape(syntax, c, fetch(i-1)) )
1006 	{ if ( tisstringescape(syntax, c, c) )
1007 	    i--;			/* Prolog 'Can''t' syntax! */
1008 	} else
1009 	  answer(toInt(i));
1010       }
1011     }
1012   }
1013 
1014   fail;
1015 }
1016 
1017 
1018 		 /*******************************
1019 		 *        SYNTAX HANDLING	*
1020 		 *******************************/
1021 
1022 #define SST_PLAIN	0x0100		/* syntax-state */
1023 #define SST_COMMENT1	0x0200		/* 1-character comment-string */
1024 #define SST_COMMENT2	0x0400		/* 2-character comment-string */
1025 #define SST_STRING	0x0800		/* string (low-order is start) */
1026 #define SST_QQ		0x1000		/* Quasi quotation */
1027 
1028 static int
match_qq(TextBuffer tb,intptr_t here,const char * s)1029 match_qq(TextBuffer tb, intptr_t here, const char *s)
1030 { for(; *s; s++)
1031   { if ( fetch(here) != *s )
1032       fail;
1033   }
1034 
1035   succeed;
1036 }
1037 
1038 
1039 static int
scan_syntax_textbuffer(TextBuffer tb,intptr_t from,intptr_t to,int flags,intptr_t * start)1040 scan_syntax_textbuffer(TextBuffer tb,
1041 		       intptr_t from, intptr_t to,
1042 		       int flags,
1043 		       intptr_t *start)
1044 { intptr_t here = from;			/* current position */
1045   SyntaxTable syntax = tb->syntax;	/* syntax-table */
1046   int state = SST_PLAIN;		/* initial/current state */
1047   intptr_t tokenstart = from;
1048   char *qq_start = NULL;
1049 
1050   if ( notNil(syntax->qq_start) )
1051     qq_start = strName(syntax->qq_start);
1052 
1053   for(; here < to; here++)
1054   { int c = fetch(here);
1055 
1056 					/* strings */
1057     if ( tisquote(syntax, c) )
1058     { int quoteisescape = tisstringescape(syntax, c, c);
1059 
1060 					/* Prolog 0'char syntax */
1061       if ( c == '\'' && syntax->prolog == ON && here > 0 )
1062       { wint_t c0 = fetch(here-1);
1063 
1064 	if ( iswdigit(c0) )		/* or <digit><number> */
1065 	{ if ( c0 == '0' )
1066 	  { here++;			/* ignore this char */
1067 	    if ( here >= to )
1068 	    { tokenstart = here-2;
1069 	      state = SST_STRING;
1070 	      break;
1071 	    }
1072 	  }
1073 	  continue;
1074 	}
1075       }
1076 
1077       state = SST_STRING|c;
1078       tokenstart = here;
1079 
1080       for(here++; here<to; here++)
1081       { int c2 = fetch(here);
1082 
1083 	if ( c2 == c )
1084 	{ if ( quoteisescape && here+1 < tb->size && fetch(here+1) == c )
1085 	  { here++;
1086 	    continue;
1087 	  }
1088 	  if ( here-1 > tokenstart &&
1089 	       (c2=fetch(here-1)) != c &&
1090 	       tisstringescape(syntax, c, c2) )
1091 	    continue;
1092 
1093 	  state = SST_PLAIN;
1094 	  goto cont;
1095 	} else if ( c2 == '\\' )
1096 	  here++;				/* skip next */
1097       }
1098     } else if ( tiscommentstart(syntax, c) ) /* COMMENT (1) */
1099     { tokenstart = here;
1100 
1101       for(here++ ; here < to; here++ )
1102       { int c = fetch(here);
1103 
1104 	if ( tiscommentend(syntax, c) )
1105 	  goto cont;
1106       }
1107 
1108       state = SST_COMMENT1;
1109       break;
1110     } else if ( tiscommentstart1(syntax, c) &&
1111 		tiscommentstart2(syntax, fetch(here+1)) )
1112     { tokenstart = here;
1113 
1114       for( here += 4; here < to; here++ )
1115       { int c = fetch(here - 1);
1116 
1117 	if ( tiscommentend2(syntax, c) )
1118 	{ c = fetch(here - 2);
1119 	  if ( tiscommentend1(syntax, c) )
1120 	    goto cont;
1121 	}
1122       }
1123 
1124       state = SST_COMMENT2;
1125       break;
1126     } else if ( qq_start && c == qq_start[0] &&
1127 		match_qq(tb, here+1, qq_start+1) )
1128     { char *qq_end;
1129 
1130       if ( notNil(syntax->qq_end) )
1131 	qq_end = strName(syntax->qq_end);
1132       else
1133 	goto cont;			/* no end.  Warning? */
1134 
1135       tokenstart = here;
1136 
1137       for(here += strlen(qq_start); here < to; here++)
1138       { int c = fetch(here);
1139 
1140 	if ( c == qq_end[0] && match_qq(tb, here+1, qq_end+1) )
1141 	{ here += strlen(qq_end);
1142 	  goto cont;
1143 	}
1144       }
1145 
1146       state = SST_QQ;
1147       break;
1148     }
1149 
1150   cont:
1151     ;
1152   }
1153 
1154   if ( start )
1155     *start = tokenstart;
1156 
1157   return state;
1158 }
1159 
1160 
1161 static Tuple
getScanSyntaxTextBuffer(TextBuffer tb,Int f,Int t)1162 getScanSyntaxTextBuffer(TextBuffer tb, Int f, Int t)
1163 { intptr_t from = NormaliseIndex(tb, valInt(f));
1164   intptr_t to   = NormaliseIndex(tb, valInt(t));
1165   intptr_t start;
1166   Name class;
1167   int s;
1168 
1169   if ( to == tb->size )
1170     to--;
1171 
1172   s = scan_syntax_textbuffer(tb, from, to, 0, &start);
1173   switch(s&0xff00)
1174   { case SST_PLAIN:
1175       class = NAME_code;
1176       break;
1177     case SST_COMMENT1:
1178     case SST_COMMENT2:
1179       class = NAME_comment;
1180       break;
1181     case SST_STRING:
1182       class = NAME_string;
1183       break;
1184     case SST_QQ:
1185       class = NAME_quasiQuotation;
1186       break;
1187     default:
1188       assert(0);
1189       fail;
1190   }
1191 
1192   answer(answerObject(ClassTuple, class, toInt(start), EAV));
1193 }
1194 
1195 
1196 static status
inStringTextBuffer(TextBuffer tb,Int pos,Int from)1197 inStringTextBuffer(TextBuffer tb, Int pos, Int from)
1198 { intptr_t idx = valInt(pos);
1199   intptr_t here = (isDefault(from) ? 0L : valInt(from));
1200   SyntaxTable syntax = tb->syntax;
1201 
1202   for( ; here <= idx; here++)
1203   { int c = fetch(here);
1204 
1205     if ( tisquote(syntax, c) )
1206     { Int match;
1207 
1208       DEBUG(NAME_inString, Cprintf("here = %ld (idx = %ld)\n", here, idx));
1209 
1210 					/* Prolog 0'char syntax */
1211       if ( c == '\'' && syntax->name == NAME_prolog && here > 0 )
1212       { wint_t c0 = fetch(here-1);
1213 
1214 	if ( iswdigit(c0) )
1215 	{ if ( c0 == '0' && idx == here+1 )
1216 	    succeed;
1217 	  continue;
1218 	}
1219       }
1220 
1221       if ( (match = getMatchingQuoteTextBuffer(tb, toInt(here), NAME_forward)))
1222       { DEBUG(NAME_inString, Cprintf("Matching: %ld\n", valInt(match)));
1223 
1224 	if ( (here = valInt(match)) >= idx )
1225 	  succeed;
1226       } else
1227 	succeed;
1228     }
1229   }
1230 
1231   fail;
1232 }
1233 
1234 #define MAXBRACKETS 1000		/* ??? */
1235 
1236 Int
getMatchingBracketTextBuffer(TextBuffer tb,Int idx,Int bracket)1237 getMatchingBracketTextBuffer(TextBuffer tb, Int idx, Int bracket)
1238 { intptr_t i = valInt(idx);
1239   wint_t stack[MAXBRACKETS];
1240   int depth = 1;
1241   int ic;
1242   SyntaxTable syntax = tb->syntax;
1243   int c;
1244 
1245   c = (notDefault(bracket) ? (int)valInt(bracket) : fetch(i));
1246   stack[0] = c;
1247 
1248   if ( tisopenbrace(syntax, c) )
1249     ic = 1;
1250   else if ( tisclosebrace(syntax, c) )
1251     ic = -1;
1252   else
1253     fail;
1254 
1255   for( i += ic; i >= 0 && i < tb->size; i += ic )
1256   { c = fetch(i);
1257 
1258     if ( tisopenbrace(syntax, c) )
1259     { if ( ic > 0 )
1260 	stack[depth] = c;
1261       depth += ic;
1262       if ( ic < 0 && !tismatching(syntax, c, stack[depth]) )
1263       { errorPce(tb, NAME_mismatchedBracket);
1264 	fail;
1265       }
1266     } else if ( tisclosebrace(syntax, c) )
1267     { if ( ic < 0 )
1268 	stack[depth] = c;
1269       depth -= ic;
1270       if ( ic > 0 && !tismatching(syntax, c, stack[depth]) )
1271       { errorPce(tb, NAME_mismatchedBracket);
1272 	fail;
1273       }
1274     } else if ( tisquote(syntax, c) )
1275     { Int mb = getMatchingQuoteTextBuffer(tb, toInt(i),
1276 					  ic > 0 ? NAME_forward
1277 					         : NAME_backward);
1278       if ( mb )
1279 	i = valInt(mb);
1280       else
1281 	fail;
1282     }
1283 
1284     if ( depth == 0 )
1285       answer(toInt(i));
1286   }
1287 
1288   fail;
1289 }
1290 
1291 
1292 Int
getSkipBlanksTextBuffer(TextBuffer tb,Int where,Name direction,BoolObj skipnl)1293 getSkipBlanksTextBuffer(TextBuffer tb, Int where, Name direction, BoolObj skipnl)
1294 { intptr_t pos = valInt(where);
1295   intptr_t size = tb->size;
1296 
1297   pos = NormaliseIndex(tb, pos);
1298 
1299   if ( isDefault(skipnl) )
1300     skipnl = ON;
1301   if ( isDefault(direction) )
1302     direction = NAME_forward;
1303 
1304   if ( direction == NAME_forward )
1305   { if ( skipnl == OFF )
1306     { while( pos < size && tisblank(tb->syntax, fetch(pos)) )
1307         pos++;
1308     } else
1309     { while( pos < size && tislayout(tb->syntax, fetch(pos)) )
1310 	pos++;
1311     }
1312   } else
1313   { if ( skipnl == OFF )
1314     { while( pos > 0 && tisblank(tb->syntax, fetch(pos-1)) )
1315         pos--;
1316     } else
1317     { while( pos > 0 && tislayout(tb->syntax, fetch(pos-1)) )
1318         pos--;
1319     }
1320   }
1321 
1322   return toInt(pos);
1323 }
1324 
1325 
1326 static Int
getSkipCommentTextBuffer(TextBuffer tb,Int where,Int to,BoolObj layouttoo)1327 getSkipCommentTextBuffer(TextBuffer tb, Int where, Int to, BoolObj layouttoo)
1328 { intptr_t pos = valInt(where);
1329   intptr_t end = (isDefault(to) ? tb->size : valInt(to));
1330   int fwd = (end >= pos);
1331 
1332   pos = NormaliseIndex(tb, pos);
1333   end = NormaliseIndex(tb, end);
1334 
1335   if ( fwd )				/* forward */
1336   { for(;;)
1337     { if ( pos < 0 )
1338 	answer(toInt(tb->size));
1339 
1340       if ( layouttoo != OFF )
1341       { for( ; pos < end && tislayout(tb->syntax, fetch(pos)); pos++ )
1342 	  ;
1343       }
1344 
1345       if ( tiscommentstart(tb->syntax, fetch(pos)) )
1346       { for( ; pos < end; pos++ )
1347 	{ int c = fetch(pos);
1348 
1349 	  if ( tiscommentend(tb->syntax, c) )
1350 	    break;
1351 	}
1352 	continue;
1353       }
1354 
1355       if ( tiscommentstart1(tb->syntax, fetch(pos)) &&
1356 	   tiscommentstart2(tb->syntax, fetch(pos+1)) )
1357       { for( pos += 4; pos < end; pos++ )
1358 	{ int c = fetch(pos - 1);
1359 
1360 	  if ( tiscommentend2(tb->syntax, c) )
1361 	  { c = fetch(pos - 2);
1362 	    if ( tiscommentend1(tb->syntax, c) )
1363 	      break;
1364 	  }
1365 	}
1366 
1367 	continue;
1368       }
1369       break;
1370     }
1371   } else				/* backwards */
1372   { for(;;)
1373     { wint_t c;
1374 
1375     again:
1376 
1377       if ( pos < end )
1378 	break;
1379 
1380       if ( layouttoo != OFF )
1381       {	for(; pos >= end && tislayout(tb->syntax, c=fetch(pos))
1382 	                 && !tiscommentend(tb->syntax, c)
1383 	    ; pos-- )
1384 	  ;
1385       }
1386 
1387       if ( tiscommentend(tb->syntax, c=fetch(pos)) )
1388       { intptr_t possave = pos;
1389 
1390 	for( pos--;
1391 	     pos >= end && !tiscommentstart(tb->syntax, c=fetch(pos));
1392 	     pos-- )
1393 	{ if ( tiscommentend(tb->syntax, c) )
1394 	  { pos = possave;
1395 	    if ( tislayout(tb->syntax, fetch(pos)) )
1396 	    { pos--;
1397 	      goto again;
1398 	    } else
1399 	      goto next;
1400 	  }
1401 	}
1402         if ( pos < end )
1403 	{ pos = possave;
1404 	  if ( tislayout(tb->syntax, fetch(pos)) )
1405 	  { pos--;
1406 	    goto again;
1407 	  } else
1408 	    goto next;
1409 	}
1410 	pos--;
1411 	continue;
1412       }
1413 
1414       next:
1415       if ( tiscommentend2(tb->syntax, fetch(pos)) &&
1416 	   tiscommentend1(tb->syntax, fetch(pos-1)) )
1417       { for( pos -= 4;
1418 	     pos >= end && !(tiscommentstart1(tb->syntax, fetch(pos+1)) &&
1419 		             tiscommentstart2(tb->syntax, fetch(pos+2)));
1420 	     pos-- )
1421 	  ;
1422 	continue;
1423       }
1424       break;
1425     }
1426   }
1427 
1428   if ( (fwd && pos > end) || (!fwd && pos < end) )
1429     pos = end;
1430 
1431   answer(toInt(NormaliseIndex(tb, pos)));
1432 }
1433 
1434 
1435 static status
inCommentTextBuffer(TextBuffer tb,Int pos,Int from)1436 inCommentTextBuffer(TextBuffer tb, Int pos, Int from)
1437 { int idx = valInt(pos);
1438   int here = (isDefault(from) ? 0 : valInt(from));
1439   SyntaxTable syntax = tb->syntax;
1440 
1441   while(here <= idx)
1442   { int c = fetch(here);
1443 
1444     if ( tisquote(syntax, c) )
1445     { Int h = getMatchingQuoteTextBuffer(tb, toInt(here), NAME_forward);
1446 
1447       if ( !h )
1448 	succeed;
1449       here = valInt(h) + 1;
1450       continue;
1451     }
1452 
1453     if ( tiscommentstart(syntax, c) ||
1454 	 (tiscommentstart1(syntax, c) &&
1455 	  tiscommentstart2(syntax, fetch(here+1))) )
1456     { here = valInt(getSkipCommentTextBuffer(tb, toInt(here), DEFAULT, OFF));
1457       if ( here >= idx )
1458 	succeed;
1459     }
1460 
1461     here++;
1462   }
1463 
1464   fail;
1465 }
1466 
1467 
1468 static status
forAllCommentsTextBuffer(TextBuffer tb,Code msg,Int from,Int to)1469 forAllCommentsTextBuffer(TextBuffer tb, Code msg, Int from, Int to)
1470 { int here = (isDefault(from) ? 0 : valInt(from));
1471   int end  = (isDefault(to)   ? tb->size : valInt(to));
1472   SyntaxTable syntax = tb->syntax;
1473 
1474   if ( here < 0 )			/* normalise the indices */
1475     here = 0;
1476   if ( end > tb->size )
1477     end = tb->size;
1478 
1479   while(here < end)
1480   { int c = fetch(here);
1481 
1482     if ( tisquote(syntax, c) )
1483     { Int h = getMatchingQuoteTextBuffer(tb, toInt(here), NAME_forward);
1484 
1485       if ( !h )
1486 	succeed;
1487       here = valInt(h) + 1;
1488       continue;
1489     }
1490 
1491     if ( tiscommentstart(syntax, c) ||
1492 	 (tiscommentstart1(syntax, c) &&
1493 	  tiscommentstart2(syntax, fetch(here+1))) )
1494     { int endc = valInt(getSkipCommentTextBuffer(tb, toInt(here),
1495 						 DEFAULT, OFF));
1496 
1497       forwardReceiverCode(msg, tb, toInt(here), toInt(endc), EAV);
1498 
1499       here = endc;
1500     }
1501 
1502     here++;
1503   }
1504 
1505   succeed;
1506 }
1507 
1508 
1509 Int
getLineNumberTextBuffer(TextBuffer tb,Int i)1510 getLineNumberTextBuffer(TextBuffer tb, Int i)
1511 { int e = (isDefault(i) ? tb->size : valInt(i));
1512 
1513   answer(toInt(count_lines_textbuffer(tb, 0, e) + 1));
1514 }
1515 
1516 
1517 Int
getCountLinesTextBuffer(TextBuffer tb,Int from,Int to)1518 getCountLinesTextBuffer(TextBuffer tb, Int from, Int to)
1519 { intptr_t f = (isDefault(from) ? 0        : valInt(from));
1520   intptr_t t = (isDefault(to)   ? tb->size : valInt(to));
1521 
1522   answer(toInt(count_lines_textbuffer(tb, f, t)));
1523 }
1524 
1525 
1526 intptr_t
find_textbuffer(TextBuffer tb,intptr_t here,PceString str,intptr_t times,char az,int ec,int wm)1527 find_textbuffer(TextBuffer tb, intptr_t here, PceString str,
1528 		intptr_t times, char az, int ec, int wm)
1529 { int hit = FALSE;
1530   int where = here;
1531 
1532   if ( times < 0 )
1533   { for( ; here >= 0 && times < 0; times++ )
1534     { for( ; here >= 0; here-- )
1535       { if ( match_textbuffer(tb, here, str, ec, wm) )
1536 	{ hit = TRUE;
1537 	  where = here;
1538 	  break;
1539 	}
1540       }
1541     }
1542   } else if ( times > 0 )
1543   { int size = tb->size;
1544 
1545     for( ; here < size && times > 0; times-- )
1546     { for( ; here < size; here++ )
1547       { if ( match_textbuffer(tb, here, str, ec, wm) )
1548         { hit = TRUE;
1549           where = here;
1550           break;
1551 	}
1552       }
1553     }
1554   } else
1555     return here;
1556 
1557   return hit ? (az == 'a' ? where : where + str->s_size) : -1;
1558 }
1559 
1560 
1561 int
match_textbuffer(TextBuffer tb,intptr_t here,PceString s,int ec,int wm)1562 match_textbuffer(TextBuffer tb, intptr_t here, PceString s, int ec, int wm)
1563 { intptr_t l = s->s_size;
1564   intptr_t i;
1565 
1566   if ( wm && (tisalnum(tb->syntax, fetch(here-1)) ||
1567 	      tisalnum(tb->syntax, fetch(here+l))) )
1568     return FALSE;
1569 
1570   if ( ec )
1571   { for( i=0; i < l; i++ )
1572     { if ( fetch(here++) != (int)str_fetch(s, i) )
1573 	return FALSE;
1574     }
1575   } else
1576   { for( i=0; i < l; i++ )
1577     { wint_t c1 = fetch(here++);
1578       wint_t c2 = str_fetch(s, i);
1579 
1580       if ( tolower(c1) != tolower(c2) )
1581 	return FALSE;
1582     }
1583   }
1584 
1585   return TRUE;
1586 }
1587 
1588 
1589 		/********************************
1590 		*            FILLING		*
1591 		********************************/
1592 
1593 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1594 Fill a line.  The text starts at `from' (possibly  holding  blanks)  the
1595 first  non-blank  character  is at column sc and rm is the right margin.
1596 The return value  is  the  start  position  of  the  next  line  in  the
1597 textbuffer.  Characters after to are always left untouched.
1598 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
1599 
1600 #define MAX_WORDS 1000
1601 
1602 static void
distribute_spaces(TextBuffer tb,int spaces,int nbreaks,intptr_t * breaks)1603 distribute_spaces(TextBuffer tb, int spaces, int nbreaks, intptr_t *breaks)
1604 { int s = (nbreaks > 1 ? (spaces / (nbreaks-1)) : 1);
1605   int n, m;
1606   int *extra = (int *)alloca(nbreaks * sizeof(int));
1607   PceString space = str_spc(&tb->buffer);
1608 
1609   DEBUG(NAME_justify, Cprintf("%d spaces (each %d)\n", spaces, s));
1610 
1611   for(n=0; n < nbreaks-1; n++)		/* give them equal size */
1612     extra[n] = s;
1613   extra[n] = 0;				/* not in last (is the newline) */
1614 					/* distribute from the center */
1615   spaces -= s*(nbreaks-1);
1616   for(m = nbreaks / 2, n = 0; spaces > 0; n++, spaces--)
1617   { int i = m + (n%2 == 0 ? n/2 : -(n/2));
1618 
1619     if ( i >= nbreaks-1 ) i = nbreaks - 2;
1620     if ( i < 0 ) i = 0;
1621     extra[i]++;
1622     DEBUG(NAME_justify, Cprintf("\tadding one at break %d\n", i));
1623   }
1624 
1625   for(n=0, m=0; n<nbreaks; n++)
1626   { breaks[n] += m;
1627     if ( extra[n] )
1628     { insert_textbuffer(tb, breaks[n], extra[n], space);
1629       m += extra[n];
1630     }
1631   }
1632 }
1633 
1634 
1635 intptr_t
fill_line_textbuffer(TextBuffer tb,intptr_t here,intptr_t to,int sc,int rm,int justify)1636 fill_line_textbuffer(TextBuffer tb, intptr_t here, intptr_t to,
1637 		     int sc, int rm, int justify)
1638 { int col = sc;
1639   intptr_t breaks[MAX_WORDS];
1640   int nbreaks = 0;
1641   int last_break_col = 0;
1642   intptr_t i;
1643   PceString nl = str_nl(&tb->buffer);
1644   PceString space = str_spc(&tb->buffer);
1645 
1646   DEBUG(NAME_fill, Cprintf("fill_line(tb, %ld, %ld, %d, %d)\n",
1647 			   here, to, sc, rm));
1648 
1649 					/* delete leading white space */
1650   for( i = here; i < to && tislayout(tb->syntax, fetch(i)); i++ )
1651     ;
1652   if ( i-here > 0 )
1653   { delete_textbuffer(tb, here, i-here);
1654     to -= i-here;
1655     DEBUG(NAME_fill, Cprintf("deleted %ld leading blanks\n", i-here));
1656   }
1657 
1658 
1659   for(;;)
1660   {					/* copy string of non-blanks */
1661     for( ; here < to && !tislayout(tb->syntax, fetch(here)); here++ )
1662       col++;
1663     DEBUG(NAME_fill,
1664 	  Cprintf("Word to %ld; col = %d; here-1 = %c, here = %d, to=%ld\n",
1665 		  here, col, fetch(here-1), fetch(here), to));
1666 
1667     if ( col > rm )			/* trapped margin */
1668     { if ( nbreaks > 0 )
1669       { store_textbuffer(tb, breaks[nbreaks-1], '\n');
1670 	if ( justify && last_break_col < rm )
1671 	  distribute_spaces(tb, rm - last_break_col, nbreaks, breaks);
1672         return breaks[nbreaks-1] + 1;
1673       } else
1674       { if ( here == to )		/* end of buffer: add a newline */
1675 	  insert_textbuffer(tb, here, 1, nl);
1676 	else
1677           store_textbuffer(tb, here, '\n');
1678 	return here+1;
1679       }
1680     }
1681 
1682     if ( here >= to )
1683       return here;
1684 
1685     breaks[nbreaks] = here;
1686     if ( nbreaks < MAX_WORDS-1 )
1687       nbreaks++;			/* avoid crash */
1688     last_break_col = col;
1689     if ( fetch(here) != ' ' )
1690       store_textbuffer(tb, here, ' ');
1691     here++, col++;
1692     if ( ends_sentence(tb, here-2) )	/* sentence: one more space */
1693     { DEBUG(NAME_fill, Cprintf("End-sentence at %ld\n", here-2));
1694       if ( fetch(here) != ' ' )
1695       { insert_textbuffer(tb, here, 1, space);
1696 	to++;
1697       }
1698       here++; col++;
1699     }
1700 
1701     for( i = here; i < to && tislayout(tb->syntax, fetch(i)); i++ )
1702       ;
1703     if ( i-here > 0 )
1704     { delete_textbuffer(tb, here, i-here);
1705       to -= i-here;
1706       DEBUG(NAME_fill, Cprintf("deleted %ld blanks\n", i-here));
1707     }
1708 
1709     if ( here >= to )
1710       return here;
1711   }
1712 }
1713 
1714 		/********************************
1715 		*            SORTING		*
1716 		********************************/
1717 
1718 static int
compare_lines(const void * s,const void * t)1719 compare_lines(const void *s, const void *t)
1720 { return strcmp(*((char **)s), *((char **)t));
1721 }
1722 
1723 
1724 status
sortTextBuffer(TextBuffer tb,Int from,Int to)1725 sortTextBuffer(TextBuffer tb, Int from, Int to)
1726 { int f, t, ln, i, n;
1727   char *buf, **lines;
1728   char *bp, **lp;
1729   int bufsize;
1730 
1731   if ( isDefault(from) )
1732     from = ZERO;
1733   if ( isDefault(to) )
1734     to = toInt(tb->size);
1735 
1736   from = getScanTextBuffer(tb, from, NAME_line, ZERO, NAME_start);
1737   to   = getScanTextBuffer(tb, to, NAME_line, ZERO, NAME_start);
1738   f = valInt(from);
1739   t = valInt(to);
1740   ln = count_lines_textbuffer(tb, f, t+1); /* <=t below */
1741 
1742   if ( ln > 1 )				/* TBD (16B) */
1743   { bufsize = t - f + 1;
1744     lines = alloc((ln+1) * sizeof(char *));
1745     buf   = alloc(bufsize);
1746 
1747     for(bp=buf, lp=lines, i=f, *lp++=bp; i <= t; i++, bp++)
1748     { *bp = fetch_textbuffer(tb, i);
1749 
1750       if ( tisendsline(tb->syntax, *bp) )
1751       { *bp = EOS;
1752 	*lp++ = bp+1;
1753       }
1754     }
1755 
1756     qsort(lines, ln, sizeof(char *), compare_lines);
1757 
1758     delete_textbuffer(tb, f, t-f);
1759     for(n=0; n<ln; n++)
1760     { PceString nl = str_nl(&tb->buffer);
1761       string s;
1762 
1763       str_set_ascii(&s, lines[n]);
1764       insert_textbuffer(tb, f, 1, &s);
1765       f += s.s_size;
1766       insert_textbuffer(tb, f, 1, nl);
1767       f++;
1768     }
1769 
1770     unalloc((ln+1) * sizeof(char *), lines);
1771     unalloc(bufsize, buf);
1772   }
1773 
1774   return changedTextBuffer(tb);
1775 }
1776 
1777 		 /*******************************
1778 		 *	    LINE COUNTS		*
1779 		 *******************************/
1780 
1781 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1782 Counting lines and finding lines.  This   is  in  some applications done
1783 quite often on long buffers and  therefore   we  have  written this at a
1784 rather low level.
1785 
1786 count_lines_textbuffer()       finds the number of newlines in a region.
1787 start_of_line_n_textbuffer()   finds the character index of the nth-1 line.
1788 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
1789 
1790 intptr_t
count_lines_textbuffer(TextBuffer tb,intptr_t f,intptr_t t)1791 count_lines_textbuffer(TextBuffer tb, intptr_t f, intptr_t t)
1792 { intptr_t lines = 0;
1793   SyntaxTable syntax = tb->syntax;
1794 
1795   f = NormaliseIndex(tb, f);
1796   t = NormaliseIndex(tb, t);
1797 
1798   if ( f == 0 && t == tb->size && tb->lines >= 0 )
1799     return tb->lines;			/* use the total count */
1800 
1801   if ( istbA(tb) )
1802   { charA *b = tb->tb_bufferA;
1803     int end1 = min(tb->gap_start, t);
1804 
1805     for( ; f<end1; f++)
1806     { if ( tisendsline(syntax, b[f]) )
1807       { lines++;
1808       }
1809     }
1810     b += tb->gap_end - tb->gap_start;
1811     for( ; f<t; f++)
1812     { if ( tisendsline(syntax, b[f]) )
1813       { lines++;
1814       }
1815     }
1816   } else
1817   { charW *b = tb->tb_bufferW;
1818     int end1 = min(tb->gap_start, t);
1819 
1820     for( ; f<end1; f++)
1821     { if ( tisendsline(syntax, b[f]) )
1822 	lines++;
1823     }
1824     b += tb->gap_end - tb->gap_start;
1825     for( ; f<t; f++)
1826     { if ( tisendsline(syntax, b[f]) )
1827 	lines++;
1828     }
1829   }
1830 
1831   return lines;
1832 }
1833 
1834 
1835 int
start_of_line_n_textbuffer(TextBuffer tb,int lineno)1836 start_of_line_n_textbuffer(TextBuffer tb, int lineno)
1837 { int i;
1838   SyntaxTable syntax = tb->syntax;
1839 
1840   if ( --lineno <= 0 )
1841     return 0;
1842 
1843   if ( istbA(tb) )
1844   { charA *b = tb->tb_bufferA;
1845 
1846     for(i=0 ; i<tb->gap_start; i++)
1847     { if ( tisendsline(syntax, b[i]) )
1848       { if ( --lineno <= 0 )
1849 	  return i+1;
1850       }
1851     }
1852     b += tb->gap_end - tb->gap_start;
1853     for( ; i<tb->size; i++)
1854     { if ( tisendsline(syntax, b[i]) )
1855       { if ( --lineno <= 0 )
1856 	  return i+1;
1857       }
1858     }
1859   } else
1860   { charW *b = tb->tb_bufferW;
1861 
1862     for(i=0 ; i<tb->gap_start; i++)
1863     { if ( tisendsline(syntax, b[i]) )
1864       { if ( --lineno <= 0 )
1865 	  return i+1;
1866       }
1867     }
1868     b += tb->gap_end - tb->gap_start;
1869     for( ; i<tb->size; i++)
1870     { if ( tisendsline(syntax, b[i]) )
1871       { if ( --lineno <= 0 )
1872 	  return i+1;
1873       }
1874     }
1875   }
1876 
1877   return tb->size;
1878 }
1879 
1880 		/********************************
1881 		*     PRIMITIVE OPERATIONS      *
1882 		*********************************/
1883 
1884 int
fetch_textbuffer(TextBuffer tb,intptr_t where)1885 fetch_textbuffer(TextBuffer tb, intptr_t where)
1886 { int idx;
1887 
1888   if ( where < 0 || where >= tb->size )
1889     return EOB;
1890   idx = Index(tb, where);
1891 
1892   return istbA(tb) ? (wint_t)tb->tb_bufferA[idx] : (wint_t)tb->tb_bufferW[idx];
1893 }
1894 
1895 
1896 static status
store_textbuffer(TextBuffer tb,intptr_t where,wint_t c)1897 store_textbuffer(TextBuffer tb, intptr_t where, wint_t c)
1898 { intptr_t idx;
1899   wint_t old;
1900 
1901   if ( where < 0 || where >= tb->size )
1902     fail;
1903   idx = Index(tb, where);
1904 
1905   if ( istbA(tb) && c > 0xff )
1906     promoteTextBuffer(tb);
1907 
1908   if ( istbA(tb) )
1909     old = tb->tb_bufferA[idx];
1910   else
1911     old = tb->tb_bufferW[idx];
1912 
1913   if ( old == c )
1914     succeed;
1915   if ( tisendsline(tb->syntax, old) )
1916     tb->lines--;
1917   if ( tisendsline(tb->syntax, c) )
1918     tb->lines++;
1919 
1920   start_change(tb, where);
1921   register_change_textbuffer(tb, where, 1);
1922 
1923   if ( istbA(tb) )
1924     tb->tb_bufferA[idx] = c;
1925   else
1926     tb->tb_bufferW[idx] = c;
1927 
1928   end_change(tb, where+1);
1929   CmodifiedTextBuffer(tb, ON);
1930 
1931   succeed;
1932 }
1933 
1934 
1935 status
change_textbuffer(TextBuffer tb,intptr_t where,PceString s)1936 change_textbuffer(TextBuffer tb, intptr_t where, PceString s)
1937 { intptr_t w, n;
1938 
1939   if ( s->s_size < 0 || where < 0 || where+s->s_size > tb->size )
1940     fail;
1941 
1942   if ( istbA(tb) && str_iswide(s) )
1943     promoteTextBuffer(tb);
1944 
1945   register_change_textbuffer(tb, where, s->s_size);
1946 
1947   if ( istbA(tb) )
1948   { for( w=where, n=0; n < s->s_size; n++, w++ )
1949     { intptr_t i = Index(tb, w);
1950       wint_t new = str_fetch(s, n);
1951 
1952       if ( tb->tb_bufferA[i] != new )
1953       { if ( tisendsline(tb->syntax, tb->tb_bufferA[i]) )
1954 	  tb->lines--;
1955 	if ( tisendsline(tb->syntax, new) )
1956 	  tb->lines++;
1957 	tb->tb_bufferA[i] = new;
1958       }
1959     }
1960   } else
1961   { for( w=where, n=0; n < s->s_size; n++, w++ )
1962     { intptr_t i = Index(tb, w);
1963       charW new = str_fetch(s, n);
1964 
1965       if ( tb->tb_bufferW[i] != new )
1966       { if ( tisendsline(tb->syntax, tb->tb_bufferW[i]) )
1967 	  tb->lines--;
1968 	if ( tisendsline(tb->syntax, new) )
1969 	  tb->lines++;
1970 	tb->tb_bufferW[i] = new;
1971       }
1972     }
1973   }
1974 
1975   start_change(tb, where);
1976   end_change(tb, where+s->s_size);
1977   CmodifiedTextBuffer(tb, ON);
1978 
1979   succeed;
1980 }
1981 
1982 
1983 static void
mirror_textbuffer(TextBuffer tb,int f,int t)1984 mirror_textbuffer(TextBuffer tb, int f, int t)
1985 { if ( istbA(tb) )
1986   { for( ; f < t; f++, t-- )
1987       Swap(tb->tb_bufferA[f], tb->tb_bufferA[t])
1988   } else
1989   { for( ; f < t; f++, t-- )
1990       Swap(tb->tb_bufferW[f], tb->tb_bufferW[t])
1991   }
1992 }
1993 
1994 
1995 static status
transpose_textbuffer(TextBuffer tb,intptr_t f1,intptr_t t1,intptr_t f2,intptr_t t2)1996 transpose_textbuffer(TextBuffer tb, intptr_t f1, intptr_t t1, intptr_t f2, intptr_t t2)
1997 { Before_i(f1, t1);
1998   Before_i(f2, t2);
1999 
2000   f1 = NormaliseIndex(tb, f1);
2001   t1 = NormaliseIndex(tb, t1);
2002   f2 = NormaliseIndex(tb, f2);
2003   t2 = NormaliseIndex(tb, t2);
2004 
2005   if ( f1 > f2 )
2006   { Swap(f1, f2);
2007     Swap(t1, t2);
2008   }
2009   if ( t1 > f2 )
2010     fail;
2011 
2012   register_change_textbuffer(tb, f1, t2-f1);
2013 
2014   room(tb, t2, 0);			/* move gap out of the way */
2015   t1--; t2--;
2016   mirror_textbuffer(tb, f1, t2);
2017   mirror_textbuffer(tb, f1, f1+t2-f2);
2018   mirror_textbuffer(tb, t2+f1-t1, t2);
2019   mirror_textbuffer(tb, f1+t2-f2+1, t2+f1-t1-1);
2020 
2021   start_change(tb, f1);
2022   end_change(tb, t2+1);
2023   CmodifiedTextBuffer(tb, ON);
2024 
2025   succeed;
2026 }
2027 
2028 
2029 static status
downcase_textbuffer(TextBuffer tb,intptr_t from,intptr_t len)2030 downcase_textbuffer(TextBuffer tb, intptr_t from, intptr_t len)
2031 { for( ; from < tb->size && len > 0; len--, from++ )
2032   { wint_t c;
2033 
2034     if ( iswupper((c=fetch(from))) )
2035       store_textbuffer(tb, from, towlower(c));
2036   }
2037 
2038   succeed;
2039 }
2040 
2041 
2042 static status
upcase_textbuffer(TextBuffer tb,intptr_t from,intptr_t len)2043 upcase_textbuffer(TextBuffer tb, intptr_t from, intptr_t len)
2044 { for( ; from < tb->size && len > 0; len--, from++ )
2045   { wint_t c;
2046 
2047     if ( iswlower((c=fetch(from))) )
2048       store_textbuffer(tb, from, towupper(c));
2049   }
2050 
2051   succeed;
2052 }
2053 
2054 
2055 static status
capitalise_textbuffer(TextBuffer tb,intptr_t from,intptr_t len)2056 capitalise_textbuffer(TextBuffer tb, intptr_t from, intptr_t len)
2057 { wint_t b = ' ';
2058 
2059   for( ; from < tb->size && len > 0; len--, from++ )
2060   { wint_t c = fetch(from);
2061     wint_t c2;
2062 
2063     if ( !iswalnum(b) )
2064       c2 = towupper(c);
2065     else
2066       c2 = towlower(c);
2067 
2068     if ( c2 != c )
2069       store_textbuffer(tb, from, c2);
2070 
2071     b = c;
2072   }
2073 
2074   succeed;
2075 }
2076 
2077 
2078 static Any
streamError(IOSTREAM * fd)2079 streamError(IOSTREAM *fd)
2080 { if ( fd->message )
2081     return cToPceStringA(NIL, fd->message, strlen(fd->message), FALSE);
2082 
2083   return getOsErrorPce(PCE);
2084 }
2085 
2086 
2087 static status
save_textbuffer(TextBuffer tb,intptr_t from,intptr_t len,SourceSink file)2088 save_textbuffer(TextBuffer tb, intptr_t from, intptr_t len, SourceSink file)
2089 { IOSTREAM *fd;
2090 
2091   room(tb, tb->size, 0);		/* move the gap to the end */
2092 
2093   if ( !(fd = Sopen_object(file, "wr")) )
2094     return errorPce(file, NAME_openFile, NAME_write, getOsErrorPce(PCE));
2095 
2096   from = NormaliseIndex(tb, from);
2097   if ( (from + len) > tb->size )
2098     len = tb->size - from;
2099 
2100   if ( istbA(tb) )
2101   { const charA *f = &tb->tb_bufferA[from];
2102     const charA *e = &f[len];
2103 
2104     for( ; f<e; f++)
2105     { if ( Sputcode(*f, fd) < 0 )
2106       { Any msg;
2107       error:
2108 	msg = streamError(fd);
2109 	Sclose(fd);
2110 	return errorPce(file, NAME_ioError, msg);
2111       }
2112     }
2113   } else
2114   { const charW *f = &tb->tb_bufferW[from];
2115     const charW *e = &f[len];
2116 
2117     for( ; f<e; f++)
2118     { if ( Sputcode(*f, fd) < 0 )
2119 	goto error;
2120     }
2121   }
2122 
2123   if ( Sclose(fd) < 0 )
2124     return errorPce(file, NAME_ioError, getOsErrorPce(PCE));
2125 
2126   succeed;
2127 }
2128 
2129 
2130 status
str_sub_text_buffer(TextBuffer tb,PceString s,intptr_t start,intptr_t len)2131 str_sub_text_buffer(TextBuffer tb, PceString s, intptr_t start, intptr_t len)
2132 { int idx;
2133 
2134   if ( start < 0 )
2135     start = 0;
2136   else if ( start > tb->size )
2137     start = tb->size-1;
2138 
2139   if ( len < 0 )
2140     len = 0;
2141   else if ( start + len > tb->size )
2142     len = tb->size - start;
2143 
2144   if ( start < tb->gap_start && start+len > tb->gap_start )
2145     room(tb, start + len, 1);
2146 
2147   str_cphdr(s, &tb->buffer);
2148   s->s_size = len;
2149 
2150   if ( start < tb->gap_start )
2151     idx = start;
2152   else
2153     idx = tb->gap_end + (start - tb->gap_start);
2154 
2155   if ( isstrA(s) )
2156     s->s_textA = &tb->tb_bufferA[idx];
2157   else
2158     s->s_textW = &tb->tb_bufferW[idx];
2159 
2160   succeed;
2161 }
2162 
2163 
2164 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2165 The promoteTextBuffer() function transforms an 8-bit textbuffer into a
2166 wide character one.  Otherwise nothing is changed.
2167 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2168 
2169 static status
promoteTextBuffer(TextBuffer tb)2170 promoteTextBuffer(TextBuffer tb)
2171 { if ( istbA(tb) )
2172   { charW *w = pceMalloc(tb->allocated * sizeof(charW));
2173     const charA *f = Address(tb, 0);
2174     const charA *e = &f[tb->allocated];
2175     charW *t = w;
2176 
2177     while(f<e)
2178       *t++ = *f++;
2179 
2180     pceFree(tb->tb_bufferA);
2181     tb->tb_bufferW = w;
2182     tb->buffer.s_iswide = TRUE;
2183   }
2184 
2185   succeed;
2186 }
2187 
2188 
2189 static status
fits_iso_latin_1(const charW * s,size_t len)2190 fits_iso_latin_1(const charW *s, size_t len)
2191 { const charW *e = &s[len];
2192 
2193   for( ;s<e; s++)
2194   { if ( *s > 0xff )
2195       succeed;
2196   }
2197 
2198   fail;
2199 }
2200 
2201 
2202 static status
demoteTextBuffer(TextBuffer tb)2203 demoteTextBuffer(TextBuffer tb)
2204 { if ( !istbA(tb) )
2205   { if ( fits_iso_latin_1(tb->tb_bufferW, tb->gap_start) &&
2206 	 fits_iso_latin_1(tb->tb_bufferW+tb->gap_end,
2207 			  tb->allocated - tb->gap_end) )
2208     { charA *s = pceMalloc(tb->allocated * sizeof(charA));
2209       const charW *f = tb->tb_bufferW;
2210       const charW *e = &f[tb->allocated];
2211       charA *t = s;
2212 
2213       while(f<e)
2214 	*t++ = *f++;
2215 
2216       pceFree(tb->tb_bufferW);
2217       tb->tb_bufferA = s;
2218       tb->buffer.s_iswide = FALSE;
2219     } else
2220     { fail;
2221     }
2222   }
2223 
2224   succeed;
2225 }
2226 
2227 
2228 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2229 insert_file_textbuffer()
2230 
2231 Insert the contents of file `file'  into   the  text  buffer at position
2232 `where' `times' times. Returns  SUCCEED  if   everything  was  ok,  FAIL
2233 otherwise.
2234 
2235 The most common case for this function is  to read an entire file simply
2236 once into the buffer. The simplest approach is   to read the file into a
2237 string and use the string insertion function   below, but the price is a
2238 potential duplication of memory usage.
2239 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2240 
2241 
2242 static int
insert_file_textbuffer(TextBuffer tb,intptr_t where,intptr_t times,SourceSink file)2243 insert_file_textbuffer(TextBuffer tb, intptr_t where, intptr_t times, SourceSink file)
2244 { intptr_t grow, here;
2245   int64_t size;
2246   IOSTREAM *fd;
2247 
2248   if ( times <= 0 )
2249     succeed;
2250 
2251   if ( !(fd = Sopen_object(file, "rr")) )
2252     fail;
2253   size = Ssize(fd);			/* size in bytes */
2254 
2255   room(tb, where, size);		/* always enough */
2256   where = tb->gap_start;		/* normalised */
2257   start_change(tb, tb->gap_start);
2258 
2259   if ( istbA(tb) )
2260   { for(;;)
2261     { int c = Sgetcode(fd);
2262 
2263       if ( c == EOF )
2264 	goto done;
2265       if ( c > 0xff )
2266       { promoteTextBuffer(tb);
2267 	tb->tb_bufferW[tb->gap_start++] = c;
2268 	tb->size++;
2269 	break;
2270       }
2271       tb->tb_bufferA[tb->gap_start++] = c;
2272       tb->size++;
2273     }
2274   }
2275 					/* promoted or already the case */
2276   if ( !istbA(tb) && !Sfeof(fd) )
2277   { for(;;)
2278     { int c = Sgetcode(fd);
2279 
2280       if ( c == EOF )
2281 	goto done;
2282       tb->tb_bufferW[tb->gap_start++] = c;
2283       tb->size++;
2284     }
2285   }
2286 
2287 done:
2288   if ( Sferror(fd) )
2289   { tb->gap_start = where;		/* forget about it */
2290     Sclose(fd);
2291 
2292     return errorPce(file, NAME_ioError, getOsErrorPce(PCE));
2293   }
2294 
2295   if ( instanceOfObject(file, ClassFile) )
2296   { FileObj f = (FileObj)file;
2297 
2298     switch(fd->newline)
2299     { case SIO_NL_POSIX:
2300       case SIO_NL_DETECT:
2301 	assign(f, newline_mode, NAME_posix);
2302 	break;
2303       case SIO_NL_DOS:
2304 	assign(f, newline_mode, NAME_dos);
2305       break;
2306     }
2307   }
2308 
2309   Sclose(fd);
2310   size = tb->gap_start - where;
2311   grow = times*size;
2312   register_insert_textbuffer(tb, where, grow);
2313 
2314   times--;
2315   room(tb, tb->gap_start, times*size);	/* enough for the copies */
2316   while(times-- > 0)
2317   { memmove(Address(tb, tb->gap_start),
2318 	    Address(tb, where),
2319 	    istbA(tb) ? size : size*sizeof(charW));
2320     tb->gap_start += size;
2321     tb->size += size;
2322   }
2323   end_change(tb, tb->gap_start);
2324 
2325 					/* update <-lines */
2326   for(here=where; here<where+grow; here++)
2327   { if ( tisendsline(tb->syntax, fetch(here)) )
2328       tb->lines++;
2329   }
2330 
2331   shift_fragments(tb, where, grow);
2332   CmodifiedTextBuffer(tb, ON);
2333 
2334   succeed;
2335 }
2336 
2337 
2338 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2339 Insert a string into a textbuffer. If  the textbuffer is ISO Latin-1 and
2340 the string is wide, the textbuffer is promoted.
2341 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2342 
2343 static status
insert_textbuffer_shift(TextBuffer tb,intptr_t where,intptr_t times,PceString s,int shift)2344 insert_textbuffer_shift(TextBuffer tb, intptr_t where, intptr_t times,
2345 			PceString s, int shift)
2346 { intptr_t grow;
2347   intptr_t here;
2348 
2349   if ( s->s_size == 0 )
2350     succeed;
2351 
2352   if ( istbA(tb) && str_iswide(s) )
2353     promoteTextBuffer(tb);
2354 
2355   grow = times * s->s_size;
2356   where = NormaliseIndex(tb, where);
2357   room(tb, where, grow);
2358 
2359   register_insert_textbuffer(tb, where, grow);
2360   start_change(tb, tb->gap_start);
2361   while(times-- > 0)
2362   { if ( tb->buffer.s_iswide == s->s_iswide )
2363     { memmove(Address(tb, tb->gap_start), s->s_text, str_datasize(s));
2364     } else if ( isstrA(s) )		/* insert A in W */
2365     { charW *d = &tb->buffer.s_textW[tb->gap_start];
2366       const charA *f = s->s_textA;
2367       const charA *e = &f[s->s_size];
2368 
2369       while(f<e)
2370 	*d++ = *f++;
2371     } else				/* insert W in A */
2372     { charA *d = &tb->buffer.s_textA[tb->gap_start];
2373       const charW *f = s->s_textW;
2374       const charW *e = &f[s->s_size];
2375 
2376       while(f<e)
2377 	*d++ = *f++;
2378     }
2379     tb->gap_start += s->s_size;
2380     tb->size += s->s_size;
2381   }
2382   end_change(tb, tb->gap_start);
2383 
2384   for(here=where; here<where+grow; here++)
2385   { if ( tisendsline(tb->syntax, fetch(here)) )
2386       tb->lines++;
2387   }
2388 
2389   if ( shift )
2390     shift_fragments(tb, where, grow);
2391 
2392   CmodifiedTextBuffer(tb, ON);
2393 
2394   succeed;
2395 }
2396 
2397 
2398 status
insert_textbuffer(TextBuffer tb,intptr_t where,intptr_t times,PceString s)2399 insert_textbuffer(TextBuffer tb, intptr_t where, intptr_t times, PceString s)
2400 { return insert_textbuffer_shift(tb, where, times, s, TRUE);
2401 }
2402 
2403 
2404 static status
clear_textbuffer(TextBuffer tb)2405 clear_textbuffer(TextBuffer tb)
2406 { register_delete_textbuffer(tb, 0, tb->size);
2407 
2408   if ( tb->tb_bufferA != NULL )
2409     pceFree(tb->tb_bufferA);
2410 
2411   start_change(tb, 0);
2412   end_change(tb, tb->size);
2413 
2414   tb->size = 0;
2415   tb->lines = 0;
2416   tb->allocated = ALLOC;
2417   tb->tb_bufferA = pceMalloc(istbA(tb) ? ALLOC : ALLOC*sizeof(charW));
2418 
2419   tb->gap_start = 0;
2420   tb->gap_end = tb->allocated;
2421 
2422   while( notNil(tb->first_fragment) )		/* destroy fragments */
2423     freeObject(tb->first_fragment);
2424   CmodifiedTextBuffer(tb, ON);
2425 
2426   succeed;
2427 }
2428 
2429 
2430 status
delete_textbuffer(TextBuffer tb,intptr_t where,intptr_t length)2431 delete_textbuffer(TextBuffer tb, intptr_t where, intptr_t length)
2432 { if ( length < 0 )				/* delete backwards */
2433   { if ( where + length < 0 )			/* passed start: normalise */
2434       length = -where;
2435 
2436     where += length;
2437     length = -length;
2438   }
2439 
2440   if ( where > tb->size )
2441   { intptr_t s = where-tb->size;
2442     where -= s;
2443     length -= s;
2444     if ( length <= 0 )
2445       succeed;
2446   }
2447 
2448   if ( where + length > tb->size )		/* normalise on end */
2449     length = tb->size - where;
2450 
2451   if ( length <= 0 )				/* out of bounds: ignore */
2452     succeed;
2453 
2454   room(tb, where, 0);				/* move the gap here */
2455   register_delete_textbuffer(tb, where, length);
2456 
2457   start_change(tb, where);
2458   tb->gap_end += length;
2459   tb->size -= length;
2460   end_change(tb, tb->size);
2461 
2462   shift_fragments(tb, where, -length);
2463   CmodifiedTextBuffer(tb, ON);
2464 
2465   succeed;
2466 }
2467 
2468 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2469 Shift the fragments after an insert/delete.  Insert is easy as this occurs
2470 either before, inside or after the fragment.  Delete is more complicated.
2471 The cases are:
2472 
2473 Text: +++++++++++++++++++++++++++++++++++++++++++++++++++++
2474 Frag:				-------
2475 1)	       11111
2476 2)			      2222
2477 3)				   333
2478 4)			      44444444444
2479 5)				     5555555
2480 6)						6666666
2481 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2482 
2483 static status
shift_fragments(TextBuffer tb,intptr_t from,intptr_t shift)2484 shift_fragments(TextBuffer tb, intptr_t from, intptr_t shift)
2485 { Fragment f;
2486   Cell cell;
2487 
2488   DEBUG(NAME_shift, Cprintf("Start shift: from = %ld, shift = %ld\n",
2489 			    from, shift));
2490 
2491   if ( shift > 0 )			/* insert */
2492   { for(f=tb->first_fragment; notNil(f); f = f->next)
2493     { if ( from < f->start ||
2494 	   (from == f->start && !(f->attributes & FRAG_INCLUDES_START)) )
2495 	f->start += shift;
2496       else if ( from < f->start + f->length ||
2497 		(from == f->start + f->length &&
2498 		 (f->attributes & FRAG_INCLUDES_END)) )
2499 	f->length += shift;
2500     }
2501   } else				/* delete */
2502   { intptr_t to = from - shift;
2503     Fragment next;
2504 
2505     for(f=tb->first_fragment; notNil(f); f = next)
2506     { intptr_t oldlen = f->length;
2507 
2508       next = f->next;
2509       DEBUG(NAME_shift, Cprintf("%s: start = %ld, length = %ld --> ",
2510 				pp(f), f->start, f->length));
2511       if ( to < f->start )			/* 1 */
2512 	f->start += shift;
2513       else
2514       { if ( from > f->start )			/* 3,5,6 */
2515 	{ if ( from < f->start + f->length )	/* 3,5 */
2516 	  { if ( to < f->start + f->length )	/* 3 */
2517 	      f->length += shift;
2518 	    else				/* 5 */
2519 	      f->length += (to - (f->start + f->length)) + shift;
2520 	  }
2521 						/* 6 */
2522 	} else					/* 2,4 */
2523 	{ if ( to < f->start + f->length )	/* 2 */
2524 	  { intptr_t reduce = to - f->start;
2525 
2526 	    f->length -= reduce;
2527 	    f->start -= -shift - reduce;
2528 	  } else				/* 4 */
2529 	  { f->length = 0;
2530 	    f->start = from;
2531 	  }
2532 	}
2533       }
2534 
2535       DEBUG(NAME_shift, Cprintf("start = %ld, length = %ld\n",
2536 				f->start, f->length));
2537 
2538       if ( f->length == 0 && oldlen != 0 )
2539       { DEBUG(NAME_shift, Cprintf("Invoking %s->emptied\n", pp(f)));
2540 	send(f, NAME_emptied, EAV);
2541       }
2542     }
2543   }
2544 
2545   for_cell(cell, tb->editors)
2546     send(cell->value, NAME_InsertEditor, toInt(from), toInt(shift), EAV);
2547 
2548   succeed;
2549 }
2550 
2551 
2552 static void
start_change(TextBuffer tb,intptr_t where)2553 start_change(TextBuffer tb, intptr_t where)
2554 { if ( tb->changed_start > where )
2555     tb->changed_start = where;
2556 }
2557 
2558 
2559 static void
end_change(TextBuffer tb,intptr_t where)2560 end_change(TextBuffer tb, intptr_t where)
2561 { if ( tb->changed_end < where )
2562     tb->changed_end = where;
2563 }
2564 
2565 
2566 /*  Ensures the gap starts at `where' and is at least `grow' bytes long.
2567 
2568  ** Tue Apr  4 17:23:28 1989  jan@swivax.UUCP (Jan Wielemaker)  */
2569 
2570 static int
room(TextBuffer tb,intptr_t where,intptr_t grow)2571 room(TextBuffer tb, intptr_t where, intptr_t grow)
2572 { ssize_t shift;
2573 
2574   if ( grow + tb->size > tb->allocated )
2575   { size_t s = ROUND(tb->size + grow, ALLOC);
2576     size_t ag = tb->allocated - tb->gap_end;
2577 
2578     shift = s - tb->allocated;
2579     tb->tb_bufferA = pceRealloc(tb->tb_bufferA,
2580 				istbA(tb) ? s : s*sizeof(charW));
2581     tb->allocated = s;
2582 
2583     memmove(Address(tb, tb->gap_end + shift),
2584 	    Address(tb, tb->gap_end),
2585 	    istbA(tb) ? ag : ag*sizeof(charW));
2586     tb->gap_end += shift;
2587   }
2588 
2589   shift = where - tb->gap_start;
2590   if ( shift < 0 )				/* move gap towards start */
2591   { size_t move = (size_t)-shift;
2592 
2593     memmove(Address(tb, tb->gap_end + shift),
2594 	    Address(tb, where),
2595 	    istbA(tb) ? move : sizeof(charW) * move);
2596   } else if ( shift > 0 )			/* move gap towards end */
2597   { size_t move = (size_t)shift;
2598 
2599     memmove(Address(tb, tb->gap_start),
2600 	    Address(tb, tb->gap_end),
2601 	    istbA(tb) ? move : sizeof(charW) * move);
2602   }
2603   tb->gap_start += shift;			/* move the gap pointers */
2604   tb->gap_end += shift;
2605 
2606   succeed;
2607 }
2608 
2609 
2610 		 /*******************************
2611 		 *	 ASFILE INTERFACE	*
2612 		 *******************************/
2613 
2614 static status
writeAsFileTextBuffer(TextBuffer tb,Int where,CharArray txt)2615 writeAsFileTextBuffer(TextBuffer tb, Int where, CharArray txt)
2616 { if ( isDefault(where) )
2617     where = toInt(tb->size);
2618 
2619   return insertTextBuffer(tb, where, txt, ONE);
2620 }
2621 
2622 
2623 static status
truncateAsFileTextBuffer(TextBuffer tb)2624 truncateAsFileTextBuffer(TextBuffer tb)
2625 { return clearTextBuffer(tb);
2626 }
2627 
2628 
2629 static Int
getSizeAsFileTextBuffer(TextBuffer tb)2630 getSizeAsFileTextBuffer(TextBuffer tb)
2631 { answer(toInt(tb->size));
2632 }
2633 
2634 
2635 static StringObj
getReadAsFileTextBuffer(TextBuffer tb,Int from,Int size)2636 getReadAsFileTextBuffer(TextBuffer tb, Int from, Int size)
2637 { return getContentsTextBuffer(tb, from, size);
2638 }
2639 
2640 
2641 		 /*******************************
2642 		 *	 CLASS DECLARATION	*
2643 		 *******************************/
2644 
2645 /* Type declarations */
2646 
2647 static char *T_writeAsFile[] =
2648         { "at=[int]", "text=char_array" };
2649 static char *T_character[] =
2650         { "at=int", "character=char" };
2651 static char *T_delete[] =
2652         { "at=int", "characters=[int]" };
2653 static char *T_insertFile[] =
2654         { "at=int", "data=source_sink", "times=[int]" };
2655 static char *T_insert[] =
2656         { "at=int", "text=char_array", "times=[int]" };
2657 static char *T_format[] =
2658         { "format=char_array", "argument=any ..." };
2659 static char *T_transpose[] =
2660         { "from1=int", "to1=int", "from2=int", "to2=int" };
2661 static char *T_contents[] =
2662         { "from=[int]", "size=[int]" };
2663 static char *T_fromADintD_toADintD[] =
2664         { "from=[int]", "to=[int]" };
2665 static char *T_matchingBracket[] =
2666         { "from=int", "bracket=[char]" };
2667 static char *T_matchingQuote[] =
2668         { "from=int", "direction={forward,backward}" };
2669 static char *T_find[] =
2670         { "from=int", "for=string", "times=[int]",
2671 	  "return=[{start,end}]", "exact_case=[bool]", "word=[bool]" };
2672 static char *T_fromAint_sizeAint[] =
2673         { "from=int", "size=int" };
2674 static char *T_skipComment[] =
2675         { "from=int", "to=[int]", "skip_layout=[bool]" };
2676 static char *T_skipLayout[] =
2677         { "from=int",
2678 	  "direction=[{forward,backward}]", "skip_newline=[bool]" };
2679 static char *T_scan[] =
2680         { "from=int",
2681 	  "unit={character,word,line,sentence,paragraph,term}",
2682 	  "times=[int]", "return=[{start,end}]" };
2683 static char *T_save[] =
2684         { "in=file", "from=[int]", "size=[int]" };
2685 static char *T_forAllComments[] =
2686 	{ "message=code", "from=[int]", "to=[int]" };
2687 static char *T_indexAint_startADintD[] =
2688         { "index=int", "start=[int]" };
2689 static char *T_append[] =
2690         { "text=char_array", "times=[int]" };
2691 
2692 /* Instance Variables */
2693 
2694 static vardecl var_textBuffer[] =
2695 { IV(NAME_firstFragment, "fragment*", IV_GET,
2696      NAME_fragment, "First fragment (lowest start index)"),
2697   IV(NAME_lastFragment, "fragment*", IV_GET,
2698      NAME_fragment, "Last fragment (highest start index)"),
2699   IV(NAME_editors, "chain", IV_GET,
2700      NAME_organisation, "Editors displaying this buffer"),
2701   SV(NAME_modified, "bool", IV_GET|IV_STORE, modifiedTextBuffer,
2702      NAME_modified, "Has buffer been modified"),
2703   SV(NAME_undoBufferSize, "bytes=int", IV_GET|IV_STORE, undoBufferSizeTextBuffer,
2704      NAME_modified, "Size of the undo-buffer in characters"),
2705   IV(NAME_syntax, "syntax_table", IV_BOTH,
2706      NAME_language, "Description of the used syntax"),
2707   IV(NAME_indentTabs, "bool", IV_BOTH,
2708      NAME_indentation, "Re-indent using tabs (true) or spaces (false)"),
2709   IV(NAME_generation, "0..", IV_GET,
2710      NAME_modified, "Indicate change-generation"),
2711   IV(NAME_changedStart, "alien:int", IV_NONE,
2712      NAME_repaint, "Start of changes since last repaint"),
2713   IV(NAME_changedEnd, "alien:int", IV_NONE,
2714      NAME_repaint, "End of changes since last repaint"),
2715   IV(NAME_gapStart, "alien:int", IV_NONE,
2716      NAME_storage, "Start of gap in buffer"),
2717   IV(NAME_gapEnd, "alien:int", IV_NONE,
2718      NAME_storage, "End of gap in buffer"),
2719   IV(NAME_size, "alien:int", IV_NONE,
2720      NAME_cardinality, "Number of valid characters in buffer"),
2721   IV(NAME_lines, "alien:int", IV_NONE,
2722      NAME_cardinality, "Number of newlines in the buffer"),
2723   IV(NAME_allocated, "alien:int", IV_NONE,
2724      NAME_storage, "Total size of buffer"),
2725   IV(NAME_undoBuffer, "alien:UndoBuffer", IV_NONE,
2726      NAME_storage, "Record undo information here"),
2727   IV(NAME_stringHeader, "alien:str_h", IV_NONE,
2728      NAME_storage, "Encoding description"),
2729   IV(NAME_buffer, "alien:char *", IV_NONE,
2730      NAME_storage, "Actual storage bin")
2731 };
2732 
2733 /* Send Methods */
2734 
2735 static senddecl send_textBuffer[] =
2736 { SM(NAME_initialise, 1, "contents=[char_array]", initialiseTextBuffer,
2737      DEFAULT, "Create from initial contents"),
2738   SM(NAME_unlink, 0, NULL, unlinkTextBuffer,
2739      DEFAULT, "Destroy the text"),
2740   SM(NAME_capitalise, 2, T_fromAint_sizeAint, capitaliseTextBuffer,
2741      NAME_case, "Capitalise (start, length)"),
2742   SM(NAME_downcase, 2, T_fromAint_sizeAint, downcaseTextBuffer,
2743      NAME_case, "Bring (start, length) to lowercase"),
2744   SM(NAME_upcase, 2, T_fromAint_sizeAint, upcaseTextBuffer,
2745      NAME_case, "Bring (start, length) to uppercase"),
2746   SM(NAME_append, 2, T_append, appendTextBuffer,
2747      NAME_edit, "Append string (n-times)"),
2748   SM(NAME_character, 2, T_character, characterTextBuffer,
2749      NAME_edit, "Change character at index to ASCII value"),
2750   SM(NAME_clear, 0, NULL, clearTextBuffer,
2751      NAME_edit, "Delete all contents (and fragments)"),
2752   SM(NAME_contents, 1, "char_array", contentsTextBuffer,
2753      NAME_edit, "Set the contents (deletes fragments)"),
2754   SM(NAME_delete, 2, T_delete, deleteTextBuffer,
2755      NAME_edit, "Delete characters from index"),
2756   SM(NAME_insert, 3, T_insert, insertTextBuffer,
2757      NAME_edit, "Insert string at index (n-times)"),
2758   SM(NAME_transpose, 4, T_transpose, transposeTextBuffer,
2759      NAME_edit, "Transpose [from1, to1) with [from2, to2)"),
2760   SM(NAME_insertFile, 3, T_insertFile, insertFileTextBuffer,
2761      NAME_file, "Insert file at index (n-times)"),
2762   SM(NAME_save, 3, T_save, saveTextBuffer,
2763      NAME_file, "Save (from, length) to file"),
2764   SM(NAME_format, 2, T_format, formatTextBuffer,
2765      NAME_format, "Append formatted text"),
2766   SM(NAME_forAllFragments, 1, "code", forAllFragmentsTextBuffer,
2767      NAME_iterate, "Iterate code over all fragments"),
2768   SM(NAME_inComment, 2, T_indexAint_startADintD, inCommentTextBuffer,
2769      NAME_language, "Test if first index is in comment"),
2770   SM(NAME_forAllComments, 3, T_forAllComments, forAllCommentsTextBuffer,
2771      NAME_iterate, "Iterate code over all comments"),
2772   SM(NAME_inString, 2, T_indexAint_startADintD, inStringTextBuffer,
2773      NAME_language, "Test if first index is in string constant"),
2774   SM(NAME_checkPointUndo, 0, NULL, checkpointUndoTextBuffer,
2775      NAME_modified, "Set `no-change' checkpoint in undo buffer"),
2776   SM(NAME_markUndo, 0, NULL, markUndoTextBuffer,
2777      NAME_modified, "Mark undo point"),
2778   SM(NAME_resetUndo, 0, NULL, resetUndoTextBuffer,
2779      NAME_modified, "Clear the undo-buffer"),
2780   SM(NAME_undo, 0, NULL, undoTextBuffer,
2781      NAME_modified, "Undo operations backto last mark"),
2782   SM(NAME_attach, 1, "editor", attachTextBuffer,
2783      NAME_organisation, "Attach the given editor"),
2784   SM(NAME_detach, 1, "editor", detachTextBuffer,
2785      NAME_organisation, "Detach the given editor"),
2786   SM(NAME_report, 3, T_report, reportTextBuffer,
2787      NAME_report, "Report message (send to <-editors)"),
2788   SM(NAME_sort, 2, T_fromADintD_toADintD, sortTextBuffer,
2789      NAME_sort, "Sort [from, to) alphabetically by line"),
2790   SM(NAME_truncateAsFile, 0, NULL, truncateAsFileTextBuffer,
2791      NAME_stream, "Implements handling a buffer as a file"),
2792   SM(NAME_writeAsFile, 2, T_writeAsFile, writeAsFileTextBuffer,
2793      NAME_stream, "Implements handling a buffer as a file"),
2794   SM(NAME_iso_latin_1, 0, NULL, demoteTextBuffer,
2795      NAME_encoding, "Try to represent text as ISO Latin-1")
2796 };
2797 
2798 /* Get Methods */
2799 
2800 static getdecl get_textBuffer[] =
2801 { GM(NAME_convert, 1, "text_buffer", "editor", getConvertTextBuffer,
2802      DEFAULT, "Return `editor <-text_buffer'"),
2803   GM(NAME_length, 0, "int", NULL, getSizeTextBuffer,
2804      NAME_cardinality, "Equivalent to <-size (# characters)"),
2805   GM(NAME_size, 0, "int", NULL, getSizeTextBuffer,
2806      NAME_cardinality, "Number of characters in buffer"),
2807   GM(NAME_findAllFragments, 1, "matching=chain", "test=[code]", getFindAllFragmentsTextBuffer,
2808      NAME_fragment, "New chain holding fragments accepted by code"),
2809   GM(NAME_findFragment, 1, "fragment", "test=code", getFindFragmentTextBuffer,
2810      NAME_fragment, "First fragment that accepts code"),
2811   GM(NAME_matchingBracket, 2, "index=int", T_matchingBracket, getMatchingBracketTextBuffer,
2812      NAME_language, "Find bracket matching bracket at index"),
2813   GM(NAME_matchingQuote, 2, "index=int", T_matchingQuote, getMatchingQuoteTextBuffer,
2814      NAME_language, "Find matching string-quote"),
2815   GM(NAME_skipComment, 3, "index=int", T_skipComment, getSkipCommentTextBuffer,
2816      NAME_language, "Skip comments and optionally white space"),
2817   GM(NAME_skipLayout, 3, "index=int", T_skipLayout, getSkipBlanksTextBuffer,
2818      NAME_language, "Skip white-space in either direction"),
2819   GM(NAME_lineNumber, 1, "line=int", "index=[int]", getLineNumberTextBuffer,
2820      NAME_line, "Get line number (1-based) for character index"),
2821   GM(NAME_scan, 4, "index=int", T_scan, getScanTextBuffer,
2822      NAME_parse, "Scan textual units"),
2823   GM(NAME_character, 1, "char", "at=int", getCharacterTextBuffer,
2824      NAME_read, "ASCII value of character at index"),
2825   GM(NAME_contents, 2, "string", T_contents, getContentsTextBuffer,
2826      NAME_read, "New string holding text (from, length)"),
2827   GM(NAME_sub, 2, "string", T_fromADintD_toADintD, getSubTextBuffer,
2828      NAME_read, "New string holding text [from, to)"),
2829   GM(NAME_find, 6, "index=int", T_find, getFindTextBuffer,
2830      NAME_search, "Search for a string"),
2831   GM(NAME_readAsFile, 2, "string", T_fromAint_sizeAint,getReadAsFileTextBuffer,
2832      NAME_stream, "Implement reading as a file"),
2833   GM(NAME_sizeAsFile, 0, "characters=int", NULL, getSizeAsFileTextBuffer,
2834      NAME_stream, "Implement seek when using as a file"),
2835   GM(NAME_scanSyntax, 2, "tuple", T_fromADintD_toADintD,
2836      getScanSyntaxTextBuffer,
2837      NAME_language, "Find syntactical state of position"),
2838   GM(NAME_countLines, 2, "int", T_fromADintD_toADintD,
2839      getCountLinesTextBuffer,
2840      NAME_line, "Count lines in character range")
2841 };
2842 
2843 /* Resources */
2844 
2845 static classvardecl rc_textBuffer[] =
2846 { RC(NAME_syntax, "[syntax_table]", "default",
2847      "Syntax definition"),
2848   RC(NAME_undoBufferSize, "int", "10000",
2849      "Memory allocated to store undo"),
2850   RC(NAME_indentTabs, "bool", "@on", NULL)
2851 };
2852 
2853 /* Class Declaration */
2854 
2855 static Name textBuffer_termnames[] = { NAME_string };
2856 
2857 ClassDecl(textBuffer_decls,
2858           var_textBuffer, send_textBuffer, get_textBuffer, rc_textBuffer,
2859           0, textBuffer_termnames,
2860           "$Rev$");
2861 
2862 status
makeClassTextBuffer(Class class)2863 makeClassTextBuffer(Class class)
2864 { declareClass(class, &textBuffer_decls);
2865 
2866   setLoadStoreFunctionClass(class, loadTextBuffer, storeTextBuffer);
2867   saveStyleVariableClass(class, NAME_editors, NAME_nil);
2868   setCloneFunctionClass(class, cloneTextBuffer);
2869   cloneStyleVariableClass(class, NAME_editors, NAME_referenceChain);
2870 
2871   succeed;
2872 }
2873