Xft support under OpenMotif 2.3.3 - I've been using this for quite a while on
[nedit.git] / source / textBuf.c
blob05cb93b4641d73e6984f1db68ad8347531676144
1 static const char CVSID[] = "$Id: textBuf.c,v 1.37 2008/01/04 22:11:04 yooden 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. In addition, you may distribute version of this program linked to *
12 * Motif or Open Motif. See README for details. *
13 * *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17 * for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
22 * *
23 * Nirvana Text Editor *
24 * June 15, 1995 *
25 * *
26 * Written by Mark Edel *
27 * *
28 *******************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 #include "../config.h"
32 #endif
34 #include "textBuf.h"
35 #include "rangeset.h"
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <ctype.h>
42 #ifdef HAVE_DEBUG_H
43 #include "../debug.h"
44 #endif
46 #define PREFERRED_GAP_SIZE 80 /* Initial size for the buffer gap (empty space
47 in the buffer where text might be inserted
48 if the user is typing sequential chars) */
50 static void histogramCharacters(const char *string, int length, char hist[256],
51 int init);
52 static void subsChars(char *string, int length, char fromChar, char toChar);
53 static char chooseNullSubsChar(char hist[256]);
54 static int insert(textBuffer *buf, int pos, const char *text);
55 static void delete(textBuffer *buf, int start, int end);
56 static void deleteRect(textBuffer *buf, int start, int end, int rectStart,
57 int rectEnd, int *replaceLen, int *endPos);
58 static void insertCol(textBuffer *buf, int column, int startPos, const char *insText,
59 int *nDeleted, int *nInserted, int *endPos);
60 static void overlayRect(textBuffer *buf, int startPos, int rectStart,
61 int rectEnd, const char *insText, int *nDeleted, int *nInserted, int *endPos);
62 static void insertColInLine(const char *line, const char *insLine, int column, int insWidth,
63 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
64 int *endOffset);
65 static void deleteRectFromLine(const char *line, int rectStart, int rectEnd,
66 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
67 int *endOffset);
68 static void overlayRectInLine(const char *line, const char *insLine, int rectStart,
69 int rectEnd, int tabDist, int useTabs, char nullSubsChar, char *outStr,
70 int *outLen, int *endOffset);
71 static void callPreDeleteCBs(textBuffer *buf, int pos, int nDeleted);
72 static void callModifyCBs(textBuffer *buf, int pos, int nDeleted,
73 int nInserted, int nRestyled, const char *deletedText);
74 static void redisplaySelection(textBuffer *buf, selection *oldSelection,
75 selection *newSelection);
76 static void moveGap(textBuffer *buf, int pos);
77 static void reallocateBuf(textBuffer *buf, int newGapStart, int newGapLen);
78 static void setSelection(selection *sel, int start, int end);
79 static void setRectSelect(selection *sel, int start, int end,
80 int rectStart, int rectEnd);
81 static void updateSelections(textBuffer *buf, int pos, int nDeleted,
82 int nInserted);
83 static void updateSelection(selection *sel, int pos, int nDeleted,
84 int nInserted);
85 static int getSelectionPos(selection *sel, int *start, int *end,
86 int *isRect, int *rectStart, int *rectEnd);
87 static char *getSelectionText(textBuffer *buf, selection *sel);
88 static void removeSelected(textBuffer *buf, selection *sel);
89 static void replaceSelected(textBuffer *buf, selection *sel, const char *text);
90 static void addPadding(char *string, int startIndent, int toIndent,
91 int tabDist, int useTabs, char nullSubsChar, int *charsAdded);
92 static int searchForward(textBuffer *buf, int startPos, char searchChar,
93 int *foundPos);
94 static int searchBackward(textBuffer *buf, int startPos, char searchChar,
95 int *foundPos);
96 static char *copyLine(const char *text, int *lineLen);
97 static int countLines(const char *string);
98 static int textWidth(const char *text, int tabDist, char nullSubsChar);
99 static void findRectSelBoundariesForCopy(textBuffer *buf, int lineStartPos,
100 int rectStart, int rectEnd, int *selStart, int *selEnd);
101 static char *realignTabs(const char *text, int origIndent, int newIndent,
102 int tabDist, int useTabs, char nullSubsChar, int *newLength);
103 static char *expandTabs(const char *text, int startIndent, int tabDist,
104 char nullSubsChar, int *newLen);
105 static char *unexpandTabs(const char *text, int startIndent, int tabDist,
106 char nullSubsChar, int *newLen);
107 static int max(int i1, int i2);
108 static int min(int i1, int i2);
110 #ifdef __MVS__
111 static const char *ControlCodeTable[64] = {
112 "nul", "soh", "stx", "etx", "sel", "ht", "rnl", "del",
113 "ge", "sps", "rpt", "vt", "ff", "cr", "so", "si",
114 "dle", "dc1", "dc2", "dc3", "res", "nl", "bs", "poc",
115 "can", "em", "ubs", "cu1", "ifs", "igs", "irs", "ius",
116 "ds", "sos", "fs", "wus", "byp", "lf", "etb", "esc",
117 "sa", "sfe", "sm", "csp", "mfa", "enq", "ack", "bel",
118 "x30", "x31", "syn", "ir", "pp", "trn", "nbs", "eot",
119 "sbs", "it", "rff", "cu3", "dc4", "nak", "x3e", "sub"};
120 #else
121 static const char *ControlCodeTable[32] = {
122 "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel",
123 "bs", "ht", "nl", "vt", "np", "cr", "so", "si",
124 "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb",
125 "can", "em", "sub", "esc", "fs", "gs", "rs", "us"};
126 #endif
129 ** Create an empty text buffer
131 textBuffer *BufCreate(void)
133 textBuffer *buf = BufCreatePreallocated(0);
134 return buf;
138 ** Create an empty text buffer of a pre-determined size (use this to
139 ** avoid unnecessary re-allocation if you know exactly how much the buffer
140 ** will need to hold
142 textBuffer *BufCreatePreallocated(int requestedSize)
144 textBuffer *buf;
146 buf = (textBuffer *)XtMalloc(sizeof(textBuffer));
147 buf->length = 0;
148 buf->buf = XtMalloc(requestedSize + PREFERRED_GAP_SIZE + 1);
149 buf->buf[requestedSize + PREFERRED_GAP_SIZE] = '\0';
150 buf->gapStart = 0;
151 buf->gapEnd = PREFERRED_GAP_SIZE;
152 buf->tabDist = 8;
153 buf->useTabs = True;
154 buf->primary.selected = False;
155 buf->primary.zeroWidth = False;
156 buf->primary.rectangular = False;
157 buf->primary.start = buf->primary.end = 0;
158 buf->secondary.selected = False;
159 buf->secondary.zeroWidth = False;
160 buf->secondary.start = buf->secondary.end = 0;
161 buf->secondary.rectangular = False;
162 buf->highlight.selected = False;
163 buf->highlight.zeroWidth = False;
164 buf->highlight.start = buf->highlight.end = 0;
165 buf->highlight.rectangular = False;
166 buf->modifyProcs = NULL;
167 buf->cbArgs = NULL;
168 buf->nModifyProcs = 0;
169 buf->preDeleteProcs = NULL;
170 buf->preDeleteCbArgs = NULL;
171 buf->nPreDeleteProcs = 0;
172 buf->nullSubsChar = '\0';
173 #ifdef PURIFY
174 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
175 #endif
176 buf->rangesetTable = NULL;
177 return buf;
181 ** Free a text buffer
183 void BufFree(textBuffer *buf)
185 XtFree(buf->buf);
186 if (buf->nModifyProcs != 0) {
187 XtFree((char *)buf->modifyProcs);
188 XtFree((char *)buf->cbArgs);
190 if (buf->rangesetTable)
191 RangesetTableFree(buf->rangesetTable);
192 if (buf->nPreDeleteProcs != 0) {
193 XtFree((char *)buf->preDeleteProcs);
194 XtFree((char *)buf->preDeleteCbArgs);
196 XtFree((char *)buf);
200 ** Get the entire contents of a text buffer. Memory is allocated to contain
201 ** the returned string, which the caller must free.
203 char *BufGetAll(textBuffer *buf)
205 char *text;
207 text = XtMalloc(buf->length+1);
208 memcpy(text, buf->buf, buf->gapStart);
209 memcpy(&text[buf->gapStart], &buf->buf[buf->gapEnd],
210 buf->length - buf->gapStart);
211 text[buf->length] = '\0';
212 return text;
216 ** Get the entire contents of a text buffer as a single string. The gap is
217 ** moved so that the buffer data can be accessed as a single contiguous
218 ** character array.
219 ** NB DO NOT ALTER THE TEXT THROUGH THE RETURNED POINTER!
220 ** (we make an exception in BufSubstituteNullChars() however)
221 ** This function is intended ONLY to provide a searchable string without copying
222 ** into a temporary buffer.
224 const char *BufAsString(textBuffer *buf)
226 char *text;
227 int bufLen = buf->length;
228 int leftLen = buf->gapStart;
229 int rightLen = bufLen - leftLen;
231 /* find where best to put the gap to minimise memory movement */
232 if (leftLen != 0 && rightLen != 0) {
233 leftLen = (leftLen < rightLen) ? 0 : bufLen;
234 moveGap(buf, leftLen);
236 /* get the start position of the actual data */
237 text = &buf->buf[(leftLen == 0) ? buf->gapEnd : 0];
238 /* make sure it's null-terminated */
239 text[bufLen] = 0;
241 return text;
245 ** Replace the entire contents of the text buffer
247 void BufSetAll(textBuffer *buf, const char *text)
249 int length, deletedLength;
250 char *deletedText;
251 length = strlen(text);
253 callPreDeleteCBs(buf, 0, buf->length);
255 /* Save information for redisplay, and get rid of the old buffer */
256 deletedText = BufGetAll(buf);
257 deletedLength = buf->length;
258 XtFree(buf->buf);
260 /* Start a new buffer with a gap of PREFERRED_GAP_SIZE in the center */
261 buf->buf = XtMalloc(length + PREFERRED_GAP_SIZE + 1);
262 buf->buf[length + PREFERRED_GAP_SIZE] = '\0';
263 buf->length = length;
264 buf->gapStart = length/2;
265 buf->gapEnd = buf->gapStart + PREFERRED_GAP_SIZE;
266 memcpy(buf->buf, text, buf->gapStart);
267 memcpy(&buf->buf[buf->gapEnd], &text[buf->gapStart], length-buf->gapStart);
268 #ifdef PURIFY
269 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
270 #endif
272 /* Zero all of the existing selections */
273 updateSelections(buf, 0, deletedLength, 0);
275 /* Call the saved display routine(s) to update the screen */
276 callModifyCBs(buf, 0, deletedLength, length, 0, deletedText);
277 XtFree(deletedText);
281 ** Return a copy of the text between "start" and "end" character positions
282 ** from text buffer "buf". Positions start at 0, and the range does not
283 ** include the character pointed to by "end"
285 char* BufGetRange(const textBuffer* buf, int start, int end)
287 char *text;
288 int length, part1Length;
290 /* Make sure start and end are ok, and allocate memory for returned string.
291 If start is bad, return "", if end is bad, adjust it. */
292 if (start < 0 || start > buf->length) {
293 text = XtMalloc(1);
294 text[0] = '\0';
295 return text;
297 if (end < start) {
298 int temp = start;
299 start = end;
300 end = temp;
302 if (end > buf->length)
303 end = buf->length;
304 length = end - start;
305 text = XtMalloc(length+1);
307 /* Copy the text from the buffer to the returned string */
308 if (end <= buf->gapStart) {
309 memcpy(text, &buf->buf[start], length);
310 } else if (start >= buf->gapStart) {
311 memcpy(text, &buf->buf[start+(buf->gapEnd-buf->gapStart)], length);
312 } else {
313 part1Length = buf->gapStart - start;
314 memcpy(text, &buf->buf[start], part1Length);
315 memcpy(&text[part1Length], &buf->buf[buf->gapEnd], length-part1Length);
317 text[length] = '\0';
318 return text;
322 ** Return the character at buffer position "pos". Positions start at 0.
324 char BufGetCharacter(const textBuffer* buf, const int pos)
326 if (pos < 0 || pos >= buf->length)
327 return '\0';
328 if (pos < buf->gapStart)
329 return buf->buf[pos];
330 else
331 return buf->buf[pos + buf->gapEnd-buf->gapStart];
335 ** Insert null-terminated string "text" at position "pos" in "buf"
337 void BufInsert(textBuffer *buf, int pos, const char *text)
339 int nInserted;
341 /* if pos is not contiguous to existing text, make it */
342 if (pos > buf->length) pos = buf->length;
343 if (pos < 0 ) pos = 0;
345 /* Even if nothing is deleted, we must call these callbacks */
346 callPreDeleteCBs(buf, pos, 0);
348 /* insert and redisplay */
349 nInserted = insert(buf, pos, text);
350 buf->cursorPosHint = pos + nInserted;
351 callModifyCBs(buf, pos, 0, nInserted, 0, NULL);
355 ** Delete the characters between "start" and "end", and insert the
356 ** null-terminated string "text" in their place in in "buf"
358 void BufReplace(textBuffer *buf, int start, int end, const char *text)
360 char *deletedText;
361 int nInserted = strlen(text);
363 callPreDeleteCBs(buf, start, end-start);
364 deletedText = BufGetRange(buf, start, end);
365 delete(buf, start, end);
366 insert(buf, start, text);
367 buf->cursorPosHint = start + nInserted;
368 callModifyCBs(buf, start, end-start, nInserted, 0, deletedText);
369 XtFree(deletedText);
372 void BufRemove(textBuffer *buf, int start, int end)
374 char *deletedText;
376 /* Make sure the arguments make sense */
377 if (start > end) {
378 int temp = start;
379 start = end;
380 end = temp;
382 if (start > buf->length) start = buf->length;
383 if (start < 0) start = 0;
384 if (end > buf->length) end = buf->length;
385 if (end < 0) end = 0;
387 callPreDeleteCBs(buf, start, end-start);
388 /* Remove and redisplay */
389 deletedText = BufGetRange(buf, start, end);
390 delete(buf, start, end);
391 buf->cursorPosHint = start;
392 callModifyCBs(buf, start, end-start, 0, 0, deletedText);
393 XtFree(deletedText);
396 void BufCopyFromBuf(textBuffer *fromBuf, textBuffer *toBuf, int fromStart,
397 int fromEnd, int toPos)
399 int length = fromEnd - fromStart;
400 int part1Length;
402 /* Prepare the buffer to receive the new text. If the new text fits in
403 the current buffer, just move the gap (if necessary) to where
404 the text should be inserted. If the new text is too large, reallocate
405 the buffer with a gap large enough to accomodate the new text and a
406 gap of PREFERRED_GAP_SIZE */
407 if (length > toBuf->gapEnd - toBuf->gapStart)
408 reallocateBuf(toBuf, toPos, length + PREFERRED_GAP_SIZE);
409 else if (toPos != toBuf->gapStart)
410 moveGap(toBuf, toPos);
412 /* Insert the new text (toPos now corresponds to the start of the gap) */
413 if (fromEnd <= fromBuf->gapStart) {
414 memcpy(&toBuf->buf[toPos], &fromBuf->buf[fromStart], length);
415 } else if (fromStart >= fromBuf->gapStart) {
416 memcpy(&toBuf->buf[toPos],
417 &fromBuf->buf[fromStart+(fromBuf->gapEnd-fromBuf->gapStart)],
418 length);
419 } else {
420 part1Length = fromBuf->gapStart - fromStart;
421 memcpy(&toBuf->buf[toPos], &fromBuf->buf[fromStart], part1Length);
422 memcpy(&toBuf->buf[toPos+part1Length], &fromBuf->buf[fromBuf->gapEnd],
423 length-part1Length);
425 toBuf->gapStart += length;
426 toBuf->length += length;
427 updateSelections(toBuf, toPos, 0, length);
431 ** Insert "text" columnwise into buffer starting at displayed character
432 ** position "column" on the line beginning at "startPos". Opens a rectangular
433 ** space the width and height of "text", by moving all text to the right of
434 ** "column" right. If charsInserted and charsDeleted are not NULL, the
435 ** number of characters inserted and deleted in the operation (beginning
436 ** at startPos) are returned in these arguments
438 void BufInsertCol(textBuffer *buf, int column, int startPos, const char *text,
439 int *charsInserted, int *charsDeleted)
441 int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
442 char *deletedText;
444 nLines = countLines(text);
445 lineStartPos = BufStartOfLine(buf, startPos);
446 nDeleted = BufEndOfLine(buf, BufCountForwardNLines(buf, startPos, nLines)) -
447 lineStartPos;
448 callPreDeleteCBs(buf, lineStartPos, nDeleted);
449 deletedText = BufGetRange(buf, lineStartPos, lineStartPos + nDeleted);
450 insertCol(buf, column, lineStartPos, text, &insertDeleted, &nInserted,
451 &buf->cursorPosHint);
452 if (nDeleted != insertDeleted)
453 fprintf(stderr, "NEdit internal consistency check ins1 failed");
454 callModifyCBs(buf, lineStartPos, nDeleted, nInserted, 0, deletedText);
455 XtFree(deletedText);
456 if (charsInserted != NULL)
457 *charsInserted = nInserted;
458 if (charsDeleted != NULL)
459 *charsDeleted = nDeleted;
463 ** Overlay "text" between displayed character positions "rectStart" and
464 ** "rectEnd" on the line beginning at "startPos". If charsInserted and
465 ** charsDeleted are not NULL, the number of characters inserted and deleted
466 ** in the operation (beginning at startPos) are returned in these arguments.
467 ** If rectEnd equals -1, the width of the inserted text is measured first.
469 void BufOverlayRect(textBuffer *buf, int startPos, int rectStart,
470 int rectEnd, const char *text, int *charsInserted, int *charsDeleted)
472 int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
473 char *deletedText;
475 nLines = countLines(text);
476 lineStartPos = BufStartOfLine(buf, startPos);
477 if(rectEnd == -1)
478 rectEnd = rectStart + textWidth(text, buf->tabDist, buf->nullSubsChar);
479 lineStartPos = BufStartOfLine(buf, startPos);
480 nDeleted = BufEndOfLine(buf, BufCountForwardNLines(buf, startPos, nLines)) -
481 lineStartPos;
482 callPreDeleteCBs(buf, lineStartPos, nDeleted);
483 deletedText = BufGetRange(buf, lineStartPos, lineStartPos + nDeleted);
484 overlayRect(buf, lineStartPos, rectStart, rectEnd, text, &insertDeleted,
485 &nInserted, &buf->cursorPosHint);
486 if (nDeleted != insertDeleted)
487 fprintf(stderr, "NEdit internal consistency check ovly1 failed");
488 callModifyCBs(buf, lineStartPos, nDeleted, nInserted, 0, deletedText);
489 XtFree(deletedText);
490 if (charsInserted != NULL)
491 *charsInserted = nInserted;
492 if (charsDeleted != NULL)
493 *charsDeleted = nDeleted;
497 ** Replace a rectangular area in buf, given by "start", "end", "rectStart",
498 ** and "rectEnd", with "text". If "text" is vertically longer than the
499 ** rectangle, add extra lines to make room for it.
501 void BufReplaceRect(textBuffer *buf, int start, int end, int rectStart,
502 int rectEnd, const char *text)
504 char *deletedText;
505 char *insText=NULL;
506 int i, nInsertedLines, nDeletedLines, insLen, hint;
507 int insertDeleted, insertInserted, deleteInserted;
508 int linesPadded = 0;
510 /* Make sure start and end refer to complete lines, since the
511 columnar delete and insert operations will replace whole lines */
512 start = BufStartOfLine(buf, start);
513 end = BufEndOfLine(buf, end);
515 callPreDeleteCBs(buf, start, end-start);
517 /* If more lines will be deleted than inserted, pad the inserted text
518 with newlines to make it as long as the number of deleted lines. This
519 will indent all of the text to the right of the rectangle to the same
520 column. If more lines will be inserted than deleted, insert extra
521 lines in the buffer at the end of the rectangle to make room for the
522 additional lines in "text" */
523 nInsertedLines = countLines(text);
524 nDeletedLines = BufCountLines(buf, start, end);
525 if (nInsertedLines < nDeletedLines) {
526 char *insPtr;
528 insLen = strlen(text);
529 insText = XtMalloc(insLen + nDeletedLines - nInsertedLines + 1);
530 strcpy(insText, text);
531 insPtr = insText + insLen;
532 for (i=0; i<nDeletedLines-nInsertedLines; i++)
533 *insPtr++ = '\n';
534 *insPtr = '\0';
535 } else if (nDeletedLines < nInsertedLines) {
536 linesPadded = nInsertedLines-nDeletedLines;
537 for (i=0; i<linesPadded; i++)
538 insert(buf, end, "\n");
539 } else /* nDeletedLines == nInsertedLines */ {
542 /* Save a copy of the text which will be modified for the modify CBs */
543 deletedText = BufGetRange(buf, start, end);
545 /* Delete then insert */
546 deleteRect(buf, start, end, rectStart, rectEnd, &deleteInserted, &hint);
547 if (insText) {
548 insertCol(buf, rectStart, start, insText, &insertDeleted, &insertInserted,
549 &buf->cursorPosHint);
550 XtFree(insText);
552 else
553 insertCol(buf, rectStart, start, text, &insertDeleted, &insertInserted,
554 &buf->cursorPosHint);
556 /* Figure out how many chars were inserted and call modify callbacks */
557 if (insertDeleted != deleteInserted + linesPadded)
558 fprintf(stderr, "NEdit: internal consistency check repl1 failed\n");
559 callModifyCBs(buf, start, end-start, insertInserted, 0, deletedText);
560 XtFree(deletedText);
564 ** Remove a rectangular swath of characters between character positions start
565 ** and end and horizontal displayed-character offsets rectStart and rectEnd.
567 void BufRemoveRect(textBuffer *buf, int start, int end, int rectStart,
568 int rectEnd)
570 char *deletedText;
571 int nInserted;
573 start = BufStartOfLine(buf, start);
574 end = BufEndOfLine(buf, end);
575 callPreDeleteCBs(buf, start, end-start);
576 deletedText = BufGetRange(buf, start, end);
577 deleteRect(buf, start, end, rectStart, rectEnd, &nInserted,
578 &buf->cursorPosHint);
579 callModifyCBs(buf, start, end-start, nInserted, 0, deletedText);
580 XtFree(deletedText);
584 ** Clear a rectangular "hole" out of the buffer between character positions
585 ** start and end and horizontal displayed-character offsets rectStart and
586 ** rectEnd.
588 void BufClearRect(textBuffer *buf, int start, int end, int rectStart,
589 int rectEnd)
591 int i, nLines;
592 char *newlineString;
594 nLines = BufCountLines(buf, start, end);
595 newlineString = XtMalloc(nLines+1);
596 for (i=0; i<nLines; i++)
597 newlineString[i] = '\n';
598 newlineString[i] = '\0';
599 BufOverlayRect(buf, start, rectStart, rectEnd, newlineString,
600 NULL, NULL);
601 XtFree(newlineString);
604 char *BufGetTextInRect(textBuffer *buf, int start, int end,
605 int rectStart, int rectEnd)
607 int lineStart, selLeft, selRight, len;
608 char *textOut, *textIn, *outPtr, *retabbedStr;
610 start = BufStartOfLine(buf, start);
611 end = BufEndOfLine(buf, end);
612 textOut = XtMalloc((end - start) + 1);
613 lineStart = start;
614 outPtr = textOut;
615 while (lineStart <= end) {
616 findRectSelBoundariesForCopy(buf, lineStart, rectStart, rectEnd,
617 &selLeft, &selRight);
618 textIn = BufGetRange(buf, selLeft, selRight);
619 len = selRight - selLeft;
620 memcpy(outPtr, textIn, len);
621 XtFree(textIn);
622 outPtr += len;
623 lineStart = BufEndOfLine(buf, selRight) + 1;
624 *outPtr++ = '\n';
626 if (outPtr != textOut)
627 outPtr--; /* don't leave trailing newline */
628 *outPtr = '\0';
630 /* If necessary, realign the tabs in the selection as if the text were
631 positioned at the left margin */
632 retabbedStr = realignTabs(textOut, rectStart, 0, buf->tabDist,
633 buf->useTabs, buf->nullSubsChar, &len);
634 XtFree(textOut);
635 return retabbedStr;
639 ** Get the hardware tab distance used by all displays for this buffer,
640 ** and used in computing offsets for rectangular selection operations.
642 int BufGetTabDistance(textBuffer *buf)
644 return buf->tabDist;
648 ** Set the hardware tab distance used by all displays for this buffer,
649 ** and used in computing offsets for rectangular selection operations.
651 void BufSetTabDistance(textBuffer *buf, int tabDist)
653 const char *deletedText;
655 /* First call the pre-delete callbacks with the previous tab setting
656 still active. */
657 callPreDeleteCBs(buf, 0, buf->length);
659 /* Change the tab setting */
660 buf->tabDist = tabDist;
662 /* Force any display routines to redisplay everything */
663 deletedText = BufAsString(buf);
664 callModifyCBs(buf, 0, buf->length, buf->length, 0, deletedText);
667 void BufCheckDisplay(textBuffer *buf, int start, int end)
669 /* just to make sure colors in the selected region are up to date */
670 callModifyCBs(buf, start, 0, 0, end-start, NULL);
673 void BufSelect(textBuffer *buf, int start, int end)
675 selection oldSelection = buf->primary;
677 setSelection(&buf->primary, start, end);
678 redisplaySelection(buf, &oldSelection, &buf->primary);
681 void BufUnselect(textBuffer *buf)
683 selection oldSelection = buf->primary;
685 buf->primary.selected = False;
686 buf->primary.zeroWidth = False;
687 redisplaySelection(buf, &oldSelection, &buf->primary);
690 void BufRectSelect(textBuffer *buf, int start, int end, int rectStart,
691 int rectEnd)
693 selection oldSelection = buf->primary;
695 setRectSelect(&buf->primary, start, end, rectStart, rectEnd);
696 redisplaySelection(buf, &oldSelection, &buf->primary);
699 int BufGetSelectionPos(textBuffer *buf, int *start, int *end,
700 int *isRect, int *rectStart, int *rectEnd)
702 return getSelectionPos(&buf->primary, start, end, isRect, rectStart,
703 rectEnd);
706 /* Same as above, but also returns TRUE for empty selections */
707 int BufGetEmptySelectionPos(textBuffer *buf, int *start, int *end,
708 int *isRect, int *rectStart, int *rectEnd)
710 return getSelectionPos(&buf->primary, start, end, isRect, rectStart,
711 rectEnd) || buf->primary.zeroWidth;
714 char *BufGetSelectionText(textBuffer *buf)
716 return getSelectionText(buf, &buf->primary);
719 void BufRemoveSelected(textBuffer *buf)
721 removeSelected(buf, &buf->primary);
724 void BufReplaceSelected(textBuffer *buf, const char *text)
726 replaceSelected(buf, &buf->primary, text);
729 void BufSecondarySelect(textBuffer *buf, int start, int end)
731 selection oldSelection = buf->secondary;
733 setSelection(&buf->secondary, start, end);
734 redisplaySelection(buf, &oldSelection, &buf->secondary);
737 void BufSecondaryUnselect(textBuffer *buf)
739 selection oldSelection = buf->secondary;
741 buf->secondary.selected = False;
742 buf->secondary.zeroWidth = False;
743 redisplaySelection(buf, &oldSelection, &buf->secondary);
746 void BufSecRectSelect(textBuffer *buf, int start, int end,
747 int rectStart, int rectEnd)
749 selection oldSelection = buf->secondary;
751 setRectSelect(&buf->secondary, start, end, rectStart, rectEnd);
752 redisplaySelection(buf, &oldSelection, &buf->secondary);
755 int BufGetSecSelectPos(textBuffer *buf, int *start, int *end,
756 int *isRect, int *rectStart, int *rectEnd)
758 return getSelectionPos(&buf->secondary, start, end, isRect, rectStart,
759 rectEnd);
762 char *BufGetSecSelectText(textBuffer *buf)
764 return getSelectionText(buf, &buf->secondary);
767 void BufRemoveSecSelect(textBuffer *buf)
769 removeSelected(buf, &buf->secondary);
772 void BufReplaceSecSelect(textBuffer *buf, const char *text)
774 replaceSelected(buf, &buf->secondary, text);
777 void BufHighlight(textBuffer *buf, int start, int end)
779 selection oldSelection = buf->highlight;
781 setSelection(&buf->highlight, start, end);
782 redisplaySelection(buf, &oldSelection, &buf->highlight);
785 void BufUnhighlight(textBuffer *buf)
787 selection oldSelection = buf->highlight;
789 buf->highlight.selected = False;
790 buf->highlight.zeroWidth = False;
791 redisplaySelection(buf, &oldSelection, &buf->highlight);
794 void BufRectHighlight(textBuffer *buf, int start, int end,
795 int rectStart, int rectEnd)
797 selection oldSelection = buf->highlight;
799 setRectSelect(&buf->highlight, start, end, rectStart, rectEnd);
800 redisplaySelection(buf, &oldSelection, &buf->highlight);
803 int BufGetHighlightPos(textBuffer *buf, int *start, int *end,
804 int *isRect, int *rectStart, int *rectEnd)
806 return getSelectionPos(&buf->highlight, start, end, isRect, rectStart,
807 rectEnd);
811 ** Add a callback routine to be called when the buffer is modified
813 void BufAddModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
814 void *cbArg)
816 bufModifyCallbackProc *newModifyProcs;
817 void **newCBArgs;
818 int i;
820 newModifyProcs = (bufModifyCallbackProc *)
821 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs+1));
822 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs+1));
823 for (i=0; i<buf->nModifyProcs; i++) {
824 newModifyProcs[i] = buf->modifyProcs[i];
825 newCBArgs[i] = buf->cbArgs[i];
827 if (buf->nModifyProcs != 0) {
828 XtFree((char *)buf->modifyProcs);
829 XtFree((char *)buf->cbArgs);
831 newModifyProcs[buf->nModifyProcs] = bufModifiedCB;
832 newCBArgs[buf->nModifyProcs] = cbArg;
833 buf->nModifyProcs++;
834 buf->modifyProcs = newModifyProcs;
835 buf->cbArgs = newCBArgs;
839 ** Similar to the above, but makes sure that the callback is called before
840 ** normal priority callbacks.
842 void BufAddHighPriorityModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
843 void *cbArg)
845 bufModifyCallbackProc *newModifyProcs;
846 void **newCBArgs;
847 int i;
849 newModifyProcs = (bufModifyCallbackProc *)
850 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs+1));
851 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs+1));
852 for (i=0; i<buf->nModifyProcs; i++) {
853 newModifyProcs[i+1] = buf->modifyProcs[i];
854 newCBArgs[i+1] = buf->cbArgs[i];
856 if (buf->nModifyProcs != 0) {
857 XtFree((char *)buf->modifyProcs);
858 XtFree((char *)buf->cbArgs);
860 newModifyProcs[0] = bufModifiedCB;
861 newCBArgs[0] = cbArg;
862 buf->nModifyProcs++;
863 buf->modifyProcs = newModifyProcs;
864 buf->cbArgs = newCBArgs;
867 void BufRemoveModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
868 void *cbArg)
870 int i, toRemove = -1;
871 bufModifyCallbackProc *newModifyProcs;
872 void **newCBArgs;
874 /* find the matching callback to remove */
875 for (i=0; i<buf->nModifyProcs; i++) {
876 if (buf->modifyProcs[i] == bufModifiedCB && buf->cbArgs[i] == cbArg) {
877 toRemove = i;
878 break;
881 if (toRemove == -1) {
882 fprintf(stderr, "NEdit Internal Error: Can't find modify CB to remove\n");
883 return;
886 /* Allocate new lists for remaining callback procs and args (if
887 any are left) */
888 buf->nModifyProcs--;
889 if (buf->nModifyProcs == 0) {
890 buf->nModifyProcs = 0;
891 XtFree((char *)buf->modifyProcs);
892 buf->modifyProcs = NULL;
893 XtFree((char *)buf->cbArgs);
894 buf->cbArgs = NULL;
895 return;
897 newModifyProcs = (bufModifyCallbackProc *)
898 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs));
899 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs));
901 /* copy out the remaining members and free the old lists */
902 for (i=0; i<toRemove; i++) {
903 newModifyProcs[i] = buf->modifyProcs[i];
904 newCBArgs[i] = buf->cbArgs[i];
906 for (; i<buf->nModifyProcs; i++) {
907 newModifyProcs[i] = buf->modifyProcs[i+1];
908 newCBArgs[i] = buf->cbArgs[i+1];
910 XtFree((char *)buf->modifyProcs);
911 XtFree((char *)buf->cbArgs);
912 buf->modifyProcs = newModifyProcs;
913 buf->cbArgs = newCBArgs;
917 ** Add a callback routine to be called before text is deleted from the buffer.
919 void BufAddPreDeleteCB(textBuffer *buf, bufPreDeleteCallbackProc bufPreDeleteCB,
920 void *cbArg)
922 bufPreDeleteCallbackProc *newPreDeleteProcs;
923 void **newCBArgs;
924 int i;
926 newPreDeleteProcs = (bufPreDeleteCallbackProc *)
927 XtMalloc(sizeof(bufPreDeleteCallbackProc *) * (buf->nPreDeleteProcs+1));
928 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nPreDeleteProcs+1));
929 for (i=0; i<buf->nPreDeleteProcs; i++) {
930 newPreDeleteProcs[i] = buf->preDeleteProcs[i];
931 newCBArgs[i] = buf->preDeleteCbArgs[i];
933 if (buf->nPreDeleteProcs != 0) {
934 XtFree((char *)buf->preDeleteProcs);
935 XtFree((char *)buf->preDeleteCbArgs);
937 newPreDeleteProcs[buf->nPreDeleteProcs] = bufPreDeleteCB;
938 newCBArgs[buf->nPreDeleteProcs] = cbArg;
939 buf->nPreDeleteProcs++;
940 buf->preDeleteProcs = newPreDeleteProcs;
941 buf->preDeleteCbArgs = newCBArgs;
944 void BufRemovePreDeleteCB(textBuffer *buf, bufPreDeleteCallbackProc bufPreDeleteCB,
945 void *cbArg)
947 int i, toRemove = -1;
948 bufPreDeleteCallbackProc *newPreDeleteProcs;
949 void **newCBArgs;
951 /* find the matching callback to remove */
952 for (i=0; i<buf->nPreDeleteProcs; i++) {
953 if (buf->preDeleteProcs[i] == bufPreDeleteCB &&
954 buf->preDeleteCbArgs[i] == cbArg) {
955 toRemove = i;
956 break;
959 if (toRemove == -1) {
960 fprintf(stderr, "NEdit Internal Error: Can't find pre-delete CB to remove\n");
961 return;
964 /* Allocate new lists for remaining callback procs and args (if
965 any are left) */
966 buf->nPreDeleteProcs--;
967 if (buf->nPreDeleteProcs == 0) {
968 buf->nPreDeleteProcs = 0;
969 XtFree((char *)buf->preDeleteProcs);
970 buf->preDeleteProcs = NULL;
971 XtFree((char *)buf->preDeleteCbArgs);
972 buf->preDeleteCbArgs = NULL;
973 return;
975 newPreDeleteProcs = (bufPreDeleteCallbackProc *)
976 XtMalloc(sizeof(bufPreDeleteCallbackProc *) * (buf->nPreDeleteProcs));
977 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nPreDeleteProcs));
979 /* copy out the remaining members and free the old lists */
980 for (i=0; i<toRemove; i++) {
981 newPreDeleteProcs[i] = buf->preDeleteProcs[i];
982 newCBArgs[i] = buf->preDeleteCbArgs[i];
984 for (; i<buf->nPreDeleteProcs; i++) {
985 newPreDeleteProcs[i] = buf->preDeleteProcs[i+1];
986 newCBArgs[i] = buf->preDeleteCbArgs[i+1];
988 XtFree((char *)buf->preDeleteProcs);
989 XtFree((char *)buf->preDeleteCbArgs);
990 buf->preDeleteProcs = newPreDeleteProcs;
991 buf->preDeleteCbArgs = newCBArgs;
995 ** Find the position of the start of the line containing position "pos"
997 int BufStartOfLine(textBuffer *buf, int pos)
999 int startPos;
1001 if (!searchBackward(buf, pos, '\n', &startPos))
1002 return 0;
1003 return startPos + 1;
1008 ** Find the position of the end of the line containing position "pos"
1009 ** (which is either a pointer to the newline character ending the line,
1010 ** or a pointer to one character beyond the end of the buffer)
1012 int BufEndOfLine(textBuffer *buf, int pos)
1014 int endPos;
1016 if (!searchForward(buf, pos, '\n', &endPos))
1017 endPos = buf->length;
1018 return endPos;
1022 ** Get a character from the text buffer expanded into it's screen
1023 ** representation (which may be several characters for a tab or a
1024 ** control code). Returns the number of characters written to "outStr".
1025 ** "indent" is the number of characters from the start of the line
1026 ** for figuring tabs. Output string is guranteed to be shorter or
1027 ** equal in length to MAX_EXP_CHAR_LEN
1029 int BufGetExpandedChar(const textBuffer* buf, const int pos, const int indent,
1030 char* outStr)
1032 return BufExpandCharacter(BufGetCharacter(buf, pos), indent, outStr,
1033 buf->tabDist, buf->nullSubsChar);
1037 ** Expand a single character from the text buffer into it's screen
1038 ** representation (which may be several characters for a tab or a
1039 ** control code). Returns the number of characters added to "outStr".
1040 ** "indent" is the number of characters from the start of the line
1041 ** for figuring tabs. Output string is guranteed to be shorter or
1042 ** equal in length to MAX_EXP_CHAR_LEN
1044 int BufExpandCharacter(const char c, const int indent, char *outStr,
1045 const int tabDist, const char nullSubsChar)
1047 int i, nSpaces;
1049 /* Convert tabs to spaces */
1050 if (c == '\t') {
1051 nSpaces = tabDist - (indent % tabDist);
1052 for (i=0; i<nSpaces; i++)
1053 outStr[i] = ' ';
1054 return nSpaces;
1057 /* Convert ASCII (and EBCDIC in the __MVS__ (OS/390) case) control
1058 codes to readable character sequences */
1059 if (c == nullSubsChar) {
1060 sprintf(outStr, "<nul>");
1061 return 5;
1063 #ifdef __MVS__
1064 if (((unsigned char)c) <= 63) {
1065 sprintf(outStr, "<%s>", ControlCodeTable[(unsigned char)c]);
1066 return strlen(outStr);
1068 #else
1069 if (((unsigned char)c) <= 31) {
1070 sprintf(outStr, "<%s>", ControlCodeTable[(unsigned char)c]);
1071 return strlen(outStr);
1072 } else if (c == 127) {
1073 sprintf(outStr, "<del>");
1074 return 5;
1076 #endif
1078 /* Otherwise, just return the character */
1079 *outStr = c;
1080 return 1;
1084 ** Return the length in displayed characters of character "c" expanded
1085 ** for display (as discussed above in BufGetExpandedChar). If the
1086 ** buffer for which the character width is being measured is doing null
1087 ** substitution, nullSubsChar should be passed as that character (or nul
1088 ** to ignore).
1090 int BufCharWidth(char c, int indent, int tabDist, char nullSubsChar)
1092 /* Note, this code must parallel that in BufExpandCharacter */
1093 if (c == nullSubsChar)
1094 return 5;
1095 else if (c == '\t')
1096 return tabDist - (indent % tabDist);
1097 else if (((unsigned char)c) <= 31)
1098 return strlen(ControlCodeTable[(unsigned char)c]) + 2;
1099 else if (c == 127)
1100 return 5;
1101 return 1;
1105 ** Count the number of displayed characters between buffer position
1106 ** "lineStartPos" and "targetPos". (displayed characters are the characters
1107 ** shown on the screen to represent characters in the buffer, where tabs and
1108 ** control characters are expanded)
1110 int BufCountDispChars(const textBuffer* buf, const int lineStartPos,
1111 const int targetPos)
1113 int pos, charCount = 0;
1114 char expandedChar[MAX_EXP_CHAR_LEN];
1116 pos = lineStartPos;
1117 while (pos < targetPos && pos < buf->length)
1118 charCount += BufGetExpandedChar(buf, pos++, charCount, expandedChar);
1119 return charCount;
1123 ** Count forward from buffer position "startPos" in displayed characters
1124 ** (displayed characters are the characters shown on the screen to represent
1125 ** characters in the buffer, where tabs and control characters are expanded)
1127 int BufCountForwardDispChars(textBuffer *buf, int lineStartPos, int nChars)
1129 int pos, charCount = 0;
1130 char c;
1132 pos = lineStartPos;
1133 while (charCount < nChars && pos < buf->length) {
1134 c = BufGetCharacter(buf, pos);
1135 if (c == '\n')
1136 return pos;
1137 charCount += BufCharWidth(c, charCount, buf->tabDist,buf->nullSubsChar);
1138 pos++;
1140 return pos;
1144 ** Count the number of newlines between startPos and endPos in buffer "buf".
1145 ** The character at position "endPos" is not counted.
1147 int BufCountLines(textBuffer *buf, int startPos, int endPos)
1149 int pos, gapLen = buf->gapEnd - buf->gapStart;
1150 int lineCount = 0;
1152 pos = startPos;
1153 while (pos < buf->gapStart) {
1154 if (pos == endPos)
1155 return lineCount;
1156 if (buf->buf[pos++] == '\n')
1157 lineCount++;
1159 while (pos < buf->length) {
1160 if (pos == endPos)
1161 return lineCount;
1162 if (buf->buf[pos++ + gapLen] == '\n')
1163 lineCount++;
1165 return lineCount;
1169 ** Find the first character of the line "nLines" forward from "startPos"
1170 ** in "buf" and return its position
1172 int BufCountForwardNLines(const textBuffer* buf, const int startPos,
1173 const unsigned nLines)
1175 int pos, gapLen = buf->gapEnd - buf->gapStart;
1176 int lineCount = 0;
1178 if (nLines == 0)
1179 return startPos;
1181 pos = startPos;
1182 while (pos < buf->gapStart) {
1183 if (buf->buf[pos++] == '\n') {
1184 lineCount++;
1185 if (lineCount == nLines)
1186 return pos;
1189 while (pos < buf->length) {
1190 if (buf->buf[pos++ + gapLen] == '\n') {
1191 lineCount++;
1192 if (lineCount >= nLines)
1193 return pos;
1196 return pos;
1200 ** Find the position of the first character of the line "nLines" backwards
1201 ** from "startPos" (not counting the character pointed to by "startpos" if
1202 ** that is a newline) in "buf". nLines == 0 means find the beginning of
1203 ** the line
1205 int BufCountBackwardNLines(textBuffer *buf, int startPos, int nLines)
1207 int pos, gapLen = buf->gapEnd - buf->gapStart;
1208 int lineCount = -1;
1210 pos = startPos - 1;
1211 if (pos <= 0)
1212 return 0;
1214 while (pos >= buf->gapStart) {
1215 if (buf->buf[pos + gapLen] == '\n') {
1216 if (++lineCount >= nLines)
1217 return pos + 1;
1219 pos--;
1221 while (pos >= 0) {
1222 if (buf->buf[pos] == '\n') {
1223 if (++lineCount >= nLines)
1224 return pos + 1;
1226 pos--;
1228 return 0;
1232 ** Search forwards in buffer "buf" for characters in "searchChars", starting
1233 ** with the character "startPos", and returning the result in "foundPos"
1234 ** returns True if found, False if not.
1236 int BufSearchForward(textBuffer *buf, int startPos, const char *searchChars,
1237 int *foundPos)
1239 int pos, gapLen = buf->gapEnd - buf->gapStart;
1240 const char *c;
1242 pos = startPos;
1243 while (pos < buf->gapStart) {
1244 for (c=searchChars; *c!='\0'; c++) {
1245 if (buf->buf[pos] == *c) {
1246 *foundPos = pos;
1247 return True;
1250 pos++;
1252 while (pos < buf->length) {
1253 for (c=searchChars; *c!='\0'; c++) {
1254 if (buf->buf[pos + gapLen] == *c) {
1255 *foundPos = pos;
1256 return True;
1259 pos++;
1261 *foundPos = buf->length;
1262 return False;
1266 ** Search backwards in buffer "buf" for characters in "searchChars", starting
1267 ** with the character BEFORE "startPos", returning the result in "foundPos"
1268 ** returns True if found, False if not.
1270 int BufSearchBackward(textBuffer *buf, int startPos, const char *searchChars,
1271 int *foundPos)
1273 int pos, gapLen = buf->gapEnd - buf->gapStart;
1274 const char *c;
1276 if (startPos == 0) {
1277 *foundPos = 0;
1278 return False;
1280 pos = startPos == 0 ? 0 : startPos - 1;
1281 while (pos >= buf->gapStart) {
1282 for (c=searchChars; *c!='\0'; c++) {
1283 if (buf->buf[pos + gapLen] == *c) {
1284 *foundPos = pos;
1285 return True;
1288 pos--;
1290 while (pos >= 0) {
1291 for (c=searchChars; *c!='\0'; c++) {
1292 if (buf->buf[pos] == *c) {
1293 *foundPos = pos;
1294 return True;
1297 pos--;
1299 *foundPos = 0;
1300 return False;
1304 ** A horrible design flaw in NEdit (from the very start, before we knew that
1305 ** NEdit would become so popular), is that it uses C NULL terminated strings
1306 ** to hold text. This means editing text containing NUL characters is not
1307 ** possible without special consideration. Here is the special consideration.
1308 ** The routines below maintain a special substitution-character which stands
1309 ** in for a null, and translates strings an buffers back and forth from/to
1310 ** the substituted form, figure out what to substitute, and figure out
1311 ** when we're in over our heads and no translation is possible.
1315 ** The primary routine for integrating new text into a text buffer with
1316 ** substitution of another character for ascii nuls. This substitutes null
1317 ** characters in the string in preparation for being copied or replaced
1318 ** into the buffer, and if neccessary, adjusts the buffer as well, in the
1319 ** event that the string contains the character it is currently using for
1320 ** substitution. Returns False, if substitution is no longer possible
1321 ** because all non-printable characters are already in use.
1323 int BufSubstituteNullChars(char *string, int length, textBuffer *buf)
1325 char histogram[256];
1327 /* Find out what characters the string contains */
1328 histogramCharacters(string, length, histogram, True);
1330 /* Does the string contain the null-substitute character? If so, re-
1331 histogram the buffer text to find a character which is ok in both the
1332 string and the buffer, and change the buffer's null-substitution
1333 character. If none can be found, give up and return False */
1334 if (histogram[(unsigned char)buf->nullSubsChar] != 0) {
1335 char *bufString, newSubsChar;
1336 /* here we know we can modify the file buffer directly,
1337 so we cast away constness */
1338 bufString = (char *)BufAsString(buf);
1339 histogramCharacters(bufString, buf->length, histogram, False);
1340 newSubsChar = chooseNullSubsChar(histogram);
1341 if (newSubsChar == '\0') {
1342 return False;
1344 /* bufString points to the buffer's data, so we substitute in situ */
1345 subsChars(bufString, buf->length, buf->nullSubsChar, newSubsChar);
1346 buf->nullSubsChar = newSubsChar;
1349 /* If the string contains null characters, substitute them with the
1350 buffer's null substitution character */
1351 if (histogram[0] != 0)
1352 subsChars(string, length, '\0', buf->nullSubsChar);
1353 return True;
1357 ** Convert strings obtained from buffers which contain null characters, which
1358 ** have been substituted for by a special substitution character, back to
1359 ** a null-containing string. There is no time penalty for calling this
1360 ** routine if no substitution has been done.
1362 void BufUnsubstituteNullChars(char *string, textBuffer *buf)
1364 register char *c, subsChar = buf->nullSubsChar;
1366 if (subsChar == '\0')
1367 return;
1368 for (c=string; *c != '\0'; c++)
1369 if (*c == subsChar)
1370 *c = '\0';
1374 ** Compares len Bytes contained in buf starting at Position pos with
1375 ** the contens of cmpText. Returns 0 if there are no differences,
1376 ** != 0 otherwise.
1379 int BufCmp(textBuffer * buf, int pos, int len, const char *cmpText)
1381 int posEnd;
1382 int part1Length;
1383 int result;
1385 posEnd = pos + len;
1386 if (posEnd > buf->length) {
1387 return (1);
1389 if (pos < 0) {
1390 return (-1);
1393 if (posEnd <= buf->gapStart) {
1394 return (strncmp(&(buf->buf[pos]), cmpText, len));
1395 } else if (pos >= buf->gapStart) {
1396 return (strncmp (&buf->buf[pos + (buf->gapEnd - buf->gapStart)],
1397 cmpText, len));
1398 } else {
1399 part1Length = buf->gapStart - pos;
1400 result = strncmp(&buf->buf[pos], cmpText, part1Length);
1401 if (result) {
1402 return (result);
1404 return (strncmp(&buf->buf[buf->gapEnd], &cmpText[part1Length],
1405 len - part1Length));
1410 ** Create a pseudo-histogram of the characters in a string (don't actually
1411 ** count, because we don't want overflow, just mark the character's presence
1412 ** with a 1). If init is true, initialize the histogram before acumulating.
1413 ** if not, add the new data to an existing histogram.
1415 static void histogramCharacters(const char *string, int length, char hist[256],
1416 int init)
1418 int i;
1419 const char *c;
1421 if (init)
1422 for (i=0; i<256; i++)
1423 hist[i] = 0;
1424 for (c=string; c < &string[length]; c++)
1425 hist[*((unsigned char *)c)] |= 1;
1429 ** Substitute fromChar with toChar in string.
1431 static void subsChars(char *string, int length, char fromChar, char toChar)
1433 char *c;
1435 for (c=string; c < &string[length]; c++)
1436 if (*c == fromChar) *c = toChar;
1440 ** Search through ascii control characters in histogram in order of least
1441 ** likelihood of use, find an unused character to use as a stand-in for a
1442 ** null. If the character set is full (no available characters outside of
1443 ** the printable set, return the null character.
1445 static char chooseNullSubsChar(char hist[256])
1447 #define N_REPLACEMENTS 25
1448 static char replacements[N_REPLACEMENTS] = {1,2,3,4,5,6,14,15,16,17,18,19,
1449 20,21,22,23,24,25,26,28,29,30,31,11,7};
1450 int i;
1451 for (i = 0; i < N_REPLACEMENTS; i++)
1452 if (hist[(unsigned char)replacements[i]] == 0)
1453 return replacements[i];
1454 return '\0';
1458 ** Internal (non-redisplaying) version of BufInsert. Returns the length of
1459 ** text inserted (this is just strlen(text), however this calculation can be
1460 ** expensive and the length will be required by any caller who will continue
1461 ** on to call redisplay). pos must be contiguous with the existing text in
1462 ** the buffer (i.e. not past the end).
1464 static int insert(textBuffer *buf, int pos, const char *text)
1466 int length = strlen(text);
1468 /* Prepare the buffer to receive the new text. If the new text fits in
1469 the current buffer, just move the gap (if necessary) to where
1470 the text should be inserted. If the new text is too large, reallocate
1471 the buffer with a gap large enough to accomodate the new text and a
1472 gap of PREFERRED_GAP_SIZE */
1473 if (length > buf->gapEnd - buf->gapStart)
1474 reallocateBuf(buf, pos, length + PREFERRED_GAP_SIZE);
1475 else if (pos != buf->gapStart)
1476 moveGap(buf, pos);
1478 /* Insert the new text (pos now corresponds to the start of the gap) */
1479 memcpy(&buf->buf[pos], text, length);
1480 buf->gapStart += length;
1481 buf->length += length;
1482 updateSelections(buf, pos, 0, length);
1484 return length;
1488 ** Internal (non-redisplaying) version of BufRemove. Removes the contents
1489 ** of the buffer between start and end (and moves the gap to the site of
1490 ** the delete).
1492 static void delete(textBuffer *buf, int start, int end)
1494 /* if the gap is not contiguous to the area to remove, move it there */
1495 if (start > buf->gapStart)
1496 moveGap(buf, start);
1497 else if (end < buf->gapStart)
1498 moveGap(buf, end);
1500 /* expand the gap to encompass the deleted characters */
1501 buf->gapEnd += end - buf->gapStart;
1502 buf->gapStart -= buf->gapStart - start;
1504 /* update the length */
1505 buf->length -= end - start;
1507 /* fix up any selections which might be affected by the change */
1508 updateSelections(buf, start, end-start, 0);
1512 ** Insert a column of text without calling the modify callbacks. Note that
1513 ** in some pathological cases, inserting can actually decrease the size of
1514 ** the buffer because of spaces being coalesced into tabs. "nDeleted" and
1515 ** "nInserted" return the number of characters deleted and inserted beginning
1516 ** at the start of the line containing "startPos". "endPos" returns buffer
1517 ** position of the lower left edge of the inserted column (as a hint for
1518 ** routines which need to set a cursor position).
1520 static void insertCol(textBuffer *buf, int column, int startPos,
1521 const char *insText, int *nDeleted, int *nInserted, int *endPos)
1523 int nLines, start, end, insWidth, lineStart, lineEnd;
1524 int expReplLen, expInsLen, len, endOffset;
1525 char *outStr, *outPtr, *line, *replText, *expText, *insLine;
1526 const char *insPtr;
1528 if (column < 0)
1529 column = 0;
1531 /* Allocate a buffer for the replacement string large enough to hold
1532 possibly expanded tabs in both the inserted text and the replaced
1533 area, as well as per line: 1) an additional 2*MAX_EXP_CHAR_LEN
1534 characters for padding where tabs and control characters cross the
1535 column of the selection, 2) up to "column" additional spaces per
1536 line for padding out to the position of "column", 3) padding up
1537 to the width of the inserted text if that must be padded to align
1538 the text beyond the inserted column. (Space for additional
1539 newlines if the inserted text extends beyond the end of the buffer
1540 is counted with the length of insText) */
1541 start = BufStartOfLine(buf, startPos);
1542 nLines = countLines(insText) + 1;
1543 insWidth = textWidth(insText, buf->tabDist, buf->nullSubsChar);
1544 end = BufEndOfLine(buf, BufCountForwardNLines(buf, start, nLines-1));
1545 replText = BufGetRange(buf, start, end);
1546 expText = expandTabs(replText, 0, buf->tabDist, buf->nullSubsChar,
1547 &expReplLen);
1548 XtFree(replText);
1549 XtFree(expText);
1550 expText = expandTabs(insText, 0, buf->tabDist, buf->nullSubsChar,
1551 &expInsLen);
1552 XtFree(expText);
1553 outStr = XtMalloc(expReplLen + expInsLen +
1554 nLines * (column + insWidth + MAX_EXP_CHAR_LEN) + 1);
1556 /* Loop over all lines in the buffer between start and end inserting
1557 text at column, splitting tabs and adding padding appropriately */
1558 outPtr = outStr;
1559 lineStart = start;
1560 insPtr = insText;
1561 while (True) {
1562 lineEnd = BufEndOfLine(buf, lineStart);
1563 line = BufGetRange(buf, lineStart, lineEnd);
1564 insLine = copyLine(insPtr, &len);
1565 insPtr += len;
1566 insertColInLine(line, insLine, column, insWidth, buf->tabDist,
1567 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1568 XtFree(line);
1569 XtFree(insLine);
1570 #if 0 /* Earlier comments claimed that trailing whitespace could multiply on
1571 the ends of lines, but insertColInLine looks like it should never
1572 add space unnecessarily, and this trimming interfered with
1573 paragraph filling, so lets see if it works without it. MWE */
1575 char *c;
1576 for (c=outPtr+len-1; c>outPtr && (*c == ' ' || *c == '\t'); c--)
1577 len--;
1579 #endif
1580 outPtr += len;
1581 *outPtr++ = '\n';
1582 lineStart = lineEnd < buf->length ? lineEnd + 1 : buf->length;
1583 if (*insPtr == '\0')
1584 break;
1585 insPtr++;
1587 if (outPtr != outStr)
1588 outPtr--; /* trim back off extra newline */
1589 *outPtr = '\0';
1591 /* replace the text between start and end with the new stuff */
1592 delete(buf, start, end);
1593 insert(buf, start, outStr);
1594 *nInserted = outPtr - outStr;
1595 *nDeleted = end - start;
1596 *endPos = start + (outPtr - outStr) - len + endOffset;
1597 XtFree(outStr);
1601 ** Delete a rectangle of text without calling the modify callbacks. Returns
1602 ** the number of characters replacing those between start and end. Note that
1603 ** in some pathological cases, deleting can actually increase the size of
1604 ** the buffer because of tab expansions. "endPos" returns the buffer position
1605 ** of the point in the last line where the text was removed (as a hint for
1606 ** routines which need to position the cursor after a delete operation)
1608 static void deleteRect(textBuffer *buf, int start, int end, int rectStart,
1609 int rectEnd, int *replaceLen, int *endPos)
1611 int nLines, lineStart, lineEnd, len, endOffset;
1612 char *outStr, *outPtr, *line, *text, *expText;
1614 /* allocate a buffer for the replacement string large enough to hold
1615 possibly expanded tabs as well as an additional MAX_EXP_CHAR_LEN * 2
1616 characters per line for padding where tabs and control characters cross
1617 the edges of the selection */
1618 start = BufStartOfLine(buf, start);
1619 end = BufEndOfLine(buf, end);
1620 nLines = BufCountLines(buf, start, end) + 1;
1621 text = BufGetRange(buf, start, end);
1622 expText = expandTabs(text, 0, buf->tabDist, buf->nullSubsChar, &len);
1623 XtFree(text);
1624 XtFree(expText);
1625 outStr = XtMalloc(len + nLines * MAX_EXP_CHAR_LEN * 2 + 1);
1627 /* loop over all lines in the buffer between start and end removing
1628 the text between rectStart and rectEnd and padding appropriately */
1629 lineStart = start;
1630 outPtr = outStr;
1631 while (lineStart <= buf->length && lineStart <= end) {
1632 lineEnd = BufEndOfLine(buf, lineStart);
1633 line = BufGetRange(buf, lineStart, lineEnd);
1634 deleteRectFromLine(line, rectStart, rectEnd, buf->tabDist,
1635 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1636 XtFree(line);
1637 outPtr += len;
1638 *outPtr++ = '\n';
1639 lineStart = lineEnd + 1;
1641 if (outPtr != outStr)
1642 outPtr--; /* trim back off extra newline */
1643 *outPtr = '\0';
1645 /* replace the text between start and end with the newly created string */
1646 delete(buf, start, end);
1647 insert(buf, start, outStr);
1648 *replaceLen = outPtr - outStr;
1649 *endPos = start + (outPtr - outStr) - len + endOffset;
1650 XtFree(outStr);
1654 ** Overlay a rectangular area of text without calling the modify callbacks.
1655 ** "nDeleted" and "nInserted" return the number of characters deleted and
1656 ** inserted beginning at the start of the line containing "startPos".
1657 ** "endPos" returns buffer position of the lower left edge of the inserted
1658 ** column (as a hint for routines which need to set a cursor position).
1660 static void overlayRect(textBuffer *buf, int startPos, int rectStart,
1661 int rectEnd, const char *insText,
1662 int *nDeleted, int *nInserted, int *endPos)
1664 int nLines, start, end, lineStart, lineEnd;
1665 int expInsLen, len, endOffset;
1666 char *c, *outStr, *outPtr, *line, *expText, *insLine;
1667 const char *insPtr;
1669 /* Allocate a buffer for the replacement string large enough to hold
1670 possibly expanded tabs in the inserted text, as well as per line: 1)
1671 an additional 2*MAX_EXP_CHAR_LEN characters for padding where tabs
1672 and control characters cross the column of the selection, 2) up to
1673 "column" additional spaces per line for padding out to the position
1674 of "column", 3) padding up to the width of the inserted text if that
1675 must be padded to align the text beyond the inserted column. (Space
1676 for additional newlines if the inserted text extends beyond the end
1677 of the buffer is counted with the length of insText) */
1678 start = BufStartOfLine(buf, startPos);
1679 nLines = countLines(insText) + 1;
1680 end = BufEndOfLine(buf, BufCountForwardNLines(buf, start, nLines-1));
1681 expText = expandTabs(insText, 0, buf->tabDist, buf->nullSubsChar,
1682 &expInsLen);
1683 XtFree(expText);
1684 outStr = XtMalloc(end-start + expInsLen +
1685 nLines * (rectEnd + MAX_EXP_CHAR_LEN) + 1);
1687 /* Loop over all lines in the buffer between start and end overlaying the
1688 text between rectStart and rectEnd and padding appropriately. Trim
1689 trailing space from line (whitespace at the ends of lines otherwise
1690 tends to multiply, since additional padding is added to maintain it */
1691 outPtr = outStr;
1692 lineStart = start;
1693 insPtr = insText;
1694 while (True) {
1695 lineEnd = BufEndOfLine(buf, lineStart);
1696 line = BufGetRange(buf, lineStart, lineEnd);
1697 insLine = copyLine(insPtr, &len);
1698 insPtr += len;
1699 overlayRectInLine(line, insLine, rectStart, rectEnd, buf->tabDist,
1700 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1701 XtFree(line);
1702 XtFree(insLine);
1703 for (c=outPtr+len-1; c>outPtr && (*c == ' ' || *c == '\t'); c--)
1704 len--;
1705 outPtr += len;
1706 *outPtr++ = '\n';
1707 lineStart = lineEnd < buf->length ? lineEnd + 1 : buf->length;
1708 if (*insPtr == '\0')
1709 break;
1710 insPtr++;
1712 if (outPtr != outStr)
1713 outPtr--; /* trim back off extra newline */
1714 *outPtr = '\0';
1716 /* replace the text between start and end with the new stuff */
1717 delete(buf, start, end);
1718 insert(buf, start, outStr);
1719 *nInserted = outPtr - outStr;
1720 *nDeleted = end - start;
1721 *endPos = start + (outPtr - outStr) - len + endOffset;
1722 XtFree(outStr);
1726 ** Insert characters from single-line string "insLine" in single-line string
1727 ** "line" at "column", leaving "insWidth" space before continuing line.
1728 ** "outLen" returns the number of characters written to "outStr", "endOffset"
1729 ** returns the number of characters from the beginning of the string to
1730 ** the right edge of the inserted text (as a hint for routines which need
1731 ** to position the cursor).
1733 static void insertColInLine(const char *line, const char *insLine,
1734 int column, int insWidth, int tabDist, int useTabs, char nullSubsChar,
1735 char *outStr, int *outLen, int *endOffset)
1737 char *c, *outPtr, *retabbedStr;
1738 const char *linePtr;
1739 int indent, toIndent, len, postColIndent;
1741 /* copy the line up to "column" */
1742 outPtr = outStr;
1743 indent = 0;
1744 for (linePtr=line; *linePtr!='\0'; linePtr++) {
1745 len = BufCharWidth(*linePtr, indent, tabDist, nullSubsChar);
1746 if (indent + len > column)
1747 break;
1748 indent += len;
1749 *outPtr++ = *linePtr;
1752 /* If "column" falls in the middle of a character, and the character is a
1753 tab, leave it off and leave the indent short and it will get padded
1754 later. If it's a control character, insert it and adjust indent
1755 accordingly. */
1756 if (indent < column && *linePtr != '\0') {
1757 postColIndent = indent + len;
1758 if (*linePtr == '\t')
1759 linePtr++;
1760 else {
1761 *outPtr++ = *linePtr++;
1762 indent += len;
1764 } else
1765 postColIndent = indent;
1767 /* If there's no text after the column and no text to insert, that's all */
1768 if (*insLine == '\0' && *linePtr == '\0') {
1769 *outLen = *endOffset = outPtr - outStr;
1770 return;
1773 /* pad out to column if text is too short */
1774 if (indent < column) {
1775 addPadding(outPtr, indent, column, tabDist, useTabs, nullSubsChar,&len);
1776 outPtr += len;
1777 indent = column;
1780 /* Copy the text from "insLine" (if any), recalculating the tabs as if
1781 the inserted string began at column 0 to its new column destination */
1782 if (*insLine != '\0') {
1783 retabbedStr = realignTabs(insLine, 0, indent, tabDist, useTabs,
1784 nullSubsChar, &len);
1785 for (c=retabbedStr; *c!='\0'; c++) {
1786 *outPtr++ = *c;
1787 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
1788 indent += len;
1790 XtFree(retabbedStr);
1793 /* If the original line did not extend past "column", that's all */
1794 if (*linePtr == '\0') {
1795 *outLen = *endOffset = outPtr - outStr;
1796 return;
1799 /* Pad out to column + width of inserted text + (additional original
1800 offset due to non-breaking character at column) */
1801 toIndent = column + insWidth + postColIndent-column;
1802 addPadding(outPtr, indent, toIndent, tabDist, useTabs, nullSubsChar, &len);
1803 outPtr += len;
1804 indent = toIndent;
1806 /* realign tabs for text beyond "column" and write it out */
1807 retabbedStr = realignTabs(linePtr, postColIndent, indent, tabDist,
1808 useTabs, nullSubsChar, &len);
1809 strcpy(outPtr, retabbedStr);
1810 XtFree(retabbedStr);
1811 *endOffset = outPtr - outStr;
1812 *outLen = (outPtr - outStr) + len;
1816 ** Remove characters in single-line string "line" between displayed positions
1817 ** "rectStart" and "rectEnd", and write the result to "outStr", which is
1818 ** assumed to be large enough to hold the returned string. Note that in
1819 ** certain cases, it is possible for the string to get longer due to
1820 ** expansion of tabs. "endOffset" returns the number of characters from
1821 ** the beginning of the string to the point where the characters were
1822 ** deleted (as a hint for routines which need to position the cursor).
1824 static void deleteRectFromLine(const char *line, int rectStart, int rectEnd,
1825 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
1826 int *endOffset)
1828 int indent, preRectIndent, postRectIndent, len;
1829 const char *c;
1830 char *outPtr;
1831 char *retabbedStr;
1833 /* copy the line up to rectStart */
1834 outPtr = outStr;
1835 indent = 0;
1836 for (c=line; *c!='\0'; c++) {
1837 if (indent > rectStart)
1838 break;
1839 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
1840 if (indent + len > rectStart && (indent == rectStart || *c == '\t'))
1841 break;
1842 indent += len;
1843 *outPtr++ = *c;
1845 preRectIndent = indent;
1847 /* skip the characters between rectStart and rectEnd */
1848 for(; *c!='\0' && indent<rectEnd; c++)
1849 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
1850 postRectIndent = indent;
1852 /* If the line ended before rectEnd, there's nothing more to do */
1853 if (*c == '\0') {
1854 *outPtr = '\0';
1855 *outLen = *endOffset = outPtr - outStr;
1856 return;
1859 /* fill in any space left by removed tabs or control characters
1860 which straddled the boundaries */
1861 indent = max(rectStart + postRectIndent-rectEnd, preRectIndent);
1862 addPadding(outPtr, preRectIndent, indent, tabDist, useTabs, nullSubsChar,
1863 &len);
1864 outPtr += len;
1866 /* Copy the rest of the line. If the indentation has changed, preserve
1867 the position of non-whitespace characters by converting tabs to
1868 spaces, then back to tabs with the correct offset */
1869 retabbedStr = realignTabs(c, postRectIndent, indent, tabDist, useTabs,
1870 nullSubsChar, &len);
1871 strcpy(outPtr, retabbedStr);
1872 XtFree(retabbedStr);
1873 *endOffset = outPtr - outStr;
1874 *outLen = (outPtr - outStr) + len;
1878 ** Overlay characters from single-line string "insLine" on single-line string
1879 ** "line" between displayed character offsets "rectStart" and "rectEnd".
1880 ** "outLen" returns the number of characters written to "outStr", "endOffset"
1881 ** returns the number of characters from the beginning of the string to
1882 ** the right edge of the inserted text (as a hint for routines which need
1883 ** to position the cursor).
1885 ** This code does not handle control characters very well, but oh well.
1887 static void overlayRectInLine(const char *line, const char *insLine,
1888 int rectStart, int rectEnd, int tabDist, int useTabs,
1889 char nullSubsChar, char *outStr, int *outLen, int *endOffset)
1891 char *c, *outPtr, *retabbedStr;
1892 const char *linePtr;
1893 int inIndent, outIndent, len, postRectIndent;
1895 /* copy the line up to "rectStart" or just before the char that
1896 contains it*/
1897 outPtr = outStr;
1898 inIndent = outIndent = 0;
1899 for (linePtr=line; *linePtr!='\0'; linePtr++) {
1900 len = BufCharWidth(*linePtr, inIndent, tabDist, nullSubsChar);
1901 if (inIndent + len > rectStart)
1902 break;
1903 inIndent += len;
1904 outIndent += len;
1905 *outPtr++ = *linePtr;
1908 /* If "rectStart" falls in the middle of a character, and the character
1909 is a tab, leave it off and leave the outIndent short and it will get
1910 padded later. If it's a control character, insert it and adjust
1911 outIndent accordingly. */
1912 if (inIndent < rectStart && *linePtr != '\0') {
1913 if (*linePtr == '\t') {
1914 /* Skip past the tab */
1915 linePtr++;
1916 inIndent += len;
1917 } else {
1918 *outPtr++ = *linePtr++;
1919 outIndent += len;
1920 inIndent += len;
1924 /* skip the characters between rectStart and rectEnd */
1925 for(; *linePtr!='\0' && inIndent < rectEnd; linePtr++)
1926 inIndent += BufCharWidth(*linePtr, inIndent, tabDist, nullSubsChar);
1927 postRectIndent = inIndent;
1929 /* After this inIndent is dead and linePtr is supposed to point at the
1930 character just past the last character that will be altered by
1931 the overlay, whether that's a \t or otherwise. postRectIndent is
1932 the position at which that character is supposed to appear */
1934 /* If there's no text after rectStart and no text to insert, that's all */
1935 if (*insLine == '\0' && *linePtr == '\0') {
1936 *outLen = *endOffset = outPtr - outStr;
1937 return;
1940 /* pad out to rectStart if text is too short */
1941 if (outIndent < rectStart) {
1942 addPadding(outPtr, outIndent, rectStart, tabDist, useTabs, nullSubsChar,
1943 &len);
1944 outPtr += len;
1946 outIndent = rectStart;
1948 /* Copy the text from "insLine" (if any), recalculating the tabs as if
1949 the inserted string began at column 0 to its new column destination */
1950 if (*insLine != '\0') {
1951 retabbedStr = realignTabs(insLine, 0, rectStart, tabDist, useTabs,
1952 nullSubsChar, &len);
1953 for (c=retabbedStr; *c!='\0'; c++) {
1954 *outPtr++ = *c;
1955 len = BufCharWidth(*c, outIndent, tabDist, nullSubsChar);
1956 outIndent += len;
1958 XtFree(retabbedStr);
1961 /* If the original line did not extend past "rectStart", that's all */
1962 if (*linePtr == '\0') {
1963 *outLen = *endOffset = outPtr - outStr;
1964 return;
1967 /* Pad out to rectEnd + (additional original offset
1968 due to non-breaking character at right boundary) */
1969 addPadding(outPtr, outIndent, postRectIndent, tabDist, useTabs,
1970 nullSubsChar, &len);
1971 outPtr += len;
1972 outIndent = postRectIndent;
1974 /* copy the text beyond "rectEnd" */
1975 strcpy(outPtr, linePtr);
1976 *endOffset = outPtr - outStr;
1977 *outLen = (outPtr - outStr) + strlen(linePtr);
1980 static void setSelection(selection *sel, int start, int end)
1982 sel->selected = start != end;
1983 sel->zeroWidth = (start == end) ? 1 : 0;
1984 sel->rectangular = False;
1985 sel->start = min(start, end);
1986 sel->end = max(start, end);
1989 static void setRectSelect(selection *sel, int start, int end,
1990 int rectStart, int rectEnd)
1992 sel->selected = rectStart < rectEnd;
1993 sel->zeroWidth = (rectStart == rectEnd) ? 1 : 0;
1994 sel->rectangular = True;
1995 sel->start = start;
1996 sel->end = end;
1997 sel->rectStart = rectStart;
1998 sel->rectEnd = rectEnd;
2001 static int getSelectionPos(selection *sel, int *start, int *end,
2002 int *isRect, int *rectStart, int *rectEnd)
2004 /* Always fill in the parameters (zero-width can be requested too). */
2005 *isRect = sel->rectangular;
2006 *start = sel->start;
2007 *end = sel->end;
2008 if (sel->rectangular) {
2009 *rectStart = sel->rectStart;
2010 *rectEnd = sel->rectEnd;
2012 return sel->selected;
2015 static char *getSelectionText(textBuffer *buf, selection *sel)
2017 int start, end, isRect, rectStart, rectEnd;
2018 char *text;
2020 /* If there's no selection, return an allocated empty string */
2021 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd)) {
2022 text = XtMalloc(1);
2023 *text = '\0';
2024 return text;
2027 /* If the selection is not rectangular, return the selected range */
2028 if (isRect)
2029 return BufGetTextInRect(buf, start, end, rectStart, rectEnd);
2030 else
2031 return BufGetRange(buf, start, end);
2034 static void removeSelected(textBuffer *buf, selection *sel)
2036 int start, end;
2037 int isRect, rectStart, rectEnd;
2039 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd))
2040 return;
2041 if (isRect)
2042 BufRemoveRect(buf, start, end, rectStart, rectEnd);
2043 else
2044 BufRemove(buf, start, end);
2047 static void replaceSelected(textBuffer *buf, selection *sel, const char *text)
2049 int start, end, isRect, rectStart, rectEnd;
2050 selection oldSelection = *sel;
2052 /* If there's no selection, return */
2053 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd))
2054 return;
2056 /* Do the appropriate type of replace */
2057 if (isRect)
2058 BufReplaceRect(buf, start, end, rectStart, rectEnd, text);
2059 else
2060 BufReplace(buf, start, end, text);
2062 /* Unselect (happens automatically in BufReplace, but BufReplaceRect
2063 can't detect when the contents of a selection goes away) */
2064 sel->selected = False;
2065 redisplaySelection(buf, &oldSelection, sel);
2068 static void addPadding(char *string, int startIndent, int toIndent,
2069 int tabDist, int useTabs, char nullSubsChar, int *charsAdded)
2071 char *outPtr;
2072 int len, indent;
2074 indent = startIndent;
2075 outPtr = string;
2076 if (useTabs) {
2077 while (indent < toIndent) {
2078 len = BufCharWidth('\t', indent, tabDist, nullSubsChar);
2079 if (len > 1 && indent + len <= toIndent) {
2080 *outPtr++ = '\t';
2081 indent += len;
2082 } else {
2083 *outPtr++ = ' ';
2084 indent++;
2087 } else {
2088 while (indent < toIndent) {
2089 *outPtr++ = ' ';
2090 indent++;
2093 *charsAdded = outPtr - string;
2097 ** Call the stored modify callback procedure(s) for this buffer to update the
2098 ** changed area(s) on the screen and any other listeners.
2100 static void callModifyCBs(textBuffer *buf, int pos, int nDeleted,
2101 int nInserted, int nRestyled, const char *deletedText)
2103 int i;
2105 for (i=0; i<buf->nModifyProcs; i++)
2106 (*buf->modifyProcs[i])(pos, nInserted, nDeleted, nRestyled,
2107 deletedText, buf->cbArgs[i]);
2111 ** Call the stored pre-delete callback procedure(s) for this buffer to update
2112 ** the changed area(s) on the screen and any other listeners.
2114 static void callPreDeleteCBs(textBuffer *buf, int pos, int nDeleted)
2116 int i;
2118 for (i=0; i<buf->nPreDeleteProcs; i++)
2119 (*buf->preDeleteProcs[i])(pos, nDeleted, buf->preDeleteCbArgs[i]);
2123 ** Call the stored redisplay procedure(s) for this buffer to update the
2124 ** screen for a change in a selection.
2126 static void redisplaySelection(textBuffer *buf, selection *oldSelection,
2127 selection *newSelection)
2129 int oldStart, oldEnd, newStart, newEnd, ch1Start, ch1End, ch2Start, ch2End;
2131 /* If either selection is rectangular, add an additional character to
2132 the end of the selection to request the redraw routines to wipe out
2133 the parts of the selection beyond the end of the line */
2134 oldStart = oldSelection->start;
2135 newStart = newSelection->start;
2136 oldEnd = oldSelection->end;
2137 newEnd = newSelection->end;
2138 if (oldSelection->rectangular)
2139 oldEnd++;
2140 if (newSelection->rectangular)
2141 newEnd++;
2143 /* If the old or new selection is unselected, just redisplay the
2144 single area that is (was) selected and return */
2145 if (!oldSelection->selected && !newSelection->selected)
2146 return;
2147 if (!oldSelection->selected) {
2148 callModifyCBs(buf, newStart, 0, 0, newEnd-newStart, NULL);
2149 return;
2151 if (!newSelection->selected) {
2152 callModifyCBs(buf, oldStart, 0, 0, oldEnd-oldStart, NULL);
2153 return;
2156 /* If the selection changed from normal to rectangular or visa versa, or
2157 if a rectangular selection changed boundaries, redisplay everything */
2158 if ((oldSelection->rectangular && !newSelection->rectangular) ||
2159 (!oldSelection->rectangular && newSelection->rectangular) ||
2160 (oldSelection->rectangular && (
2161 (oldSelection->rectStart != newSelection->rectStart) ||
2162 (oldSelection->rectEnd != newSelection->rectEnd)))) {
2163 callModifyCBs(buf, min(oldStart, newStart), 0, 0,
2164 max(oldEnd, newEnd) - min(oldStart, newStart), NULL);
2165 return;
2168 /* If the selections are non-contiguous, do two separate updates
2169 and return */
2170 if (oldEnd < newStart || newEnd < oldStart) {
2171 callModifyCBs(buf, oldStart, 0, 0, oldEnd-oldStart, NULL);
2172 callModifyCBs(buf, newStart, 0, 0, newEnd-newStart, NULL);
2173 return;
2176 /* Otherwise, separate into 3 separate regions: ch1, and ch2 (the two
2177 changed areas), and the unchanged area of their intersection,
2178 and update only the changed area(s) */
2179 ch1Start = min(oldStart, newStart);
2180 ch2End = max(oldEnd, newEnd);
2181 ch1End = max(oldStart, newStart);
2182 ch2Start = min(oldEnd, newEnd);
2183 if (ch1Start != ch1End)
2184 callModifyCBs(buf, ch1Start, 0, 0, ch1End-ch1Start, NULL);
2185 if (ch2Start != ch2End)
2186 callModifyCBs(buf, ch2Start, 0, 0, ch2End-ch2Start, NULL);
2189 static void moveGap(textBuffer *buf, int pos)
2191 int gapLen = buf->gapEnd - buf->gapStart;
2193 if (pos > buf->gapStart)
2194 memmove(&buf->buf[buf->gapStart], &buf->buf[buf->gapEnd],
2195 pos - buf->gapStart);
2196 else
2197 memmove(&buf->buf[pos + gapLen], &buf->buf[pos], buf->gapStart - pos);
2198 buf->gapEnd += pos - buf->gapStart;
2199 buf->gapStart += pos - buf->gapStart;
2203 ** reallocate the text storage in "buf" to have a gap starting at "newGapStart"
2204 ** and a gap size of "newGapLen", preserving the buffer's current contents.
2206 static void reallocateBuf(textBuffer *buf, int newGapStart, int newGapLen)
2208 char *newBuf;
2209 int newGapEnd;
2211 newBuf = XtMalloc(buf->length + newGapLen + 1);
2212 newBuf[buf->length + PREFERRED_GAP_SIZE] = '\0';
2213 newGapEnd = newGapStart + newGapLen;
2214 if (newGapStart <= buf->gapStart) {
2215 memcpy(newBuf, buf->buf, newGapStart);
2216 memcpy(&newBuf[newGapEnd], &buf->buf[newGapStart],
2217 buf->gapStart - newGapStart);
2218 memcpy(&newBuf[newGapEnd + buf->gapStart - newGapStart],
2219 &buf->buf[buf->gapEnd], buf->length - buf->gapStart);
2220 } else { /* newGapStart > buf->gapStart */
2221 memcpy(newBuf, buf->buf, buf->gapStart);
2222 memcpy(&newBuf[buf->gapStart], &buf->buf[buf->gapEnd],
2223 newGapStart - buf->gapStart);
2224 memcpy(&newBuf[newGapEnd],
2225 &buf->buf[buf->gapEnd + newGapStart - buf->gapStart],
2226 buf->length - newGapStart);
2228 XtFree(buf->buf);
2229 buf->buf = newBuf;
2230 buf->gapStart = newGapStart;
2231 buf->gapEnd = newGapEnd;
2232 #ifdef PURIFY
2233 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
2234 #endif
2238 ** Update all of the selections in "buf" for changes in the buffer's text
2240 static void updateSelections(textBuffer *buf, int pos, int nDeleted,
2241 int nInserted)
2243 updateSelection(&buf->primary, pos, nDeleted, nInserted);
2244 updateSelection(&buf->secondary, pos, nDeleted, nInserted);
2245 updateSelection(&buf->highlight, pos, nDeleted, nInserted);
2249 ** Update an individual selection for changes in the corresponding text
2251 static void updateSelection(selection *sel, int pos, int nDeleted,
2252 int nInserted)
2254 if ((!sel->selected && !sel->zeroWidth) || pos > sel->end)
2255 return;
2256 if (pos+nDeleted <= sel->start) {
2257 sel->start += nInserted - nDeleted;
2258 sel->end += nInserted - nDeleted;
2259 } else if (pos <= sel->start && pos+nDeleted >= sel->end) {
2260 sel->start = pos;
2261 sel->end = pos;
2262 sel->selected = False;
2263 sel->zeroWidth = False;
2264 } else if (pos <= sel->start && pos+nDeleted < sel->end) {
2265 sel->start = pos;
2266 sel->end = nInserted + sel->end - nDeleted;
2267 } else if (pos < sel->end) {
2268 sel->end += nInserted - nDeleted;
2269 if (sel->end <= sel->start)
2270 sel->selected = False;
2275 ** Search forwards in buffer "buf" for character "searchChar", starting
2276 ** with the character "startPos", and returning the result in "foundPos"
2277 ** returns True if found, False if not. (The difference between this and
2278 ** BufSearchForward is that it's optimized for single characters. The
2279 ** overall performance of the text widget is dependent on its ability to
2280 ** count lines quickly, hence searching for a single character: newline)
2282 static int searchForward(textBuffer *buf, int startPos, char searchChar,
2283 int *foundPos)
2285 int pos, gapLen = buf->gapEnd - buf->gapStart;
2287 pos = startPos;
2288 while (pos < buf->gapStart) {
2289 if (buf->buf[pos] == searchChar) {
2290 *foundPos = pos;
2291 return True;
2293 pos++;
2295 while (pos < buf->length) {
2296 if (buf->buf[pos + gapLen] == searchChar) {
2297 *foundPos = pos;
2298 return True;
2300 pos++;
2302 *foundPos = buf->length;
2303 return False;
2307 ** Search backwards in buffer "buf" for character "searchChar", starting
2308 ** with the character BEFORE "startPos", returning the result in "foundPos"
2309 ** returns True if found, False if not. (The difference between this and
2310 ** BufSearchBackward is that it's optimized for single characters. The
2311 ** overall performance of the text widget is dependent on its ability to
2312 ** count lines quickly, hence searching for a single character: newline)
2314 static int searchBackward(textBuffer *buf, int startPos, char searchChar,
2315 int *foundPos)
2317 int pos, gapLen = buf->gapEnd - buf->gapStart;
2319 if (startPos == 0) {
2320 *foundPos = 0;
2321 return False;
2323 pos = startPos == 0 ? 0 : startPos - 1;
2324 while (pos >= buf->gapStart) {
2325 if (buf->buf[pos + gapLen] == searchChar) {
2326 *foundPos = pos;
2327 return True;
2329 pos--;
2331 while (pos >= 0) {
2332 if (buf->buf[pos] == searchChar) {
2333 *foundPos = pos;
2334 return True;
2336 pos--;
2338 *foundPos = 0;
2339 return False;
2343 ** Copy from "text" to end up to but not including newline (or end of "text")
2344 ** and return the copy as the function value, and the length of the line in
2345 ** "lineLen"
2347 static char *copyLine(const char *text, int *lineLen)
2349 int len = 0;
2350 const char *c;
2351 char *outStr;
2353 for (c=text; *c!='\0' && *c!='\n'; c++)
2354 len++;
2355 outStr = XtMalloc(len + 1);
2356 strncpy(outStr, text, len);
2357 outStr[len] = '\0';
2358 *lineLen = len;
2359 return outStr;
2363 ** Count the number of newlines in a null-terminated text string;
2365 static int countLines(const char *string)
2367 const char *c;
2368 int lineCount = 0;
2370 for (c=string; *c!='\0'; c++)
2371 if (*c == '\n') lineCount++;
2372 return lineCount;
2376 ** Measure the width in displayed characters of string "text"
2378 static int textWidth(const char *text, int tabDist, char nullSubsChar)
2380 int width = 0, maxWidth = 0;
2381 const char *c;
2383 for (c=text; *c!='\0'; c++) {
2384 if (*c == '\n') {
2385 if (width > maxWidth)
2386 maxWidth = width;
2387 width = 0;
2388 } else
2389 width += BufCharWidth(*c, width, tabDist, nullSubsChar);
2391 if (width > maxWidth)
2392 return width;
2393 return maxWidth;
2397 ** Find the first and last character position in a line withing a rectangular
2398 ** selection (for copying). Includes tabs which cross rectStart, but not
2399 ** control characters which do so. Leaves off tabs which cross rectEnd.
2401 ** Technically, the calling routine should convert tab characters which
2402 ** cross the right boundary of the selection to spaces which line up with
2403 ** the edge of the selection. Unfortunately, the additional memory
2404 ** management required in the parent routine to allow for the changes
2405 ** in string size is not worth all the extra work just for a couple of
2406 ** shifted characters, so if a tab protrudes, just lop it off and hope
2407 ** that there are other characters in the selection to establish the right
2408 ** margin for subsequent columnar pastes of this data.
2410 static void findRectSelBoundariesForCopy(textBuffer *buf, int lineStartPos,
2411 int rectStart, int rectEnd, int *selStart, int *selEnd)
2413 int pos, width, indent = 0;
2414 char c;
2416 /* find the start of the selection */
2417 for (pos=lineStartPos; pos<buf->length; pos++) {
2418 c = BufGetCharacter(buf, pos);
2419 if (c == '\n')
2420 break;
2421 width = BufCharWidth(c, indent, buf->tabDist, buf->nullSubsChar);
2422 if (indent + width > rectStart) {
2423 if (indent != rectStart && c != '\t') {
2424 pos++;
2425 indent += width;
2427 break;
2429 indent += width;
2431 *selStart = pos;
2433 /* find the end */
2434 for (; pos<buf->length; pos++) {
2435 c = BufGetCharacter(buf, pos);
2436 if (c == '\n')
2437 break;
2438 width = BufCharWidth(c, indent, buf->tabDist, buf->nullSubsChar);
2439 indent += width;
2440 if (indent > rectEnd) {
2441 if (indent-width != rectEnd && c != '\t')
2442 pos++;
2443 break;
2446 *selEnd = pos;
2450 ** Adjust the space and tab characters from string "text" so that non-white
2451 ** characters remain stationary when the text is shifted from starting at
2452 ** "origIndent" to starting at "newIndent". Returns an allocated string
2453 ** which must be freed by the caller with XtFree.
2455 static char *realignTabs(const char *text, int origIndent, int newIndent,
2456 int tabDist, int useTabs, char nullSubsChar, int *newLength)
2458 char *expStr, *outStr;
2459 int len;
2461 /* If the tabs settings are the same, retain original tabs */
2462 if (origIndent % tabDist == newIndent %tabDist) {
2463 len = strlen(text);
2464 outStr = XtMalloc(len + 1);
2465 strcpy(outStr, text);
2466 *newLength = len;
2467 return outStr;
2470 /* If the tab settings are not the same, brutally convert tabs to
2471 spaces, then back to tabs in the new position */
2472 expStr = expandTabs(text, origIndent, tabDist, nullSubsChar, &len);
2473 if (!useTabs) {
2474 *newLength = len;
2475 return expStr;
2477 outStr = unexpandTabs(expStr, newIndent, tabDist, nullSubsChar, newLength);
2478 XtFree(expStr);
2479 return outStr;
2483 ** Expand tabs to spaces for a block of text. The additional parameter
2484 ** "startIndent" if nonzero, indicates that the text is a rectangular selection
2485 ** beginning at column "startIndent"
2487 static char *expandTabs(const char *text, int startIndent, int tabDist,
2488 char nullSubsChar, int *newLen)
2490 char *outStr, *outPtr;
2491 const char *c;
2492 int indent, len, outLen = 0;
2494 /* rehearse the expansion to figure out length for output string */
2495 indent = startIndent;
2496 for (c=text; *c!='\0'; c++) {
2497 if (*c == '\t') {
2498 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
2499 outLen += len;
2500 indent += len;
2501 } else if (*c == '\n') {
2502 indent = startIndent;
2503 outLen++;
2504 } else {
2505 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
2506 outLen++;
2510 /* do the expansion */
2511 outStr = XtMalloc(outLen+1);
2512 outPtr = outStr;
2513 indent = startIndent;
2514 for (c=text; *c!= '\0'; c++) {
2515 if (*c == '\t') {
2516 len = BufExpandCharacter(*c, indent, outPtr, tabDist, nullSubsChar);
2517 outPtr += len;
2518 indent += len;
2519 } else if (*c == '\n') {
2520 indent = startIndent;
2521 *outPtr++ = *c;
2522 } else {
2523 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
2524 *outPtr++ = *c;
2527 outStr[outLen] = '\0';
2528 *newLen = outLen;
2529 return outStr;
2533 ** Convert sequences of spaces into tabs. The threshold for conversion is
2534 ** when 3 or more spaces can be converted into a single tab, this avoids
2535 ** converting double spaces after a period withing a block of text.
2537 static char *unexpandTabs(const char *text, int startIndent, int tabDist,
2538 char nullSubsChar, int *newLen)
2540 char *outStr, *outPtr, expandedChar[MAX_EXP_CHAR_LEN];
2541 const char *c;
2542 int indent, len;
2544 outStr = XtMalloc(strlen(text)+1);
2545 outPtr = outStr;
2546 indent = startIndent;
2547 for (c=text; *c!='\0';) {
2548 if (*c == ' ') {
2549 len = BufExpandCharacter('\t', indent, expandedChar, tabDist,
2550 nullSubsChar);
2551 if (len >= 3 && !strncmp(c, expandedChar, len)) {
2552 c += len;
2553 *outPtr++ = '\t';
2554 indent += len;
2555 } else {
2556 *outPtr++ = *c++;
2557 indent++;
2559 } else if (*c == '\n') {
2560 indent = startIndent;
2561 *outPtr++ = *c++;
2562 } else {
2563 *outPtr++ = *c++;
2564 indent++;
2567 *outPtr = '\0';
2568 *newLen = outPtr - outStr;
2569 return outStr;
2572 static int max(int i1, int i2)
2574 return i1 >= i2 ? i1 : i2;
2577 static int min(int i1, int i2)
2579 return i1 <= i2 ? i1 : i2;