#449569: Ensure shell/macro accelerators also have accelerator fix
[nedit.git] / source / textBuf.c
blob9080fd3509d3204dea72adbdc5204e2af55f1900
1 static const char CVSID[] = "$Id: textBuf.c,v 1.11 2001/08/14 08:37:16 jlous Exp $";
2 /*******************************************************************************
3 * *
4 * textBuf.c - Manage source text for one or more text areas *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * June 15, 1995 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <ctype.h>
33 #include <Xm/Xm.h>
35 #include "textBuf.h"
37 #define PREFERRED_GAP_SIZE 80 /* Initial size for the buffer gap (empty space
38 in the buffer where text might be inserted
39 if the user is typing sequential chars) */
41 static void histogramCharacters(const char *string, int length, char hist[256],
42 int init);
43 static void subsChars(char *string, int length, char fromChar, char toChar);
44 static char chooseNullSubsChar(char hist[256]);
45 static int insert(textBuffer *buf, int pos, const char *text);
46 static void delete(textBuffer *buf, int start, int end);
47 static void deleteRect(textBuffer *buf, int start, int end, int rectStart,
48 int rectEnd, int *replaceLen, int *endPos);
49 static void insertCol(textBuffer *buf, int column, int startPos, const char *insText,
50 int *nDeleted, int *nInserted, int *endPos);
51 static void overlayRect(textBuffer *buf, int startPos, int rectStart,
52 int rectEnd, char *insText, int *nDeleted, int *nInserted, int *endPos);
53 static void insertColInLine(const char *line, const char *insLine, int column, int insWidth,
54 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
55 int *endOffset);
56 static void deleteRectFromLine(const char *line, int rectStart, int rectEnd,
57 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
58 int *endOffset);
59 static void overlayRectInLine(const char *line, const char *insLine, int rectStart,
60 int rectEnd, int tabDist, int useTabs, char nullSubsChar, char *outStr,
61 int *outLen, int *endOffset);
62 static void callModifyCBs(textBuffer *buf, int pos, int nDeleted,
63 int nInserted, int nRestyled, char *deletedText);
64 static void redisplaySelection(textBuffer *buf, selection *oldSelection,
65 selection *newSelection);
66 static void moveGap(textBuffer *buf, int pos);
67 static void reallocateBuf(textBuffer *buf, int newGapStart, int newGapLen);
68 static void setSelection(selection *sel, int start, int end);
69 static void setRectSelect(selection *sel, int start, int end,
70 int rectStart, int rectEnd);
71 static void updateSelections(textBuffer *buf, int pos, int nDeleted,
72 int nInserted);
73 static void updateSelection(selection *sel, int pos, int nDeleted,
74 int nInserted);
75 static int getSelectionPos(selection *sel, int *start, int *end,
76 int *isRect, int *rectStart, int *rectEnd);
77 static char *getSelectionText(textBuffer *buf, selection *sel);
78 static void removeSelected(textBuffer *buf, selection *sel);
79 static void replaceSelected(textBuffer *buf, selection *sel, const char *text);
80 static void addPadding(char *string, int startIndent, int toIndent,
81 int tabDist, int useTabs, char nullSubsChar, int *charsAdded);
82 static int searchForward(textBuffer *buf, int startPos, char searchChar,
83 int *foundPos);
84 static int searchBackward(textBuffer *buf, int startPos, char searchChar,
85 int *foundPos);
86 static char *copyLine(const char *text, int *lineLen);
87 static int countLines(const char *string);
88 static int textWidth(const char *text, int tabDist, char nullSubsChar);
89 static void findRectSelBoundariesForCopy(textBuffer *buf, int lineStartPos,
90 int rectStart, int rectEnd, int *selStart, int *selEnd);
91 static char *realignTabs(const char *text, int origIndent, int newIndent,
92 int tabDist, int useTabs, char nullSubsChar, int *newLength);
93 static char *expandTabs(const char *text, int startIndent, int tabDist,
94 char nullSubsChar, int *newLen);
95 static char *unexpandTabs(const char *text, int startIndent, int tabDist,
96 char nullSubsChar, int *newLen);
97 static int max(int i1, int i2);
98 static int min(int i1, int i2);
100 #ifdef __MVS__
101 static const char *ControlCodeTable[64] = {
102 "nul", "soh", "stx", "etx", "sel", "ht", "rnl", "del",
103 "ge", "sps", "rpt", "vt", "ff", "cr", "so", "si",
104 "dle", "dc1", "dc2", "dc3", "res", "nl", "bs", "poc",
105 "can", "em", "ubs", "cu1", "ifs", "igs", "irs", "ius",
106 "ds", "sos", "fs", "wus", "byp", "lf", "etb", "esc",
107 "sa", "sfe", "sm", "csp", "mfa", "enq", "ack", "bel",
108 "x30", "x31", "syn", "ir", "pp", "trn", "nbs", "eot",
109 "sbs", "it", "rff", "cu3", "dc4", "nak", "x3e", "sub"};
110 #else
111 static const char *ControlCodeTable[32] = {
112 "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel",
113 "bs", "ht", "nl", "vt", "np", "cr", "so", "si",
114 "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb",
115 "can", "em", "sub", "esc", "fs", "gs", "rs", "us"};
116 #endif
119 ** Create an empty text buffer
121 textBuffer *BufCreate(void)
123 return BufCreatePreallocated(0);
127 ** Create an empty text buffer of a pre-determined size (use this to
128 ** avoid unnecessary re-allocation if you know exactly how much the buffer
129 ** will need to hold
131 textBuffer *BufCreatePreallocated(int requestedSize)
133 textBuffer *buf;
135 buf = (textBuffer *)XtMalloc(sizeof(textBuffer));
136 buf->length = 0;
137 buf->buf = XtMalloc(requestedSize + PREFERRED_GAP_SIZE);
138 buf->gapStart = 0;
139 buf->gapEnd = PREFERRED_GAP_SIZE;
140 buf->tabDist = 8;
141 buf->useTabs = True;
142 buf->primary.selected = False;
143 buf->primary.rectangular = False;
144 buf->primary.start = buf->primary.end = 0;
145 buf->secondary.selected = False;
146 buf->secondary.start = buf->secondary.end = 0;
147 buf->secondary.rectangular = False;
148 buf->highlight.selected = False;
149 buf->highlight.start = buf->highlight.end = 0;
150 buf->highlight.rectangular = False;
151 buf->modifyProcs = NULL;
152 buf->cbArgs = NULL;
153 buf->nModifyProcs = 0;
154 buf->nullSubsChar = '\0';
155 #ifdef PURIFY
156 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
157 #endif
158 return buf;
162 ** Free a text buffer
164 void BufFree(textBuffer *buf)
166 XtFree(buf->buf);
167 if (buf->nModifyProcs != 0) {
168 XtFree((char *)buf->modifyProcs);
169 XtFree((char *)buf->cbArgs);
171 XtFree((char *)buf);
175 ** Get the entire contents of a text buffer. Memory is allocated to contain
176 ** the returned string, which the caller must free.
178 char *BufGetAll(textBuffer *buf)
180 char *text;
182 text = XtMalloc(buf->length+1);
183 memcpy(text, buf->buf, buf->gapStart);
184 memcpy(&text[buf->gapStart], &buf->buf[buf->gapEnd],
185 buf->length - buf->gapStart);
186 text[buf->length] = '\0';
187 return text;
191 ** Replace the entire contents of the text buffer
193 void BufSetAll(textBuffer *buf, const char *text)
195 int length, deletedLength;
196 char *deletedText;
198 /* Save information for redisplay, and get rid of the old buffer */
199 deletedText = BufGetAll(buf);
200 deletedLength = buf->length;
201 XtFree(buf->buf);
203 /* Start a new buffer with a gap of PREFERRED_GAP_SIZE in the center */
204 length = strlen(text);
205 buf->buf = XtMalloc(length + PREFERRED_GAP_SIZE);
206 buf->length = length;
207 buf->gapStart = length/2;
208 buf->gapEnd = buf->gapStart + PREFERRED_GAP_SIZE;
209 memcpy(buf->buf, text, buf->gapStart);
210 memcpy(&buf->buf[buf->gapEnd], &text[buf->gapStart], length-buf->gapStart);
211 #ifdef PURIFY
212 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
213 #endif
215 /* Zero all of the existing selections */
216 updateSelections(buf, 0, deletedLength, 0);
218 /* Call the saved display routine(s) to update the screen */
219 callModifyCBs(buf, 0, deletedLength, length, 0, deletedText);
220 XtFree(deletedText);
224 ** Return a copy of the text between "start" and "end" character positions
225 ** from text buffer "buf". Positions start at 0, and the range does not
226 ** include the character pointed to by "end"
228 char *BufGetRange(textBuffer *buf, int start, int end)
230 char *text;
231 int length, part1Length;
233 /* Make sure start and end are ok, and allocate memory for returned string.
234 If start is bad, return "", if end is bad, adjust it. */
235 if (start < 0 || start > buf->length) {
236 text = XtMalloc(1);
237 text[0] = '\0';
238 return text;
240 if (end < start) {
241 int temp = start;
242 start = end;
243 end = temp;
245 if (end > buf->length)
246 end = buf->length;
247 length = end - start;
248 text = XtMalloc(length+1);
250 /* Copy the text from the buffer to the returned string */
251 if (end <= buf->gapStart) {
252 memcpy(text, &buf->buf[start], length);
253 } else if (start >= buf->gapStart) {
254 memcpy(text, &buf->buf[start+(buf->gapEnd-buf->gapStart)], length);
255 } else {
256 part1Length = buf->gapStart - start;
257 memcpy(text, &buf->buf[start], part1Length);
258 memcpy(&text[part1Length], &buf->buf[buf->gapEnd], length-part1Length);
260 text[length] = '\0';
261 return text;
265 ** Return the character at buffer position "pos". Positions start at 0.
267 char BufGetCharacter(textBuffer *buf, int pos)
269 if (pos < 0 || pos > buf->length)
270 return '\0';
271 if (pos < buf->gapStart)
272 return buf->buf[pos];
273 else
274 return buf->buf[pos + buf->gapEnd-buf->gapStart];
278 ** Insert null-terminated string "text" at position "pos" in "buf"
280 void BufInsert(textBuffer *buf, int pos, const char *text)
282 int nInserted;
284 /* if pos is not contiguous to existing text, make it */
285 if (pos > buf->length) pos = buf->length;
286 if (pos < 0 ) pos = 0;
288 /* insert and redisplay */
289 nInserted = insert(buf, pos, text);
290 buf->cursorPosHint = pos + nInserted;
291 callModifyCBs(buf, pos, 0, nInserted, 0, NULL);
295 ** Delete the characters between "start" and "end", and insert the
296 ** null-terminated string "text" in their place in in "buf"
298 void BufReplace(textBuffer *buf, int start, int end, const char *text)
300 char *deletedText;
301 int nInserted;
303 deletedText = BufGetRange(buf, start, end);
304 delete(buf, start, end);
305 nInserted = insert(buf, start, text);
306 buf->cursorPosHint = start + nInserted;
307 callModifyCBs(buf, start, end-start, nInserted, 0, deletedText);
308 XtFree(deletedText);
311 void BufRemove(textBuffer *buf, int start, int end)
313 char *deletedText;
315 /* Make sure the arguments make sense */
316 if (start > end) {
317 int temp = start;
318 start = end;
319 end = temp;
321 if (start > buf->length) start = buf->length;
322 if (start < 0) start = 0;
323 if (end > buf->length) end = buf->length;
324 if (end < 0) end = 0;
326 /* Remove and redisplay */
327 deletedText = BufGetRange(buf, start, end);
328 delete(buf, start, end);
329 buf->cursorPosHint = start;
330 callModifyCBs(buf, start, end-start, 0, 0, deletedText);
331 XtFree(deletedText);
334 void BufCopyFromBuf(textBuffer *fromBuf, textBuffer *toBuf, int fromStart,
335 int fromEnd, int toPos)
337 int length = fromEnd - fromStart;
338 int part1Length;
340 /* Prepare the buffer to receive the new text. If the new text fits in
341 the current buffer, just move the gap (if necessary) to where
342 the text should be inserted. If the new text is too large, reallocate
343 the buffer with a gap large enough to accomodate the new text and a
344 gap of PREFERRED_GAP_SIZE */
345 if (length > toBuf->gapEnd - toBuf->gapStart)
346 reallocateBuf(toBuf, toPos, length + PREFERRED_GAP_SIZE);
347 else if (toPos != toBuf->gapStart)
348 moveGap(toBuf, toPos);
350 /* Insert the new text (toPos now corresponds to the start of the gap) */
351 if (fromEnd <= fromBuf->gapStart) {
352 memcpy(&toBuf->buf[toPos], &fromBuf->buf[fromStart], length);
353 } else if (fromStart >= fromBuf->gapStart) {
354 memcpy(&toBuf->buf[toPos],
355 &fromBuf->buf[fromStart+(fromBuf->gapEnd-fromBuf->gapStart)],
356 length);
357 } else {
358 part1Length = fromBuf->gapStart - fromStart;
359 memcpy(&toBuf->buf[toPos], &fromBuf->buf[fromStart], part1Length);
360 memcpy(&toBuf->buf[toPos+part1Length], &fromBuf->buf[fromBuf->gapEnd],
361 length-part1Length);
363 toBuf->gapStart += length;
364 toBuf->length += length;
365 updateSelections(toBuf, toPos, 0, length);
369 ** Insert "text" columnwise into buffer starting at displayed character
370 ** position "column" on the line beginning at "startPos". Opens a rectangular
371 ** space the width and height of "text", by moving all text to the right of
372 ** "column" right. If charsInserted and charsDeleted are not NULL, the
373 ** number of characters inserted and deleted in the operation (beginning
374 ** at startPos) are returned in these arguments
376 void BufInsertCol(textBuffer *buf, int column, int startPos, const char *text,
377 int *charsInserted, int *charsDeleted)
379 int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
380 char *deletedText;
382 nLines = countLines(text);
383 lineStartPos = BufStartOfLine(buf, startPos);
384 nDeleted = BufEndOfLine(buf, BufCountForwardNLines(buf, startPos, nLines)) -
385 lineStartPos;
386 deletedText = BufGetRange(buf, lineStartPos, lineStartPos + nDeleted);
387 insertCol(buf, column, lineStartPos, text, &insertDeleted, &nInserted,
388 &buf->cursorPosHint);
389 if (nDeleted != insertDeleted)
390 fprintf(stderr, "internal consistency check ins1 failed");
391 callModifyCBs(buf, lineStartPos, nDeleted, nInserted, 0, deletedText);
392 XtFree(deletedText);
393 if (charsInserted != NULL)
394 *charsInserted = nInserted;
395 if (charsDeleted != NULL)
396 *charsDeleted = nDeleted;
400 ** Overlay "text" between displayed character positions "rectStart" and
401 ** "rectEnd" on the line beginning at "startPos". If charsInserted and
402 ** charsDeleted are not NULL, the number of characters inserted and deleted
403 ** in the operation (beginning at startPos) are returned in these arguments.
405 void BufOverlayRect(textBuffer *buf, int startPos, int rectStart,
406 int rectEnd, char *text, int *charsInserted, int *charsDeleted)
408 int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
409 char *deletedText;
411 nLines = countLines(text);
412 lineStartPos = BufStartOfLine(buf, startPos);
413 nDeleted = BufEndOfLine(buf, BufCountForwardNLines(buf, startPos, nLines)) -
414 lineStartPos;
415 deletedText = BufGetRange(buf, lineStartPos, lineStartPos + nDeleted);
416 overlayRect(buf, lineStartPos, rectStart, rectEnd, text, &insertDeleted,
417 &nInserted, &buf->cursorPosHint);
418 if (nDeleted != insertDeleted)
419 fprintf(stderr, "internal consistency check ovly1 failed");
420 callModifyCBs(buf, lineStartPos, nDeleted, nInserted, 0, deletedText);
421 XtFree(deletedText);
422 if (charsInserted != NULL)
423 *charsInserted = nInserted;
424 if (charsDeleted != NULL)
425 *charsDeleted = nDeleted;
429 ** Replace a rectangular area in buf, given by "start", "end", "rectStart",
430 ** and "rectEnd", with "text". If "text" is vertically longer than the
431 ** rectangle, add extra lines to make room for it.
433 void BufReplaceRect(textBuffer *buf, int start, int end, int rectStart,
434 int rectEnd, const char *text)
436 char *deletedText;
437 char *insText=NULL;
438 int i, nInsertedLines, nDeletedLines, insLen, hint;
439 int insertDeleted, insertInserted, deleteInserted;
440 int linesPadded = 0;
442 /* Make sure start and end refer to complete lines, since the
443 columnar delete and insert operations will replace whole lines */
444 start = BufStartOfLine(buf, start);
445 end = BufEndOfLine(buf, end);
447 /* If more lines will be deleted than inserted, pad the inserted text
448 with newlines to make it as long as the number of deleted lines. This
449 will indent all of the text to the right of the rectangle to the same
450 column. If more lines will be inserted than deleted, insert extra
451 lines in the buffer at the end of the rectangle to make room for the
452 additional lines in "text" */
453 nInsertedLines = countLines(text);
454 nDeletedLines = BufCountLines(buf, start, end);
455 if (nInsertedLines < nDeletedLines) {
456 char *insPtr;
458 insLen = strlen(text);
459 insText = XtMalloc(insLen + nDeletedLines - nInsertedLines + 1);
460 strcpy(insText, text);
461 insPtr = insText + insLen;
462 for (i=0; i<nDeletedLines-nInsertedLines; i++)
463 *insPtr++ = '\n';
464 *insPtr = '\0';
465 } else if (nDeletedLines < nInsertedLines) {
466 linesPadded = nInsertedLines-nDeletedLines;
467 for (i=0; i<linesPadded; i++)
468 insert(buf, end, "\n");
469 } else /* nDeletedLines == nInsertedLines */ {
472 /* Save a copy of the text which will be modified for the modify CBs */
473 deletedText = BufGetRange(buf, start, end);
475 /* Delete then insert */
476 deleteRect(buf, start, end, rectStart, rectEnd, &deleteInserted, &hint);
477 if (insText) {
478 insertCol(buf, rectStart, start, insText, &insertDeleted, &insertInserted,
479 &buf->cursorPosHint);
480 XtFree(insText);
482 else
483 insertCol(buf, rectStart, start, text, &insertDeleted, &insertInserted,
484 &buf->cursorPosHint);
486 /* Figure out how many chars were inserted and call modify callbacks */
487 if (insertDeleted != deleteInserted + linesPadded)
488 fprintf(stderr, "NEdit: internal consistency check repl1 failed\n");
489 callModifyCBs(buf, start, end-start, insertInserted, 0, deletedText);
490 XtFree(deletedText);
494 ** Remove a rectangular swath of characters between character positions start
495 ** and end and horizontal displayed-character offsets rectStart and rectEnd.
497 void BufRemoveRect(textBuffer *buf, int start, int end, int rectStart,
498 int rectEnd)
500 char *deletedText;
501 int nInserted;
503 start = BufStartOfLine(buf, start);
504 end = BufEndOfLine(buf, end);
505 deletedText = BufGetRange(buf, start, end);
506 deleteRect(buf, start, end, rectStart, rectEnd, &nInserted,
507 &buf->cursorPosHint);
508 callModifyCBs(buf, start, end-start, nInserted, 0, deletedText);
509 XtFree(deletedText);
513 ** Clear a rectangular "hole" out of the buffer between character positions
514 ** start and end and horizontal displayed-character offsets rectStart and
515 ** rectEnd.
517 void BufClearRect(textBuffer *buf, int start, int end, int rectStart,
518 int rectEnd)
520 int i, nLines;
521 char *newlineString;
523 nLines = BufCountLines(buf, start, end);
524 newlineString = XtMalloc(nLines+1);
525 for (i=0; i<nLines; i++)
526 newlineString[i] = '\n';
527 newlineString[i] = '\0';
528 BufOverlayRect(buf, start, rectStart, rectEnd, newlineString,
529 NULL, NULL);
530 XtFree(newlineString);
533 char *BufGetTextInRect(textBuffer *buf, int start, int end,
534 int rectStart, int rectEnd)
536 int lineStart, selLeft, selRight, len;
537 char *textOut, *textIn, *outPtr, *retabbedStr;
539 start = BufStartOfLine(buf, start);
540 end = BufEndOfLine(buf, end);
541 textOut = XtMalloc((end - start) + 1);
542 lineStart = start;
543 outPtr = textOut;
544 while (lineStart <= end) {
545 findRectSelBoundariesForCopy(buf, lineStart, rectStart, rectEnd,
546 &selLeft, &selRight);
547 textIn = BufGetRange(buf, selLeft, selRight);
548 len = selRight - selLeft;
549 memcpy(outPtr, textIn, len);
550 XtFree(textIn);
551 outPtr += len;
552 lineStart = BufEndOfLine(buf, selRight) + 1;
553 *outPtr++ = '\n';
555 if (outPtr != textOut)
556 outPtr--; /* don't leave trailing newline */
557 *outPtr = '\0';
559 /* If necessary, realign the tabs in the selection as if the text were
560 positioned at the left margin */
561 retabbedStr = realignTabs(textOut, rectStart, 0, buf->tabDist,
562 buf->useTabs, buf->nullSubsChar, &len);
563 XtFree(textOut);
564 return retabbedStr;
568 ** Get the hardware tab distance used by all displays for this buffer,
569 ** and used in computing offsets for rectangular selection operations.
571 int BufGetTabDistance(textBuffer *buf)
573 return buf->tabDist;
577 ** Set the hardware tab distance used by all displays for this buffer,
578 ** and used in computing offsets for rectangular selection operations.
580 void BufSetTabDistance(textBuffer *buf, int tabDist)
582 char *deletedText;
584 /* Change the tab setting */
585 buf->tabDist = tabDist;
587 /* Force any display routines to redisplay everything (unfortunately,
588 this means copying the whole buffer contents to provide "deletedText" */
589 deletedText = BufGetAll(buf);
590 callModifyCBs(buf, 0, buf->length, buf->length, 0, deletedText);
591 XtFree(deletedText);
594 void BufSelect(textBuffer *buf, int start, int end)
596 selection oldSelection = buf->primary;
598 setSelection(&buf->primary, start, end);
599 redisplaySelection(buf, &oldSelection, &buf->primary);
602 void BufUnselect(textBuffer *buf)
604 selection oldSelection = buf->primary;
606 buf->primary.selected = False;
607 redisplaySelection(buf, &oldSelection, &buf->primary);
610 void BufRectSelect(textBuffer *buf, int start, int end, int rectStart,
611 int rectEnd)
613 selection oldSelection = buf->primary;
615 setRectSelect(&buf->primary, start, end, rectStart, rectEnd);
616 redisplaySelection(buf, &oldSelection, &buf->primary);
619 int BufGetSelectionPos(textBuffer *buf, int *start, int *end,
620 int *isRect, int *rectStart, int *rectEnd)
622 return getSelectionPos(&buf->primary, start, end, isRect, rectStart,
623 rectEnd);
626 char *BufGetSelectionText(textBuffer *buf)
628 return getSelectionText(buf, &buf->primary);
631 void BufRemoveSelected(textBuffer *buf)
633 removeSelected(buf, &buf->primary);
636 void BufReplaceSelected(textBuffer *buf, char *text)
638 replaceSelected(buf, &buf->primary, text);
641 void BufSecondarySelect(textBuffer *buf, int start, int end)
643 selection oldSelection = buf->secondary;
645 setSelection(&buf->secondary, start, end);
646 redisplaySelection(buf, &oldSelection, &buf->secondary);
649 void BufSecondaryUnselect(textBuffer *buf)
651 selection oldSelection = buf->secondary;
653 buf->secondary.selected = False;
654 redisplaySelection(buf, &oldSelection, &buf->secondary);
657 void BufSecRectSelect(textBuffer *buf, int start, int end,
658 int rectStart, int rectEnd)
660 selection oldSelection = buf->secondary;
662 setRectSelect(&buf->secondary, start, end, rectStart, rectEnd);
663 redisplaySelection(buf, &oldSelection, &buf->secondary);
666 int BufGetSecSelectPos(textBuffer *buf, int *start, int *end,
667 int *isRect, int *rectStart, int *rectEnd)
669 return getSelectionPos(&buf->secondary, start, end, isRect, rectStart,
670 rectEnd);
673 char *BufGetSecSelectText(textBuffer *buf)
675 return getSelectionText(buf, &buf->secondary);
678 void BufRemoveSecSelect(textBuffer *buf)
680 removeSelected(buf, &buf->secondary);
683 void BufReplaceSecSelect(textBuffer *buf, char *text)
685 replaceSelected(buf, &buf->secondary, text);
688 void BufHighlight(textBuffer *buf, int start, int end)
690 selection oldSelection = buf->highlight;
692 setSelection(&buf->highlight, start, end);
693 redisplaySelection(buf, &oldSelection, &buf->highlight);
696 void BufUnhighlight(textBuffer *buf)
698 selection oldSelection = buf->highlight;
700 buf->highlight.selected = False;
701 redisplaySelection(buf, &oldSelection, &buf->highlight);
704 void BufRectHighlight(textBuffer *buf, int start, int end,
705 int rectStart, int rectEnd)
707 selection oldSelection = buf->highlight;
709 setRectSelect(&buf->highlight, start, end, rectStart, rectEnd);
710 redisplaySelection(buf, &oldSelection, &buf->highlight);
713 int BufGetHighlightPos(textBuffer *buf, int *start, int *end,
714 int *isRect, int *rectStart, int *rectEnd)
716 return getSelectionPos(&buf->highlight, start, end, isRect, rectStart,
717 rectEnd);
720 char *BufGetHighlightText(textBuffer *buf)
722 return getSelectionText(buf, &buf->highlight);
726 ** Add a callback routine to be called when the buffer is modified
728 void BufAddModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
729 void *cbArg)
731 bufModifyCallbackProc *newModifyProcs;
732 void **newCBArgs;
733 int i;
735 newModifyProcs = (bufModifyCallbackProc *)
736 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs+1));
737 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs+1));
738 for (i=0; i<buf->nModifyProcs; i++) {
739 newModifyProcs[i] = buf->modifyProcs[i];
740 newCBArgs[i] = buf->cbArgs[i];
742 if (buf->nModifyProcs != 0) {
743 XtFree((char *)buf->modifyProcs);
744 XtFree((char *)buf->cbArgs);
746 newModifyProcs[buf->nModifyProcs] = bufModifiedCB;
747 newCBArgs[buf->nModifyProcs] = cbArg;
748 buf->nModifyProcs++;
749 buf->modifyProcs = newModifyProcs;
750 buf->cbArgs = newCBArgs;
753 void BufRemoveModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
754 void *cbArg)
756 int i, toRemove = -1;
757 bufModifyCallbackProc *newModifyProcs;
758 void **newCBArgs;
760 /* find the matching callback to remove */
761 for (i=0; i<buf->nModifyProcs; i++) {
762 if (buf->modifyProcs[i] == bufModifiedCB && buf->cbArgs[i] == cbArg) {
763 toRemove = i;
764 break;
767 if (toRemove == -1) {
768 fprintf(stderr, "Internal Error: Can't find modify CB to remove\n");
769 return;
772 /* Allocate new lists for remaining callback procs and args (if
773 any are left) */
774 buf->nModifyProcs--;
775 if (buf->nModifyProcs == 0) {
776 buf->nModifyProcs = 0;
777 XtFree((char *)buf->modifyProcs);
778 buf->modifyProcs = NULL;
779 XtFree((char *)buf->cbArgs);
780 buf->cbArgs = NULL;
781 return;
783 newModifyProcs = (bufModifyCallbackProc *)
784 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs));
785 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs));
787 /* copy out the remaining members and free the old lists */
788 for (i=0; i<toRemove; i++) {
789 newModifyProcs[i] = buf->modifyProcs[i];
790 newCBArgs[i] = buf->cbArgs[i];
792 for (; i<buf->nModifyProcs; i++) {
793 newModifyProcs[i] = buf->modifyProcs[i+1];
794 newCBArgs[i] = buf->cbArgs[i+1];
796 XtFree((char *)buf->modifyProcs);
797 XtFree((char *)buf->cbArgs);
798 buf->modifyProcs = newModifyProcs;
799 buf->cbArgs = newCBArgs;
803 ** Return the text from the entire line containing position "pos"
805 char *BufGetLineText(textBuffer *buf, int pos)
807 return BufGetRange(buf, BufStartOfLine(buf, pos), BufEndOfLine(buf, pos));
811 ** Find the position of the start of the line containing position "pos"
813 int BufStartOfLine(textBuffer *buf, int pos)
815 int startPos;
817 if (!searchBackward(buf, pos, '\n', &startPos))
818 return 0;
819 return startPos + 1;
824 ** Find the position of the end of the line containing position "pos"
825 ** (which is either a pointer to the newline character ending the line,
826 ** or a pointer to one character beyond the end of the buffer)
828 int BufEndOfLine(textBuffer *buf, int pos)
830 int endPos;
832 if (!searchForward(buf, pos, '\n', &endPos))
833 endPos = buf->length;
834 return endPos;
838 ** Get a character from the text buffer expanded into it's screen
839 ** representation (which may be several characters for a tab or a
840 ** control code). Returns the number of characters written to "outStr".
841 ** "indent" is the number of characters from the start of the line
842 ** for figuring tabs. Output string is guranteed to be shorter or
843 ** equal in length to MAX_EXP_CHAR_LEN
845 int BufGetExpandedChar(textBuffer *buf, int pos, int indent, char *outStr)
847 return BufExpandCharacter(BufGetCharacter(buf, pos), indent, outStr,
848 buf->tabDist, buf->nullSubsChar);
852 ** Expand a single character from the text buffer into it's screen
853 ** representation (which may be several characters for a tab or a
854 ** control code). Returns the number of characters added to "outStr".
855 ** "indent" is the number of characters from the start of the line
856 ** for figuring tabs. Output string is guranteed to be shorter or
857 ** equal in length to MAX_EXP_CHAR_LEN
859 int BufExpandCharacter(char c, int indent, char *outStr, int tabDist,
860 char nullSubsChar)
862 int i, nSpaces;
864 /* Convert tabs to spaces */
865 if (c == '\t') {
866 nSpaces = tabDist - (indent % tabDist);
867 for (i=0; i<nSpaces; i++)
868 outStr[i] = ' ';
869 return nSpaces;
872 /* Convert ASCII (and EBCDIC in the __MVS__ (OS/390) case) control
873 codes to readable character sequences */
874 if (c == nullSubsChar) {
875 sprintf(outStr, "<nul>");
876 return 5;
878 #ifdef __MVS__
879 if (((unsigned char)c) <= 63) {
880 sprintf(outStr, "<%s>", ControlCodeTable[(unsigned char)c]);
881 return strlen(outStr);
883 #else
884 if (((unsigned char)c) <= 31) {
885 sprintf(outStr, "<%s>", ControlCodeTable[(unsigned char)c]);
886 return strlen(outStr);
887 } else if (c == 127) {
888 sprintf(outStr, "<del>");
889 return 5;
891 #endif
893 /* Otherwise, just return the character */
894 *outStr = c;
895 return 1;
899 ** Return the length in displayed characters of character "c" expanded
900 ** for display (as discussed above in BufGetExpandedChar). If the
901 ** buffer for which the character width is being measured is doing null
902 ** substitution, nullSubsChar should be passed as that character (or nul
903 ** to ignore).
905 int BufCharWidth(char c, int indent, int tabDist, char nullSubsChar)
907 /* Note, this code must parallel that in BufExpandCharacter */
908 if (c == nullSubsChar)
909 return 5;
910 else if (c == '\t')
911 return tabDist - (indent % tabDist);
912 else if (((unsigned char)c) <= 31)
913 return strlen(ControlCodeTable[(unsigned char)c]) + 2;
914 else if (c == 127)
915 return 5;
916 return 1;
920 ** Count the number of displayed characters between buffer position
921 ** "lineStartPos" and "targetPos". (displayed characters are the characters
922 ** shown on the screen to represent characters in the buffer, where tabs and
923 ** control characters are expanded)
925 int BufCountDispChars(textBuffer *buf, int lineStartPos, int targetPos)
927 int pos, charCount = 0;
928 char expandedChar[MAX_EXP_CHAR_LEN];
930 pos = lineStartPos;
931 while (pos < targetPos)
932 charCount += BufGetExpandedChar(buf, pos++, charCount, expandedChar);
933 return charCount;
937 ** Count forward from buffer position "startPos" in displayed characters
938 ** (displayed characters are the characters shown on the screen to represent
939 ** characters in the buffer, where tabs and control characters are expanded)
941 int BufCountForwardDispChars(textBuffer *buf, int lineStartPos, int nChars)
943 int pos, charCount = 0;
944 char c;
946 pos = lineStartPos;
947 while (charCount < nChars && pos < buf->length) {
948 c = BufGetCharacter(buf, pos);
949 if (c == '\n')
950 return pos;
951 charCount += BufCharWidth(c, charCount, buf->tabDist,buf->nullSubsChar);
952 pos++;
954 return pos;
958 ** Count the number of newlines between startPos and endPos in buffer "buf".
959 ** The character at position "endPos" is not counted.
961 int BufCountLines(textBuffer *buf, int startPos, int endPos)
963 int pos, gapLen = buf->gapEnd - buf->gapStart;
964 int lineCount = 0;
966 pos = startPos;
967 while (pos < buf->gapStart) {
968 if (pos == endPos)
969 return lineCount;
970 if (buf->buf[pos++] == '\n')
971 lineCount++;
973 while (pos < buf->length) {
974 if (pos == endPos)
975 return lineCount;
976 if (buf->buf[pos++ + gapLen] == '\n')
977 lineCount++;
979 return lineCount;
983 ** Find the first character of the line "nLines" forward from "startPos"
984 ** in "buf" and return its position
986 int BufCountForwardNLines(textBuffer *buf, int startPos, int nLines)
988 int pos, gapLen = buf->gapEnd - buf->gapStart;
989 int lineCount = 0;
991 if (nLines == 0)
992 return startPos;
994 pos = startPos;
995 while (pos < buf->gapStart) {
996 if (buf->buf[pos++] == '\n') {
997 lineCount++;
998 if (lineCount == nLines)
999 return pos;
1002 while (pos < buf->length) {
1003 if (buf->buf[pos++ + gapLen] == '\n') {
1004 lineCount++;
1005 if (lineCount >= nLines)
1006 return pos;
1009 return pos;
1013 ** Find the position of the first character of the line "nLines" backwards
1014 ** from "startPos" (not counting the character pointed to by "startpos" if
1015 ** that is a newline) in "buf". nLines == 0 means find the beginning of
1016 ** the line
1018 int BufCountBackwardNLines(textBuffer *buf, int startPos, int nLines)
1020 int pos, gapLen = buf->gapEnd - buf->gapStart;
1021 int lineCount = -1;
1023 pos = startPos - 1;
1024 if (pos <= 0)
1025 return 0;
1027 while (pos >= buf->gapStart) {
1028 if (buf->buf[pos + gapLen] == '\n') {
1029 if (++lineCount >= nLines)
1030 return pos + 1;
1032 pos--;
1034 while (pos >= 0) {
1035 if (buf->buf[pos] == '\n') {
1036 if (++lineCount >= nLines)
1037 return pos + 1;
1039 pos--;
1041 return 0;
1045 ** Search forwards in buffer "buf" for characters in "searchChars", starting
1046 ** with the character "startPos", and returning the result in "foundPos"
1047 ** returns True if found, False if not.
1049 int BufSearchForward(textBuffer *buf, int startPos, char *searchChars,
1050 int *foundPos)
1052 int pos, gapLen = buf->gapEnd - buf->gapStart;
1053 char *c;
1055 pos = startPos;
1056 while (pos < buf->gapStart) {
1057 for (c=searchChars; *c!='\0'; c++) {
1058 if (buf->buf[pos] == *c) {
1059 *foundPos = pos;
1060 return True;
1063 pos++;
1065 while (pos < buf->length) {
1066 for (c=searchChars; *c!='\0'; c++) {
1067 if (buf->buf[pos + gapLen] == *c) {
1068 *foundPos = pos;
1069 return True;
1072 pos++;
1074 *foundPos = buf->length;
1075 return False;
1079 ** Search backwards in buffer "buf" for characters in "searchChars", starting
1080 ** with the character BEFORE "startPos", returning the result in "foundPos"
1081 ** returns True if found, False if not.
1083 int BufSearchBackward(textBuffer *buf, int startPos, char *searchChars,
1084 int *foundPos)
1086 int pos, gapLen = buf->gapEnd - buf->gapStart;
1087 char *c;
1089 if (startPos == 0) {
1090 *foundPos = 0;
1091 return False;
1093 pos = startPos == 0 ? 0 : startPos - 1;
1094 while (pos >= buf->gapStart) {
1095 for (c=searchChars; *c!='\0'; c++) {
1096 if (buf->buf[pos + gapLen] == *c) {
1097 *foundPos = pos;
1098 return True;
1101 pos--;
1103 while (pos >= 0) {
1104 for (c=searchChars; *c!='\0'; c++) {
1105 if (buf->buf[pos] == *c) {
1106 *foundPos = pos;
1107 return True;
1110 pos--;
1112 *foundPos = 0;
1113 return False;
1117 ** A horrible design flaw in NEdit (from the very start, before we knew that
1118 ** NEdit would become so popular), is that it uses C NULL terminated strings
1119 ** to hold text. This means editing text containing NUL characters is not
1120 ** possible without special consideration. Here is the special consideration.
1121 ** The routines below maintain a special substitution-character which stands
1122 ** in for a null, and translates strings an buffers back and forth from/to
1123 ** the substituted form, figure out what to substitute, and figure out
1124 ** when we're in over our heads and no translation is possible.
1128 ** The primary routine for integrating new text into a text buffer with
1129 ** substitution of another character for ascii nuls. This substitutes null
1130 ** characters in the string in preparation for being copied or replaced
1131 ** into the buffer, and if neccessary, adjusts the buffer as well, in the
1132 ** event that the string contains the character it is currently using for
1133 ** substitution. Returns False, if substitution is no longer possible
1134 ** because all non-printable characters are already in use.
1136 int BufSubstituteNullChars(char *string, int length, textBuffer *buf)
1138 char histogram[256];
1140 /* Find out what characters the string contains */
1141 histogramCharacters(string, length, histogram, True);
1143 /* Does the string contain the null-substitute character? If so, re-
1144 histogram the buffer text to find a character which is ok in both the
1145 string and the buffer, and change the buffer's null-substitution
1146 character. If none can be found, give up and return False */
1147 if (histogram[(unsigned char)buf->nullSubsChar] != 0) {
1148 char *bufString, newSubsChar;
1149 bufString = BufGetAll(buf);
1150 histogramCharacters(bufString, buf->length, histogram, False);
1151 newSubsChar = chooseNullSubsChar(histogram);
1152 if (newSubsChar == '\0') {
1153 XtFree(bufString);
1154 return False;
1156 subsChars(bufString, buf->length, buf->nullSubsChar, newSubsChar);
1157 delete(buf, 0, buf->length);
1158 insert(buf, 0, bufString);
1159 XtFree(bufString);
1160 buf->nullSubsChar = newSubsChar;
1163 /* If the string contains null characters, substitute them with the
1164 buffer's null substitution character */
1165 if (histogram[0] != 0)
1166 subsChars(string, length, '\0', buf->nullSubsChar);
1167 return True;
1171 ** Convert strings obtained from buffers which contain null characters, which
1172 ** have been substituted for by a special substitution character, back to
1173 ** a null-containing string. There is no time penalty for calling this
1174 ** routine if no substitution has been done.
1176 void BufUnsubstituteNullChars(char *string, textBuffer *buf)
1178 register char *c, subsChar = buf->nullSubsChar;
1180 if (subsChar == '\0')
1181 return;
1182 for (c=string; *c != '\0'; c++)
1183 if (*c == subsChar)
1184 *c = '\0';
1188 ** Create a pseudo-histogram of the characters in a string (don't actually
1189 ** count, because we don't want overflow, just mark the character's presence
1190 ** with a 1). If init is true, initialize the histogram before acumulating.
1191 ** if not, add the new data to an existing histogram.
1193 static void histogramCharacters(const char *string, int length, char hist[256],
1194 int init)
1196 int i;
1197 const char *c;
1199 if (init)
1200 for (i=0; i<256; i++)
1201 hist[i] = 0;
1202 for (c=string; c < &string[length]; c++)
1203 hist[*((unsigned char *)c)] |= 1;
1207 ** Substitute fromChar with toChar in string.
1209 static void subsChars(char *string, int length, char fromChar, char toChar)
1211 char *c;
1213 for (c=string; c < &string[length]; c++)
1214 if (*c == fromChar) *c = toChar;
1218 ** Search through ascii control characters in histogram in order of least
1219 ** likelihood of use, find an unused character to use as a stand-in for a
1220 ** null. If the character set is full (no available characters outside of
1221 ** the printable set, return the null character.
1223 static char chooseNullSubsChar(char hist[256])
1225 #define N_REPLACEMENTS 25
1226 static char replacements[N_REPLACEMENTS] = {1,2,3,4,5,6,14,15,16,17,18,19,
1227 20,21,22,23,24,25,26,28,29,30,31,11,7};
1228 int i;
1229 for (i = 0; i < N_REPLACEMENTS; i++)
1230 if (hist[(unsigned char)replacements[i]] == 0)
1231 return replacements[i];
1232 return '\0';
1236 ** Internal (non-redisplaying) version of BufInsert. Returns the length of
1237 ** text inserted (this is just strlen(text), however this calculation can be
1238 ** expensive and the length will be required by any caller who will continue
1239 ** on to call redisplay). pos must be contiguous with the existing text in
1240 ** the buffer (i.e. not past the end).
1242 static int insert(textBuffer *buf, int pos, const char *text)
1244 int length = strlen(text);
1246 /* Prepare the buffer to receive the new text. If the new text fits in
1247 the current buffer, just move the gap (if necessary) to where
1248 the text should be inserted. If the new text is too large, reallocate
1249 the buffer with a gap large enough to accomodate the new text and a
1250 gap of PREFERRED_GAP_SIZE */
1251 if (length > buf->gapEnd - buf->gapStart)
1252 reallocateBuf(buf, pos, length + PREFERRED_GAP_SIZE);
1253 else if (pos != buf->gapStart)
1254 moveGap(buf, pos);
1256 /* Insert the new text (pos now corresponds to the start of the gap) */
1257 memcpy(&buf->buf[pos], text, length);
1258 buf->gapStart += length;
1259 buf->length += length;
1260 updateSelections(buf, pos, 0, length);
1262 return length;
1266 ** Internal (non-redisplaying) version of BufRemove. Removes the contents
1267 ** of the buffer between start and end (and moves the gap to the site of
1268 ** the delete).
1270 static void delete(textBuffer *buf, int start, int end)
1272 /* if the gap is not contiguous to the area to remove, move it there */
1273 if (start > buf->gapStart)
1274 moveGap(buf, start);
1275 else if (end < buf->gapStart)
1276 moveGap(buf, end);
1278 /* expand the gap to encompass the deleted characters */
1279 buf->gapEnd += end - buf->gapStart;
1280 buf->gapStart -= buf->gapStart - start;
1282 /* update the length */
1283 buf->length -= end - start;
1285 /* fix up any selections which might be affected by the change */
1286 updateSelections(buf, start, end-start, 0);
1290 ** Insert a column of text without calling the modify callbacks. Note that
1291 ** in some pathological cases, inserting can actually decrease the size of
1292 ** the buffer because of spaces being coalesced into tabs. "nDeleted" and
1293 ** "nInserted" return the number of characters deleted and inserted beginning
1294 ** at the start of the line containing "startPos". "endPos" returns buffer
1295 ** position of the lower left edge of the inserted column (as a hint for
1296 ** routines which need to set a cursor position).
1298 static void insertCol(textBuffer *buf, int column, int startPos, const char *insText,
1299 int *nDeleted, int *nInserted, int *endPos)
1301 int nLines, start, end, insWidth, lineStart, lineEnd;
1302 int expReplLen, expInsLen, len, endOffset;
1303 char *outStr, *outPtr, *line, *replText, *expText, *insLine;
1304 const char *insPtr;
1306 if (column < 0)
1307 column = 0;
1309 /* Allocate a buffer for the replacement string large enough to hold
1310 possibly expanded tabs in both the inserted text and the replaced
1311 area, as well as per line: 1) an additional 2*MAX_EXP_CHAR_LEN
1312 characters for padding where tabs and control characters cross the
1313 column of the selection, 2) up to "column" additional spaces per
1314 line for padding out to the position of "column", 3) padding up
1315 to the width of the inserted text if that must be padded to align
1316 the text beyond the inserted column. (Space for additional
1317 newlines if the inserted text extends beyond the end of the buffer
1318 is counted with the length of insText) */
1319 start = BufStartOfLine(buf, startPos);
1320 nLines = countLines(insText) + 1;
1321 insWidth = textWidth(insText, buf->tabDist, buf->nullSubsChar);
1322 end = BufEndOfLine(buf, BufCountForwardNLines(buf, start, nLines-1));
1323 replText = BufGetRange(buf, start, end);
1324 expText = expandTabs(replText, 0, buf->tabDist, buf->nullSubsChar,
1325 &expReplLen);
1326 XtFree(replText);
1327 XtFree(expText);
1328 expText = expandTabs(insText, 0, buf->tabDist, buf->nullSubsChar,
1329 &expInsLen);
1330 XtFree(expText);
1331 outStr = XtMalloc(expReplLen + expInsLen +
1332 nLines * (column + insWidth + MAX_EXP_CHAR_LEN) + 1);
1334 /* Loop over all lines in the buffer between start and end inserting
1335 text at column, splitting tabs and adding padding appropriately */
1336 outPtr = outStr;
1337 lineStart = start;
1338 insPtr = insText;
1339 while (True) {
1340 lineEnd = BufEndOfLine(buf, lineStart);
1341 line = BufGetRange(buf, lineStart, lineEnd);
1342 insLine = copyLine(insPtr, &len);
1343 insPtr += len;
1344 insertColInLine(line, insLine, column, insWidth, buf->tabDist,
1345 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1346 XtFree(line);
1347 XtFree(insLine);
1348 #if 0 /* Earlier comments claimed that trailing whitespace could multiply on
1349 the ends of lines, but insertColInLine looks like it should never
1350 add space unnecessarily, and this trimming interfered with
1351 paragraph filling, so lets see if it works without it. MWE */
1353 char *c;
1354 for (c=outPtr+len-1; c>outPtr && isspace((unsigned char)*c); c--)
1355 len--;
1357 #endif
1358 outPtr += len;
1359 *outPtr++ = '\n';
1360 lineStart = lineEnd < buf->length ? lineEnd + 1 : buf->length;
1361 if (*insPtr == '\0')
1362 break;
1363 insPtr++;
1365 if (outPtr != outStr)
1366 outPtr--; /* trim back off extra newline */
1367 *outPtr = '\0';
1369 /* replace the text between start and end with the new stuff */
1370 delete(buf, start, end);
1371 insert(buf, start, outStr);
1372 *nInserted = outPtr - outStr;
1373 *nDeleted = end - start;
1374 *endPos = start + (outPtr - outStr) - len + endOffset;
1375 XtFree(outStr);
1379 ** Delete a rectangle of text without calling the modify callbacks. Returns
1380 ** the number of characters replacing those between start and end. Note that
1381 ** in some pathological cases, deleting can actually increase the size of
1382 ** the buffer because of tab expansions. "endPos" returns the buffer position
1383 ** of the point in the last line where the text was removed (as a hint for
1384 ** routines which need to position the cursor after a delete operation)
1386 static void deleteRect(textBuffer *buf, int start, int end, int rectStart,
1387 int rectEnd, int *replaceLen, int *endPos)
1389 int nLines, lineStart, lineEnd, len, endOffset;
1390 char *outStr, *outPtr, *line, *text, *expText;
1392 /* allocate a buffer for the replacement string large enough to hold
1393 possibly expanded tabs as well as an additional MAX_EXP_CHAR_LEN * 2
1394 characters per line for padding where tabs and control characters cross
1395 the edges of the selection */
1396 start = BufStartOfLine(buf, start);
1397 end = BufEndOfLine(buf, end);
1398 nLines = BufCountLines(buf, start, end) + 1;
1399 text = BufGetRange(buf, start, end);
1400 expText = expandTabs(text, 0, buf->tabDist, buf->nullSubsChar, &len);
1401 XtFree(text);
1402 XtFree(expText);
1403 outStr = XtMalloc(len + nLines * MAX_EXP_CHAR_LEN * 2 + 1);
1405 /* loop over all lines in the buffer between start and end removing
1406 the text between rectStart and rectEnd and padding appropriately */
1407 lineStart = start;
1408 outPtr = outStr;
1409 while (lineStart <= buf->length && lineStart <= end) {
1410 lineEnd = BufEndOfLine(buf, lineStart);
1411 line = BufGetRange(buf, lineStart, lineEnd);
1412 deleteRectFromLine(line, rectStart, rectEnd, buf->tabDist,
1413 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1414 XtFree(line);
1415 outPtr += len;
1416 *outPtr++ = '\n';
1417 lineStart = lineEnd + 1;
1419 if (outPtr != outStr)
1420 outPtr--; /* trim back off extra newline */
1421 *outPtr = '\0';
1423 /* replace the text between start and end with the newly created string */
1424 delete(buf, start, end);
1425 insert(buf, start, outStr);
1426 *replaceLen = outPtr - outStr;
1427 *endPos = start + (outPtr - outStr) - len + endOffset;
1428 XtFree(outStr);
1432 ** Overlay a rectangular area of text without calling the modify callbacks.
1433 ** "nDeleted" and "nInserted" return the number of characters deleted and
1434 ** inserted beginning at the start of the line containing "startPos".
1435 ** "endPos" returns buffer position of the lower left edge of the inserted
1436 ** column (as a hint for routines which need to set a cursor position).
1438 static void overlayRect(textBuffer *buf, int startPos, int rectStart,
1439 int rectEnd, char *insText, int *nDeleted, int *nInserted, int *endPos)
1441 int nLines, start, end, lineStart, lineEnd;
1442 int expInsLen, len, endOffset;
1443 char *c, *outStr, *outPtr, *line, *expText, *insLine, *insPtr;
1445 /* Allocate a buffer for the replacement string large enough to hold
1446 possibly expanded tabs in the inserted text, as well as per line: 1)
1447 an additional 2*MAX_EXP_CHAR_LEN characters for padding where tabs
1448 and control characters cross the column of the selection, 2) up to
1449 "column" additional spaces per line for padding out to the position
1450 of "column", 3) padding up to the width of the inserted text if that
1451 must be padded to align the text beyond the inserted column. (Space
1452 for additional newlines if the inserted text extends beyond the end
1453 of the buffer is counted with the length of insText) */
1454 start = BufStartOfLine(buf, startPos);
1455 nLines = countLines(insText) + 1;
1456 end = BufEndOfLine(buf, BufCountForwardNLines(buf, start, nLines-1));
1457 expText = expandTabs(insText, 0, buf->tabDist, buf->nullSubsChar,
1458 &expInsLen);
1459 XtFree(expText);
1460 outStr = XtMalloc(end-start + expInsLen +
1461 nLines * (rectEnd + MAX_EXP_CHAR_LEN) + 1);
1463 /* Loop over all lines in the buffer between start and end overlaying the
1464 text between rectStart and rectEnd and padding appropriately. Trim
1465 trailing space from line (whitespace at the ends of lines otherwise
1466 tends to multiply, since additional padding is added to maintain it */
1467 outPtr = outStr;
1468 lineStart = start;
1469 insPtr = insText;
1470 while (True) {
1471 lineEnd = BufEndOfLine(buf, lineStart);
1472 line = BufGetRange(buf, lineStart, lineEnd);
1473 insLine = copyLine(insPtr, &len);
1474 insPtr += len;
1475 overlayRectInLine(line, insLine, rectStart, rectEnd, buf->tabDist,
1476 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1477 XtFree(line);
1478 XtFree(insLine);
1479 for (c=outPtr+len-1; c>outPtr && isspace((unsigned char)*c); c--)
1480 len--;
1481 outPtr += len;
1482 *outPtr++ = '\n';
1483 lineStart = lineEnd < buf->length ? lineEnd + 1 : buf->length;
1484 if (*insPtr == '\0')
1485 break;
1486 insPtr++;
1488 if (outPtr != outStr)
1489 outPtr--; /* trim back off extra newline */
1490 *outPtr = '\0';
1492 /* replace the text between start and end with the new stuff */
1493 delete(buf, start, end);
1494 insert(buf, start, outStr);
1495 *nInserted = outPtr - outStr;
1496 *nDeleted = end - start;
1497 *endPos = start + (outPtr - outStr) - len + endOffset;
1498 XtFree(outStr);
1502 ** Insert characters from single-line string "insLine" in single-line string
1503 ** "line" at "column", leaving "insWidth" space before continuing line.
1504 ** "outLen" returns the number of characters written to "outStr", "endOffset"
1505 ** returns the number of characters from the beginning of the string to
1506 ** the right edge of the inserted text (as a hint for routines which need
1507 ** to position the cursor).
1509 static void insertColInLine(const char *line, const char *insLine, int column, int insWidth,
1510 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
1511 int *endOffset)
1513 char *c, *outPtr, *retabbedStr;
1514 const char *linePtr;
1515 int indent, toIndent, len, postColIndent;
1517 /* copy the line up to "column" */
1518 outPtr = outStr;
1519 indent = 0;
1520 for (linePtr=line; *linePtr!='\0'; linePtr++) {
1521 len = BufCharWidth(*linePtr, indent, tabDist, nullSubsChar);
1522 if (indent + len > column)
1523 break;
1524 indent += len;
1525 *outPtr++ = *linePtr;
1528 /* If "column" falls in the middle of a character, and the character is a
1529 tab, leave it off and leave the indent short and it will get padded
1530 later. If it's a control character, insert it and adjust indent
1531 accordingly. */
1532 if (indent < column && *linePtr != '\0') {
1533 postColIndent = indent + len;
1534 if (*linePtr == '\t')
1535 linePtr++;
1536 else {
1537 *outPtr++ = *linePtr++;
1538 indent += len;
1540 } else
1541 postColIndent = indent;
1543 /* If there's no text after the column and no text to insert, that's all */
1544 if (*insLine == '\0' && *linePtr == '\0') {
1545 *outLen = *endOffset = outPtr - outStr;
1546 return;
1549 /* pad out to column if text is too short */
1550 if (indent < column) {
1551 addPadding(outPtr, indent, column, tabDist, useTabs, nullSubsChar,&len);
1552 outPtr += len;
1553 indent = column;
1556 /* Copy the text from "insLine" (if any), recalculating the tabs as if
1557 the inserted string began at column 0 to its new column destination */
1558 if (*insLine != '\0') {
1559 retabbedStr = realignTabs(insLine, 0, indent, tabDist, useTabs,
1560 nullSubsChar, &len);
1561 for (c=retabbedStr; *c!='\0'; c++) {
1562 *outPtr++ = *c;
1563 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
1564 indent += len;
1566 XtFree(retabbedStr);
1569 /* If the original line did not extend past "column", that's all */
1570 if (*linePtr == '\0') {
1571 *outLen = *endOffset = outPtr - outStr;
1572 return;
1575 /* Pad out to column + width of inserted text + (additional original
1576 offset due to non-breaking character at column) */
1577 toIndent = column + insWidth + postColIndent-column;
1578 addPadding(outPtr, indent, toIndent, tabDist, useTabs, nullSubsChar, &len);
1579 outPtr += len;
1580 indent = toIndent;
1582 /* realign tabs for text beyond "column" and write it out */
1583 retabbedStr = realignTabs(linePtr, postColIndent, indent, tabDist,
1584 useTabs, nullSubsChar, &len);
1585 strcpy(outPtr, retabbedStr);
1586 XtFree(retabbedStr);
1587 *endOffset = outPtr - outStr;
1588 *outLen = (outPtr - outStr) + len;
1592 ** Remove characters in single-line string "line" between displayed positions
1593 ** "rectStart" and "rectEnd", and write the result to "outStr", which is
1594 ** assumed to be large enough to hold the returned string. Note that in
1595 ** certain cases, it is possible for the string to get longer due to
1596 ** expansion of tabs. "endOffset" returns the number of characters from
1597 ** the beginning of the string to the point where the characters were
1598 ** deleted (as a hint for routines which need to position the cursor).
1600 static void deleteRectFromLine(const char *line, int rectStart, int rectEnd,
1601 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
1602 int *endOffset)
1604 int indent, preRectIndent, postRectIndent, len;
1605 const char *c;
1606 char *outPtr;
1607 char *retabbedStr;
1609 /* copy the line up to rectStart */
1610 outPtr = outStr;
1611 indent = 0;
1612 for (c=line; *c!='\0'; c++) {
1613 if (indent > rectStart)
1614 break;
1615 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
1616 if (indent + len > rectStart && (indent == rectStart || *c == '\t'))
1617 break;
1618 indent += len;
1619 *outPtr++ = *c;
1621 preRectIndent = indent;
1623 /* skip the characters between rectStart and rectEnd */
1624 for(; *c!='\0' && indent<rectEnd; c++)
1625 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
1626 postRectIndent = indent;
1628 /* If the line ended before rectEnd, there's nothing more to do */
1629 if (*c == '\0') {
1630 *outPtr = '\0';
1631 *outLen = *endOffset = outPtr - outStr;
1632 return;
1635 /* fill in any space left by removed tabs or control characters
1636 which straddled the boundaries */
1637 indent = max(rectStart + postRectIndent-rectEnd, preRectIndent);
1638 addPadding(outPtr, preRectIndent, indent, tabDist, useTabs, nullSubsChar,
1639 &len);
1640 outPtr += len;
1642 /* Copy the rest of the line. If the indentation has changed, preserve
1643 the position of non-whitespace characters by converting tabs to
1644 spaces, then back to tabs with the correct offset */
1645 retabbedStr = realignTabs(c, postRectIndent, indent, tabDist, useTabs,
1646 nullSubsChar, &len);
1647 strcpy(outPtr, retabbedStr);
1648 XtFree(retabbedStr);
1649 *endOffset = outPtr - outStr;
1650 *outLen = (outPtr - outStr) + len;
1654 ** Overlay characters from single-line string "insLine" on single-line string
1655 ** "line" between displayed character offsets "rectStart" and "rectEnd".
1656 ** "outLen" returns the number of characters written to "outStr", "endOffset"
1657 ** returns the number of characters from the beginning of the string to
1658 ** the right edge of the inserted text (as a hint for routines which need
1659 ** to position the cursor).
1661 static void overlayRectInLine(const char *line, const char *insLine, int rectStart,
1662 int rectEnd, int tabDist, int useTabs, char nullSubsChar, char *outStr,
1663 int *outLen, int *endOffset)
1665 char *c, *outPtr, *retabbedStr;
1666 const char *linePtr;
1667 int inIndent, outIndent, len, postRectIndent;
1669 /* copy the line up to "rectStart" */
1670 outPtr = outStr;
1671 inIndent = outIndent = 0;
1672 for (linePtr=line; *linePtr!='\0'; linePtr++) {
1673 len = BufCharWidth(*linePtr, inIndent, tabDist, nullSubsChar);
1674 if (inIndent + len > rectStart)
1675 break;
1676 inIndent += len;
1677 outIndent += len;
1678 *outPtr++ = *linePtr;
1681 /* If "rectStart" falls in the middle of a character, and the character
1682 is a tab, leave it off and leave the outIndent short and it will get
1683 padded later. If it's a control character, insert it and adjust
1684 outIndent accordingly. */
1685 if (inIndent < rectStart && *linePtr != '\0') {
1686 if (*linePtr == '\t') {
1687 linePtr++;
1688 inIndent += len;
1689 } else {
1690 *outPtr++ = *linePtr++;
1691 outIndent += len;
1692 inIndent += len;
1696 /* skip the characters between rectStart and rectEnd */
1697 postRectIndent = rectEnd;
1698 for(; *linePtr!='\0'; linePtr++) {
1699 inIndent += BufCharWidth(*linePtr, inIndent, tabDist, nullSubsChar);
1700 if (inIndent >= rectEnd) {
1701 linePtr++;
1702 postRectIndent = inIndent;
1703 break;
1707 /* If there's no text after rectStart and no text to insert, that's all */
1708 if (*insLine == '\0' && *linePtr == '\0') {
1709 *outLen = *endOffset = outPtr - outStr;
1710 return;
1713 /* pad out to rectStart if text is too short */
1714 if (outIndent < rectStart) {
1715 addPadding(outPtr, outIndent, rectStart, tabDist, useTabs, nullSubsChar,
1716 &len);
1717 outPtr += len;
1719 outIndent = rectStart;
1721 /* Copy the text from "insLine" (if any), recalculating the tabs as if
1722 the inserted string began at column 0 to its new column destination */
1723 if (*insLine != '\0') {
1724 retabbedStr = realignTabs(insLine, 0, rectStart, tabDist, useTabs,
1725 nullSubsChar, &len);
1726 for (c=retabbedStr; *c!='\0'; c++) {
1727 *outPtr++ = *c;
1728 len = BufCharWidth(*c, outIndent, tabDist, nullSubsChar);
1729 outIndent += len;
1731 XtFree(retabbedStr);
1734 /* If the original line did not extend past "rectStart", that's all */
1735 if (*linePtr == '\0') {
1736 *outLen = *endOffset = outPtr - outStr;
1737 return;
1740 /* Pad out to rectEnd + (additional original offset
1741 due to non-breaking character at right boundary) */
1742 addPadding(outPtr, outIndent, postRectIndent, tabDist, useTabs,
1743 nullSubsChar, &len);
1744 outPtr += len;
1745 outIndent = postRectIndent;
1747 /* copy the text beyond "rectEnd" */
1748 strcpy(outPtr, linePtr);
1749 *endOffset = outPtr - outStr;
1750 *outLen = (outPtr - outStr) + strlen(linePtr);
1753 static void setSelection(selection *sel, int start, int end)
1755 sel->selected = start != end;
1756 sel->rectangular = False;
1757 sel->start = min(start, end);
1758 sel->end = max(start, end);
1761 static void setRectSelect(selection *sel, int start, int end,
1762 int rectStart, int rectEnd)
1764 sel->selected = rectStart < rectEnd;
1765 sel->rectangular = True;
1766 sel->start = start;
1767 sel->end = end;
1768 sel->rectStart = rectStart;
1769 sel->rectEnd = rectEnd;
1772 static int getSelectionPos(selection *sel, int *start, int *end,
1773 int *isRect, int *rectStart, int *rectEnd)
1775 if (!sel->selected)
1776 return False;
1777 *isRect = sel->rectangular;
1778 *start = sel->start;
1779 *end = sel->end;
1780 if (sel->rectangular) {
1781 *rectStart = sel->rectStart;
1782 *rectEnd = sel->rectEnd;
1784 return True;
1787 static char *getSelectionText(textBuffer *buf, selection *sel)
1789 int start, end, isRect, rectStart, rectEnd;
1790 char *text;
1792 /* If there's no selection, return an allocated empty string */
1793 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd)) {
1794 text = XtMalloc(1);
1795 *text = '\0';
1796 return text;
1799 /* If the selection is not rectangular, return the selected range */
1800 if (isRect)
1801 return BufGetTextInRect(buf, start, end, rectStart, rectEnd);
1802 else
1803 return BufGetRange(buf, start, end);
1806 static void removeSelected(textBuffer *buf, selection *sel)
1808 int start, end;
1809 int isRect, rectStart, rectEnd;
1811 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd))
1812 return;
1813 if (isRect)
1814 BufRemoveRect(buf, start, end, rectStart, rectEnd);
1815 else
1816 BufRemove(buf, start, end);
1819 static void replaceSelected(textBuffer *buf, selection *sel, const char *text)
1821 int start, end, isRect, rectStart, rectEnd;
1822 selection oldSelection = *sel;
1824 /* If there's no selection, return */
1825 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd))
1826 return;
1828 /* Do the appropriate type of replace */
1829 if (isRect)
1830 BufReplaceRect(buf, start, end, rectStart, rectEnd, text);
1831 else
1832 BufReplace(buf, start, end, text);
1834 /* Unselect (happens automatically in BufReplace, but BufReplaceRect
1835 can't detect when the contents of a selection goes away) */
1836 sel->selected = False;
1837 redisplaySelection(buf, &oldSelection, sel);
1840 static void addPadding(char *string, int startIndent, int toIndent,
1841 int tabDist, int useTabs, char nullSubsChar, int *charsAdded)
1843 char *outPtr;
1844 int len, indent;
1846 indent = startIndent;
1847 outPtr = string;
1848 if (useTabs) {
1849 while (indent < toIndent) {
1850 len = BufCharWidth('\t', indent, tabDist, nullSubsChar);
1851 if (len > 1 && indent + len <= toIndent) {
1852 *outPtr++ = '\t';
1853 indent += len;
1854 } else {
1855 *outPtr++ = ' ';
1856 indent++;
1859 } else {
1860 while (indent < toIndent) {
1861 *outPtr++ = ' ';
1862 indent++;
1865 *charsAdded = outPtr - string;
1869 ** Call the stored modify callback procedure(s) for this buffer to update the
1870 ** changed area(s) on the screen and any other listeners.
1872 static void callModifyCBs(textBuffer *buf, int pos, int nDeleted,
1873 int nInserted, int nRestyled, char *deletedText)
1875 int i;
1877 for (i=0; i<buf->nModifyProcs; i++)
1878 (*buf->modifyProcs[i])(pos, nInserted, nDeleted, nRestyled,
1879 deletedText, buf->cbArgs[i]);
1883 ** Call the stored redisplay procedure(s) for this buffer to update the
1884 ** screen for a change in a selection.
1886 static void redisplaySelection(textBuffer *buf, selection *oldSelection,
1887 selection *newSelection)
1889 int oldStart, oldEnd, newStart, newEnd, ch1Start, ch1End, ch2Start, ch2End;
1891 /* If either selection is rectangular, add an additional character to
1892 the end of the selection to request the redraw routines to wipe out
1893 the parts of the selection beyond the end of the line */
1894 oldStart = oldSelection->start;
1895 newStart = newSelection->start;
1896 oldEnd = oldSelection->end;
1897 newEnd = newSelection->end;
1898 if (oldSelection->rectangular)
1899 oldEnd++;
1900 if (newSelection->rectangular)
1901 newEnd++;
1903 /* If the old or new selection is unselected, just redisplay the
1904 single area that is (was) selected and return */
1905 if (!oldSelection->selected && !newSelection->selected)
1906 return;
1907 if (!oldSelection->selected) {
1908 callModifyCBs(buf, newStart, 0, 0, newEnd-newStart, NULL);
1909 return;
1911 if (!newSelection->selected) {
1912 callModifyCBs(buf, oldStart, 0, 0, oldEnd-oldStart, NULL);
1913 return;
1916 /* If the selection changed from normal to rectangular or visa versa, or
1917 if a rectangular selection changed boundaries, redisplay everything */
1918 if ((oldSelection->rectangular && !newSelection->rectangular) ||
1919 (!oldSelection->rectangular && newSelection->rectangular) ||
1920 (oldSelection->rectangular && (
1921 (oldSelection->rectStart != newSelection->rectStart) ||
1922 (oldSelection->rectEnd != newSelection->rectEnd)))) {
1923 callModifyCBs(buf, min(oldStart, newStart), 0, 0,
1924 max(oldEnd, newEnd) - min(oldStart, newStart), NULL);
1925 return;
1928 /* If the selections are non-contiguous, do two separate updates
1929 and return */
1930 if (oldEnd < newStart || newEnd < oldStart) {
1931 callModifyCBs(buf, oldStart, 0, 0, oldEnd-oldStart, NULL);
1932 callModifyCBs(buf, newStart, 0, 0, newEnd-newStart, NULL);
1933 return;
1936 /* Otherwise, separate into 3 separate regions: ch1, and ch2 (the two
1937 changed areas), and the unchanged area of their intersection,
1938 and update only the changed area(s) */
1939 ch1Start = min(oldStart, newStart);
1940 ch2End = max(oldEnd, newEnd);
1941 ch1End = max(oldStart, newStart);
1942 ch2Start = min(oldEnd, newEnd);
1943 if (ch1Start != ch1End)
1944 callModifyCBs(buf, ch1Start, 0, 0, ch1End-ch1Start, NULL);
1945 if (ch2Start != ch2End)
1946 callModifyCBs(buf, ch2Start, 0, 0, ch2End-ch2Start, NULL);
1949 static void moveGap(textBuffer *buf, int pos)
1951 int gapLen = buf->gapEnd - buf->gapStart;
1953 if (pos > buf->gapStart)
1954 memmove(&buf->buf[buf->gapStart], &buf->buf[buf->gapEnd],
1955 pos - buf->gapStart);
1956 else
1957 memmove(&buf->buf[pos + gapLen], &buf->buf[pos], buf->gapStart - pos);
1958 buf->gapEnd += pos - buf->gapStart;
1959 buf->gapStart += pos - buf->gapStart;
1963 ** reallocate the text storage in "buf" to have a gap starting at "newGapStart"
1964 ** and a gap size of "newGapLen", preserving the buffer's current contents.
1966 static void reallocateBuf(textBuffer *buf, int newGapStart, int newGapLen)
1968 char *newBuf;
1969 int newGapEnd;
1971 newBuf = XtMalloc(buf->length + newGapLen);
1972 newGapEnd = newGapStart + newGapLen;
1973 if (newGapStart <= buf->gapStart) {
1974 memcpy(newBuf, buf->buf, newGapStart);
1975 memcpy(&newBuf[newGapEnd], &buf->buf[newGapStart],
1976 buf->gapStart - newGapStart);
1977 memcpy(&newBuf[newGapEnd + buf->gapStart - newGapStart],
1978 &buf->buf[buf->gapEnd], buf->length - buf->gapStart);
1979 } else { /* newGapStart > buf->gapStart */
1980 memcpy(newBuf, buf->buf, buf->gapStart);
1981 memcpy(&newBuf[buf->gapStart], &buf->buf[buf->gapEnd],
1982 newGapStart - buf->gapStart);
1983 memcpy(&newBuf[newGapEnd],
1984 &buf->buf[buf->gapEnd + newGapStart - buf->gapStart],
1985 buf->length - newGapStart);
1987 XtFree(buf->buf);
1988 buf->buf = newBuf;
1989 buf->gapStart = newGapStart;
1990 buf->gapEnd = newGapEnd;
1991 #ifdef PURIFY
1992 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
1993 #endif
1997 ** Update all of the selections in "buf" for changes in the buffer's text
1999 static void updateSelections(textBuffer *buf, int pos, int nDeleted,
2000 int nInserted)
2002 updateSelection(&buf->primary, pos, nDeleted, nInserted);
2003 updateSelection(&buf->secondary, pos, nDeleted, nInserted);
2004 updateSelection(&buf->highlight, pos, nDeleted, nInserted);
2008 ** Update an individual selection for changes in the corresponding text
2010 static void updateSelection(selection *sel, int pos, int nDeleted,
2011 int nInserted)
2013 if (!sel->selected || pos > sel->end)
2014 return;
2015 if (pos+nDeleted <= sel->start) {
2016 sel->start += nInserted - nDeleted;
2017 sel->end += nInserted - nDeleted;
2018 } else if (pos <= sel->start && pos+nDeleted >= sel->end) {
2019 sel->start = pos;
2020 sel->end = pos;
2021 sel->selected = False;
2022 } else if (pos <= sel->start && pos+nDeleted < sel->end) {
2023 sel->start = pos;
2024 sel->end = nInserted + sel->end - nDeleted;
2025 } else if (pos < sel->end) {
2026 sel->end += nInserted - nDeleted;
2027 if (sel->end <= sel->start)
2028 sel->selected = False;
2033 ** Search forwards in buffer "buf" for character "searchChar", starting
2034 ** with the character "startPos", and returning the result in "foundPos"
2035 ** returns True if found, False if not. (The difference between this and
2036 ** BufSearchForward is that it's optimized for single characters. The
2037 ** overall performance of the text widget is dependent on its ability to
2038 ** count lines quickly, hence searching for a single character: newline)
2040 static int searchForward(textBuffer *buf, int startPos, char searchChar,
2041 int *foundPos)
2043 int pos, gapLen = buf->gapEnd - buf->gapStart;
2045 pos = startPos;
2046 while (pos < buf->gapStart) {
2047 if (buf->buf[pos] == searchChar) {
2048 *foundPos = pos;
2049 return True;
2051 pos++;
2053 while (pos < buf->length) {
2054 if (buf->buf[pos + gapLen] == searchChar) {
2055 *foundPos = pos;
2056 return True;
2058 pos++;
2060 *foundPos = buf->length;
2061 return False;
2065 ** Search backwards in buffer "buf" for character "searchChar", starting
2066 ** with the character BEFORE "startPos", returning the result in "foundPos"
2067 ** returns True if found, False if not. (The difference between this and
2068 ** BufSearchBackward is that it's optimized for single characters. The
2069 ** overall performance of the text widget is dependent on its ability to
2070 ** count lines quickly, hence searching for a single character: newline)
2072 static int searchBackward(textBuffer *buf, int startPos, char searchChar,
2073 int *foundPos)
2075 int pos, gapLen = buf->gapEnd - buf->gapStart;
2077 if (startPos == 0) {
2078 *foundPos = 0;
2079 return False;
2081 pos = startPos == 0 ? 0 : startPos - 1;
2082 while (pos >= buf->gapStart) {
2083 if (buf->buf[pos + gapLen] == searchChar) {
2084 *foundPos = pos;
2085 return True;
2087 pos--;
2089 while (pos >= 0) {
2090 if (buf->buf[pos] == searchChar) {
2091 *foundPos = pos;
2092 return True;
2094 pos--;
2096 *foundPos = 0;
2097 return False;
2101 ** Copy from "text" to end up to but not including newline (or end of "text")
2102 ** and return the copy as the function value, and the length of the line in
2103 ** "lineLen"
2105 static char *copyLine(const char *text, int *lineLen)
2107 int len = 0;
2108 const char *c;
2109 char *outStr;
2111 for (c=text; *c!='\0' && *c!='\n'; c++)
2112 len++;
2113 outStr = XtMalloc(len + 1);
2114 strncpy(outStr, text, len);
2115 outStr[len] = '\0';
2116 *lineLen = len;
2117 return outStr;
2121 ** Count the number of newlines in a null-terminated text string;
2123 static int countLines(const char *string)
2125 const char *c;
2126 int lineCount = 0;
2128 for (c=string; *c!='\0'; c++)
2129 if (*c == '\n') lineCount++;
2130 return lineCount;
2134 ** Measure the width in displayed characters of string "text"
2136 static int textWidth(const char *text, int tabDist, char nullSubsChar)
2138 int width = 0, maxWidth = 0;
2139 const char *c;
2141 for (c=text; *c!='\0'; c++) {
2142 if (*c == '\n') {
2143 if (width > maxWidth)
2144 maxWidth = width;
2145 width = 0;
2146 } else
2147 width += BufCharWidth(*c, width, tabDist, nullSubsChar);
2149 if (width > maxWidth)
2150 return width;
2151 return maxWidth;
2155 ** Find the first and last character position in a line withing a rectangular
2156 ** selection (for copying). Includes tabs which cross rectStart, but not
2157 ** control characters which do so. Leaves off tabs which cross rectEnd.
2159 ** Technically, the calling routine should convert tab characters which
2160 ** cross the right boundary of the selection to spaces which line up with
2161 ** the edge of the selection. Unfortunately, the additional memory
2162 ** management required in the parent routine to allow for the changes
2163 ** in string size is not worth all the extra work just for a couple of
2164 ** shifted characters, so if a tab protrudes, just lop it off and hope
2165 ** that there are other characters in the selection to establish the right
2166 ** margin for subsequent columnar pastes of this data.
2168 static void findRectSelBoundariesForCopy(textBuffer *buf, int lineStartPos,
2169 int rectStart, int rectEnd, int *selStart, int *selEnd)
2171 int pos, width, indent = 0;
2172 char c;
2174 /* find the start of the selection */
2175 for (pos=lineStartPos; pos<buf->length; pos++) {
2176 c = BufGetCharacter(buf, pos);
2177 if (c == '\n')
2178 break;
2179 width = BufCharWidth(c, indent, buf->tabDist, buf->nullSubsChar);
2180 if (indent + width > rectStart) {
2181 if (indent != rectStart && c != '\t') {
2182 pos++;
2183 indent += width;
2185 break;
2187 indent += width;
2189 *selStart = pos;
2191 /* find the end */
2192 for (; pos<buf->length; pos++) {
2193 c = BufGetCharacter(buf, pos);
2194 if (c == '\n')
2195 break;
2196 width = BufCharWidth(c, indent, buf->tabDist, buf->nullSubsChar);
2197 indent += width;
2198 if (indent > rectEnd) {
2199 if (indent-width != rectEnd && c != '\t')
2200 pos++;
2201 break;
2204 *selEnd = pos;
2208 ** Adjust the space and tab characters from string "text" so that non-white
2209 ** characters remain stationary when the text is shifted from starting at
2210 ** "origIndent" to starting at "newIndent". Returns an allocated string
2211 ** which must be freed by the caller with XtFree.
2213 static char *realignTabs(const char *text, int origIndent, int newIndent,
2214 int tabDist, int useTabs, char nullSubsChar, int *newLength)
2216 char *expStr, *outStr;
2217 int len;
2219 /* If the tabs settings are the same, retain original tabs */
2220 if (origIndent % tabDist == newIndent %tabDist) {
2221 len = strlen(text);
2222 outStr = XtMalloc(len + 1);
2223 strcpy(outStr, text);
2224 *newLength = len;
2225 return outStr;
2228 /* If the tab settings are not the same, brutally convert tabs to
2229 spaces, then back to tabs in the new position */
2230 expStr = expandTabs(text, origIndent, tabDist, nullSubsChar, &len);
2231 if (!useTabs) {
2232 *newLength = len;
2233 return expStr;
2235 outStr = unexpandTabs(expStr, newIndent, tabDist, nullSubsChar, newLength);
2236 XtFree(expStr);
2237 return outStr;
2241 ** Expand tabs to spaces for a block of text. The additional parameter
2242 ** "startIndent" if nonzero, indicates that the text is a rectangular selection
2243 ** beginning at column "startIndent"
2245 static char *expandTabs(const char *text, int startIndent, int tabDist,
2246 char nullSubsChar, int *newLen)
2248 char *outStr, *outPtr;
2249 const char *c;
2250 int indent, len, outLen = 0;
2252 /* rehearse the expansion to figure out length for output string */
2253 indent = startIndent;
2254 for (c=text; *c!='\0'; c++) {
2255 if (*c == '\t') {
2256 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
2257 outLen += len;
2258 indent += len;
2259 } else if (*c == '\n') {
2260 indent = startIndent;
2261 outLen++;
2262 } else {
2263 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
2264 outLen++;
2268 /* do the expansion */
2269 outStr = XtMalloc(outLen+1);
2270 outPtr = outStr;
2271 indent = startIndent;
2272 for (c=text; *c!= '\0'; c++) {
2273 if (*c == '\t') {
2274 len = BufExpandCharacter(*c, indent, outPtr, tabDist, nullSubsChar);
2275 outPtr += len;
2276 indent += len;
2277 } else if (*c == '\n') {
2278 indent = startIndent;
2279 *outPtr++ = *c;
2280 } else {
2281 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
2282 *outPtr++ = *c;
2285 outStr[outLen] = '\0';
2286 *newLen = outLen;
2287 return outStr;
2291 ** Convert sequences of spaces into tabs. The threshold for conversion is
2292 ** when 3 or more spaces can be converted into a single tab, this avoids
2293 ** converting double spaces after a period withing a block of text.
2295 static char *unexpandTabs(const char *text, int startIndent, int tabDist,
2296 char nullSubsChar, int *newLen)
2298 char *outStr, *outPtr, expandedChar[MAX_EXP_CHAR_LEN];
2299 const char *c;
2300 int indent, len;
2302 outStr = XtMalloc(strlen(text)+1);
2303 outPtr = outStr;
2304 indent = startIndent;
2305 for (c=text; *c!='\0';) {
2306 if (*c == ' ') {
2307 len = BufExpandCharacter('\t', indent, expandedChar, tabDist,
2308 nullSubsChar);
2309 if (len >= 3 && !strncmp(c, expandedChar, len)) {
2310 c += len;
2311 *outPtr++ = '\t';
2312 indent += len;
2313 } else {
2314 *outPtr++ = *c++;
2315 indent++;
2317 } else if (*c == '\n') {
2318 indent = startIndent;
2319 *outPtr++ = *c++;
2320 } else {
2321 *outPtr++ = *c++;
2322 indent++;
2325 *outPtr = '\0';
2326 *newLen = outPtr - outStr;
2327 return outStr;
2330 static int max(int i1, int i2)
2332 return i1 >= i2 ? i1 : i2;
2335 static int min(int i1, int i2)
2337 return i1 <= i2 ? i1 : i2;