Fix for SF bug #1122813: Tab-related crash on posting unload calltips file
[nedit.git] / source / textBuf.c
blob403380dc28620df8b225f085e1865a74dc65e958
1 static const char CVSID[] = "$Id: textBuf.c,v 1.32 2004/07/21 11:32:05 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, 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);
149 buf->gapStart = 0;
150 buf->gapEnd = PREFERRED_GAP_SIZE;
151 buf->tabDist = 8;
152 buf->useTabs = True;
153 buf->primary.selected = False;
154 buf->primary.zeroWidth = False;
155 buf->primary.rectangular = False;
156 buf->primary.start = buf->primary.end = 0;
157 buf->secondary.selected = False;
158 buf->secondary.zeroWidth = False;
159 buf->secondary.start = buf->secondary.end = 0;
160 buf->secondary.rectangular = False;
161 buf->highlight.selected = False;
162 buf->highlight.zeroWidth = False;
163 buf->highlight.start = buf->highlight.end = 0;
164 buf->highlight.rectangular = False;
165 buf->modifyProcs = NULL;
166 buf->cbArgs = NULL;
167 buf->nModifyProcs = 0;
168 buf->preDeleteProcs = NULL;
169 buf->preDeleteCbArgs = NULL;
170 buf->nPreDeleteProcs = 0;
171 buf->nullSubsChar = '\0';
172 #ifdef PURIFY
173 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
174 #endif
175 buf->rangesetTable = NULL;
176 return buf;
180 ** Free a text buffer
182 void BufFree(textBuffer *buf)
184 XtFree(buf->buf);
185 if (buf->nModifyProcs != 0) {
186 XtFree((char *)buf->modifyProcs);
187 XtFree((char *)buf->cbArgs);
189 if (buf->rangesetTable)
190 RangesetTableFree(buf->rangesetTable);
191 if (buf->nPreDeleteProcs != 0) {
192 XtFree((char *)buf->preDeleteProcs);
193 XtFree((char *)buf->preDeleteCbArgs);
195 XtFree((char *)buf);
199 ** Get the entire contents of a text buffer. Memory is allocated to contain
200 ** the returned string, which the caller must free.
202 char *BufGetAll(textBuffer *buf)
204 char *text;
206 text = XtMalloc(buf->length+1);
207 memcpy(text, buf->buf, buf->gapStart);
208 memcpy(&text[buf->gapStart], &buf->buf[buf->gapEnd],
209 buf->length - buf->gapStart);
210 text[buf->length] = '\0';
211 return text;
215 ** Replace the entire contents of the text buffer
217 void BufSetAll(textBuffer *buf, const char *text)
219 int length, deletedLength;
220 char *deletedText;
221 length = strlen(text);
223 callPreDeleteCBs(buf, 0, buf->length);
225 /* Save information for redisplay, and get rid of the old buffer */
226 deletedText = BufGetAll(buf);
227 deletedLength = buf->length;
228 XtFree(buf->buf);
230 /* Start a new buffer with a gap of PREFERRED_GAP_SIZE in the center */
231 buf->buf = XtMalloc(length + PREFERRED_GAP_SIZE);
232 buf->length = length;
233 buf->gapStart = length/2;
234 buf->gapEnd = buf->gapStart + PREFERRED_GAP_SIZE;
235 memcpy(buf->buf, text, buf->gapStart);
236 memcpy(&buf->buf[buf->gapEnd], &text[buf->gapStart], length-buf->gapStart);
237 #ifdef PURIFY
238 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
239 #endif
241 /* Zero all of the existing selections */
242 updateSelections(buf, 0, deletedLength, 0);
244 /* Call the saved display routine(s) to update the screen */
245 callModifyCBs(buf, 0, deletedLength, length, 0, deletedText);
246 XtFree(deletedText);
250 ** Return a copy of the text between "start" and "end" character positions
251 ** from text buffer "buf". Positions start at 0, and the range does not
252 ** include the character pointed to by "end"
254 char *BufGetRange(textBuffer *buf, int start, int end)
256 char *text;
257 int length, part1Length;
259 /* Make sure start and end are ok, and allocate memory for returned string.
260 If start is bad, return "", if end is bad, adjust it. */
261 if (start < 0 || start > buf->length) {
262 text = XtMalloc(1);
263 text[0] = '\0';
264 return text;
266 if (end < start) {
267 int temp = start;
268 start = end;
269 end = temp;
271 if (end > buf->length)
272 end = buf->length;
273 length = end - start;
274 text = XtMalloc(length+1);
276 /* Copy the text from the buffer to the returned string */
277 if (end <= buf->gapStart) {
278 memcpy(text, &buf->buf[start], length);
279 } else if (start >= buf->gapStart) {
280 memcpy(text, &buf->buf[start+(buf->gapEnd-buf->gapStart)], length);
281 } else {
282 part1Length = buf->gapStart - start;
283 memcpy(text, &buf->buf[start], part1Length);
284 memcpy(&text[part1Length], &buf->buf[buf->gapEnd], length-part1Length);
286 text[length] = '\0';
287 return text;
291 ** Return the character at buffer position "pos". Positions start at 0.
293 char BufGetCharacter(textBuffer *buf, int pos)
295 if (pos < 0 || pos >= buf->length)
296 return '\0';
297 if (pos < buf->gapStart)
298 return buf->buf[pos];
299 else
300 return buf->buf[pos + buf->gapEnd-buf->gapStart];
304 ** Insert null-terminated string "text" at position "pos" in "buf"
306 void BufInsert(textBuffer *buf, int pos, const char *text)
308 int nInserted;
310 /* if pos is not contiguous to existing text, make it */
311 if (pos > buf->length) pos = buf->length;
312 if (pos < 0 ) pos = 0;
314 /* Even if nothing is deleted, we must call these callbacks */
315 callPreDeleteCBs(buf, pos, 0);
317 /* insert and redisplay */
318 nInserted = insert(buf, pos, text);
319 buf->cursorPosHint = pos + nInserted;
320 callModifyCBs(buf, pos, 0, nInserted, 0, NULL);
324 ** Delete the characters between "start" and "end", and insert the
325 ** null-terminated string "text" in their place in in "buf"
327 void BufReplace(textBuffer *buf, int start, int end, const char *text)
329 char *deletedText;
330 int nInserted = strlen(text);
332 callPreDeleteCBs(buf, start, end-start);
333 deletedText = BufGetRange(buf, start, end);
334 delete(buf, start, end);
335 insert(buf, start, text);
336 buf->cursorPosHint = start + nInserted;
337 callModifyCBs(buf, start, end-start, nInserted, 0, deletedText);
338 XtFree(deletedText);
341 void BufRemove(textBuffer *buf, int start, int end)
343 char *deletedText;
345 /* Make sure the arguments make sense */
346 if (start > end) {
347 int temp = start;
348 start = end;
349 end = temp;
351 if (start > buf->length) start = buf->length;
352 if (start < 0) start = 0;
353 if (end > buf->length) end = buf->length;
354 if (end < 0) end = 0;
356 callPreDeleteCBs(buf, start, end-start);
357 /* Remove and redisplay */
358 deletedText = BufGetRange(buf, start, end);
359 delete(buf, start, end);
360 buf->cursorPosHint = start;
361 callModifyCBs(buf, start, end-start, 0, 0, deletedText);
362 XtFree(deletedText);
365 void BufCopyFromBuf(textBuffer *fromBuf, textBuffer *toBuf, int fromStart,
366 int fromEnd, int toPos)
368 int length = fromEnd - fromStart;
369 int part1Length;
371 /* Prepare the buffer to receive the new text. If the new text fits in
372 the current buffer, just move the gap (if necessary) to where
373 the text should be inserted. If the new text is too large, reallocate
374 the buffer with a gap large enough to accomodate the new text and a
375 gap of PREFERRED_GAP_SIZE */
376 if (length > toBuf->gapEnd - toBuf->gapStart)
377 reallocateBuf(toBuf, toPos, length + PREFERRED_GAP_SIZE);
378 else if (toPos != toBuf->gapStart)
379 moveGap(toBuf, toPos);
381 /* Insert the new text (toPos now corresponds to the start of the gap) */
382 if (fromEnd <= fromBuf->gapStart) {
383 memcpy(&toBuf->buf[toPos], &fromBuf->buf[fromStart], length);
384 } else if (fromStart >= fromBuf->gapStart) {
385 memcpy(&toBuf->buf[toPos],
386 &fromBuf->buf[fromStart+(fromBuf->gapEnd-fromBuf->gapStart)],
387 length);
388 } else {
389 part1Length = fromBuf->gapStart - fromStart;
390 memcpy(&toBuf->buf[toPos], &fromBuf->buf[fromStart], part1Length);
391 memcpy(&toBuf->buf[toPos+part1Length], &fromBuf->buf[fromBuf->gapEnd],
392 length-part1Length);
394 toBuf->gapStart += length;
395 toBuf->length += length;
396 updateSelections(toBuf, toPos, 0, length);
400 ** Insert "text" columnwise into buffer starting at displayed character
401 ** position "column" on the line beginning at "startPos". Opens a rectangular
402 ** space the width and height of "text", by moving all text to the right of
403 ** "column" right. If charsInserted and charsDeleted are not NULL, the
404 ** number of characters inserted and deleted in the operation (beginning
405 ** at startPos) are returned in these arguments
407 void BufInsertCol(textBuffer *buf, int column, int startPos, const char *text,
408 int *charsInserted, int *charsDeleted)
410 int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
411 char *deletedText;
413 nLines = countLines(text);
414 lineStartPos = BufStartOfLine(buf, startPos);
415 nDeleted = BufEndOfLine(buf, BufCountForwardNLines(buf, startPos, nLines)) -
416 lineStartPos;
417 callPreDeleteCBs(buf, lineStartPos, nDeleted);
418 deletedText = BufGetRange(buf, lineStartPos, lineStartPos + nDeleted);
419 insertCol(buf, column, lineStartPos, text, &insertDeleted, &nInserted,
420 &buf->cursorPosHint);
421 if (nDeleted != insertDeleted)
422 fprintf(stderr, "NEdit internal consistency check ins1 failed");
423 callModifyCBs(buf, lineStartPos, nDeleted, nInserted, 0, deletedText);
424 XtFree(deletedText);
425 if (charsInserted != NULL)
426 *charsInserted = nInserted;
427 if (charsDeleted != NULL)
428 *charsDeleted = nDeleted;
432 ** Overlay "text" between displayed character positions "rectStart" and
433 ** "rectEnd" on the line beginning at "startPos". If charsInserted and
434 ** charsDeleted are not NULL, the number of characters inserted and deleted
435 ** in the operation (beginning at startPos) are returned in these arguments.
436 ** If rectEnd equals -1, the width of the inserted text is measured first.
438 void BufOverlayRect(textBuffer *buf, int startPos, int rectStart,
439 int rectEnd, const char *text, int *charsInserted, int *charsDeleted)
441 int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
442 char *deletedText;
444 nLines = countLines(text);
445 lineStartPos = BufStartOfLine(buf, startPos);
446 if(rectEnd == -1)
447 rectEnd = rectStart + textWidth(text, buf->tabDist, buf->nullSubsChar);
448 lineStartPos = BufStartOfLine(buf, startPos);
449 nDeleted = BufEndOfLine(buf, BufCountForwardNLines(buf, startPos, nLines)) -
450 lineStartPos;
451 callPreDeleteCBs(buf, lineStartPos, nDeleted);
452 deletedText = BufGetRange(buf, lineStartPos, lineStartPos + nDeleted);
453 overlayRect(buf, lineStartPos, rectStart, rectEnd, text, &insertDeleted,
454 &nInserted, &buf->cursorPosHint);
455 if (nDeleted != insertDeleted)
456 fprintf(stderr, "NEdit internal consistency check ovly1 failed");
457 callModifyCBs(buf, lineStartPos, nDeleted, nInserted, 0, deletedText);
458 XtFree(deletedText);
459 if (charsInserted != NULL)
460 *charsInserted = nInserted;
461 if (charsDeleted != NULL)
462 *charsDeleted = nDeleted;
466 ** Replace a rectangular area in buf, given by "start", "end", "rectStart",
467 ** and "rectEnd", with "text". If "text" is vertically longer than the
468 ** rectangle, add extra lines to make room for it.
470 void BufReplaceRect(textBuffer *buf, int start, int end, int rectStart,
471 int rectEnd, const char *text)
473 char *deletedText;
474 char *insText=NULL;
475 int i, nInsertedLines, nDeletedLines, insLen, hint;
476 int insertDeleted, insertInserted, deleteInserted;
477 int linesPadded = 0;
479 /* Make sure start and end refer to complete lines, since the
480 columnar delete and insert operations will replace whole lines */
481 start = BufStartOfLine(buf, start);
482 end = BufEndOfLine(buf, end);
484 callPreDeleteCBs(buf, start, end-start);
486 /* If more lines will be deleted than inserted, pad the inserted text
487 with newlines to make it as long as the number of deleted lines. This
488 will indent all of the text to the right of the rectangle to the same
489 column. If more lines will be inserted than deleted, insert extra
490 lines in the buffer at the end of the rectangle to make room for the
491 additional lines in "text" */
492 nInsertedLines = countLines(text);
493 nDeletedLines = BufCountLines(buf, start, end);
494 if (nInsertedLines < nDeletedLines) {
495 char *insPtr;
497 insLen = strlen(text);
498 insText = XtMalloc(insLen + nDeletedLines - nInsertedLines + 1);
499 strcpy(insText, text);
500 insPtr = insText + insLen;
501 for (i=0; i<nDeletedLines-nInsertedLines; i++)
502 *insPtr++ = '\n';
503 *insPtr = '\0';
504 } else if (nDeletedLines < nInsertedLines) {
505 linesPadded = nInsertedLines-nDeletedLines;
506 for (i=0; i<linesPadded; i++)
507 insert(buf, end, "\n");
508 } else /* nDeletedLines == nInsertedLines */ {
511 /* Save a copy of the text which will be modified for the modify CBs */
512 deletedText = BufGetRange(buf, start, end);
514 /* Delete then insert */
515 deleteRect(buf, start, end, rectStart, rectEnd, &deleteInserted, &hint);
516 if (insText) {
517 insertCol(buf, rectStart, start, insText, &insertDeleted, &insertInserted,
518 &buf->cursorPosHint);
519 XtFree(insText);
521 else
522 insertCol(buf, rectStart, start, text, &insertDeleted, &insertInserted,
523 &buf->cursorPosHint);
525 /* Figure out how many chars were inserted and call modify callbacks */
526 if (insertDeleted != deleteInserted + linesPadded)
527 fprintf(stderr, "NEdit: internal consistency check repl1 failed\n");
528 callModifyCBs(buf, start, end-start, insertInserted, 0, deletedText);
529 XtFree(deletedText);
533 ** Remove a rectangular swath of characters between character positions start
534 ** and end and horizontal displayed-character offsets rectStart and rectEnd.
536 void BufRemoveRect(textBuffer *buf, int start, int end, int rectStart,
537 int rectEnd)
539 char *deletedText;
540 int nInserted;
542 start = BufStartOfLine(buf, start);
543 end = BufEndOfLine(buf, end);
544 callPreDeleteCBs(buf, start, end-start);
545 deletedText = BufGetRange(buf, start, end);
546 deleteRect(buf, start, end, rectStart, rectEnd, &nInserted,
547 &buf->cursorPosHint);
548 callModifyCBs(buf, start, end-start, nInserted, 0, deletedText);
549 XtFree(deletedText);
553 ** Clear a rectangular "hole" out of the buffer between character positions
554 ** start and end and horizontal displayed-character offsets rectStart and
555 ** rectEnd.
557 void BufClearRect(textBuffer *buf, int start, int end, int rectStart,
558 int rectEnd)
560 int i, nLines;
561 char *newlineString;
563 nLines = BufCountLines(buf, start, end);
564 newlineString = XtMalloc(nLines+1);
565 for (i=0; i<nLines; i++)
566 newlineString[i] = '\n';
567 newlineString[i] = '\0';
568 BufOverlayRect(buf, start, rectStart, rectEnd, newlineString,
569 NULL, NULL);
570 XtFree(newlineString);
573 char *BufGetTextInRect(textBuffer *buf, int start, int end,
574 int rectStart, int rectEnd)
576 int lineStart, selLeft, selRight, len;
577 char *textOut, *textIn, *outPtr, *retabbedStr;
579 start = BufStartOfLine(buf, start);
580 end = BufEndOfLine(buf, end);
581 textOut = XtMalloc((end - start) + 1);
582 lineStart = start;
583 outPtr = textOut;
584 while (lineStart <= end) {
585 findRectSelBoundariesForCopy(buf, lineStart, rectStart, rectEnd,
586 &selLeft, &selRight);
587 textIn = BufGetRange(buf, selLeft, selRight);
588 len = selRight - selLeft;
589 memcpy(outPtr, textIn, len);
590 XtFree(textIn);
591 outPtr += len;
592 lineStart = BufEndOfLine(buf, selRight) + 1;
593 *outPtr++ = '\n';
595 if (outPtr != textOut)
596 outPtr--; /* don't leave trailing newline */
597 *outPtr = '\0';
599 /* If necessary, realign the tabs in the selection as if the text were
600 positioned at the left margin */
601 retabbedStr = realignTabs(textOut, rectStart, 0, buf->tabDist,
602 buf->useTabs, buf->nullSubsChar, &len);
603 XtFree(textOut);
604 return retabbedStr;
608 ** Get the hardware tab distance used by all displays for this buffer,
609 ** and used in computing offsets for rectangular selection operations.
611 int BufGetTabDistance(textBuffer *buf)
613 return buf->tabDist;
617 ** Set the hardware tab distance used by all displays for this buffer,
618 ** and used in computing offsets for rectangular selection operations.
620 void BufSetTabDistance(textBuffer *buf, int tabDist)
622 char *deletedText;
624 /* First call the pre-delete callbacks with the previous tab setting
625 still active. */
626 callPreDeleteCBs(buf, 0, buf->length);
628 /* Change the tab setting */
629 buf->tabDist = tabDist;
631 /* Force any display routines to redisplay everything (unfortunately,
632 this means copying the whole buffer contents to provide "deletedText" */
633 deletedText = BufGetAll(buf);
634 callModifyCBs(buf, 0, buf->length, buf->length, 0, deletedText);
635 XtFree(deletedText);
638 void BufCheckDisplay(textBuffer *buf, int start, int end)
640 /* just to make sure colors in the selected region are up to date */
641 callModifyCBs(buf, start, 0, 0, end-start, NULL);
644 void BufSelect(textBuffer *buf, int start, int end)
646 selection oldSelection = buf->primary;
648 setSelection(&buf->primary, start, end);
649 redisplaySelection(buf, &oldSelection, &buf->primary);
652 void BufUnselect(textBuffer *buf)
654 selection oldSelection = buf->primary;
656 buf->primary.selected = False;
657 buf->primary.zeroWidth = False;
658 redisplaySelection(buf, &oldSelection, &buf->primary);
661 void BufRectSelect(textBuffer *buf, int start, int end, int rectStart,
662 int rectEnd)
664 selection oldSelection = buf->primary;
666 setRectSelect(&buf->primary, start, end, rectStart, rectEnd);
667 redisplaySelection(buf, &oldSelection, &buf->primary);
670 int BufGetSelectionPos(textBuffer *buf, int *start, int *end,
671 int *isRect, int *rectStart, int *rectEnd)
673 return getSelectionPos(&buf->primary, start, end, isRect, rectStart,
674 rectEnd);
677 /* Same as above, but also returns TRUE for empty selections */
678 int BufGetEmptySelectionPos(textBuffer *buf, int *start, int *end,
679 int *isRect, int *rectStart, int *rectEnd)
681 return getSelectionPos(&buf->primary, start, end, isRect, rectStart,
682 rectEnd) || buf->primary.zeroWidth;
685 char *BufGetSelectionText(textBuffer *buf)
687 return getSelectionText(buf, &buf->primary);
690 void BufRemoveSelected(textBuffer *buf)
692 removeSelected(buf, &buf->primary);
695 void BufReplaceSelected(textBuffer *buf, const char *text)
697 replaceSelected(buf, &buf->primary, text);
700 void BufSecondarySelect(textBuffer *buf, int start, int end)
702 selection oldSelection = buf->secondary;
704 setSelection(&buf->secondary, start, end);
705 redisplaySelection(buf, &oldSelection, &buf->secondary);
708 void BufSecondaryUnselect(textBuffer *buf)
710 selection oldSelection = buf->secondary;
712 buf->secondary.selected = False;
713 buf->secondary.zeroWidth = False;
714 redisplaySelection(buf, &oldSelection, &buf->secondary);
717 void BufSecRectSelect(textBuffer *buf, int start, int end,
718 int rectStart, int rectEnd)
720 selection oldSelection = buf->secondary;
722 setRectSelect(&buf->secondary, start, end, rectStart, rectEnd);
723 redisplaySelection(buf, &oldSelection, &buf->secondary);
726 int BufGetSecSelectPos(textBuffer *buf, int *start, int *end,
727 int *isRect, int *rectStart, int *rectEnd)
729 return getSelectionPos(&buf->secondary, start, end, isRect, rectStart,
730 rectEnd);
733 char *BufGetSecSelectText(textBuffer *buf)
735 return getSelectionText(buf, &buf->secondary);
738 void BufRemoveSecSelect(textBuffer *buf)
740 removeSelected(buf, &buf->secondary);
743 void BufReplaceSecSelect(textBuffer *buf, const char *text)
745 replaceSelected(buf, &buf->secondary, text);
748 void BufHighlight(textBuffer *buf, int start, int end)
750 selection oldSelection = buf->highlight;
752 setSelection(&buf->highlight, start, end);
753 redisplaySelection(buf, &oldSelection, &buf->highlight);
756 void BufUnhighlight(textBuffer *buf)
758 selection oldSelection = buf->highlight;
760 buf->highlight.selected = False;
761 buf->highlight.zeroWidth = False;
762 redisplaySelection(buf, &oldSelection, &buf->highlight);
765 void BufRectHighlight(textBuffer *buf, int start, int end,
766 int rectStart, int rectEnd)
768 selection oldSelection = buf->highlight;
770 setRectSelect(&buf->highlight, start, end, rectStart, rectEnd);
771 redisplaySelection(buf, &oldSelection, &buf->highlight);
774 int BufGetHighlightPos(textBuffer *buf, int *start, int *end,
775 int *isRect, int *rectStart, int *rectEnd)
777 return getSelectionPos(&buf->highlight, start, end, isRect, rectStart,
778 rectEnd);
781 char *BufGetHighlightText(textBuffer *buf)
783 return getSelectionText(buf, &buf->highlight);
787 ** Add a callback routine to be called when the buffer is modified
789 void BufAddModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
790 void *cbArg)
792 bufModifyCallbackProc *newModifyProcs;
793 void **newCBArgs;
794 int i;
796 newModifyProcs = (bufModifyCallbackProc *)
797 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs+1));
798 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs+1));
799 for (i=0; i<buf->nModifyProcs; i++) {
800 newModifyProcs[i] = buf->modifyProcs[i];
801 newCBArgs[i] = buf->cbArgs[i];
803 if (buf->nModifyProcs != 0) {
804 XtFree((char *)buf->modifyProcs);
805 XtFree((char *)buf->cbArgs);
807 newModifyProcs[buf->nModifyProcs] = bufModifiedCB;
808 newCBArgs[buf->nModifyProcs] = cbArg;
809 buf->nModifyProcs++;
810 buf->modifyProcs = newModifyProcs;
811 buf->cbArgs = newCBArgs;
815 ** Similar to the above, but makes sure that the callback is called before
816 ** normal priority callbacks.
818 void BufAddHighPriorityModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
819 void *cbArg)
821 bufModifyCallbackProc *newModifyProcs;
822 void **newCBArgs;
823 int i;
825 newModifyProcs = (bufModifyCallbackProc *)
826 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs+1));
827 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs+1));
828 for (i=0; i<buf->nModifyProcs; i++) {
829 newModifyProcs[i+1] = buf->modifyProcs[i];
830 newCBArgs[i+1] = buf->cbArgs[i];
832 if (buf->nModifyProcs != 0) {
833 XtFree((char *)buf->modifyProcs);
834 XtFree((char *)buf->cbArgs);
836 newModifyProcs[0] = bufModifiedCB;
837 newCBArgs[0] = cbArg;
838 buf->nModifyProcs++;
839 buf->modifyProcs = newModifyProcs;
840 buf->cbArgs = newCBArgs;
843 void BufRemoveModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
844 void *cbArg)
846 int i, toRemove = -1;
847 bufModifyCallbackProc *newModifyProcs;
848 void **newCBArgs;
850 /* find the matching callback to remove */
851 for (i=0; i<buf->nModifyProcs; i++) {
852 if (buf->modifyProcs[i] == bufModifiedCB && buf->cbArgs[i] == cbArg) {
853 toRemove = i;
854 break;
857 if (toRemove == -1) {
858 fprintf(stderr, "NEdit Internal Error: Can't find modify CB to remove\n");
859 return;
862 /* Allocate new lists for remaining callback procs and args (if
863 any are left) */
864 buf->nModifyProcs--;
865 if (buf->nModifyProcs == 0) {
866 buf->nModifyProcs = 0;
867 XtFree((char *)buf->modifyProcs);
868 buf->modifyProcs = NULL;
869 XtFree((char *)buf->cbArgs);
870 buf->cbArgs = NULL;
871 return;
873 newModifyProcs = (bufModifyCallbackProc *)
874 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs));
875 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs));
877 /* copy out the remaining members and free the old lists */
878 for (i=0; i<toRemove; i++) {
879 newModifyProcs[i] = buf->modifyProcs[i];
880 newCBArgs[i] = buf->cbArgs[i];
882 for (; i<buf->nModifyProcs; i++) {
883 newModifyProcs[i] = buf->modifyProcs[i+1];
884 newCBArgs[i] = buf->cbArgs[i+1];
886 XtFree((char *)buf->modifyProcs);
887 XtFree((char *)buf->cbArgs);
888 buf->modifyProcs = newModifyProcs;
889 buf->cbArgs = newCBArgs;
893 ** Add a callback routine to be called before text is deleted from the buffer.
895 void BufAddPreDeleteCB(textBuffer *buf, bufPreDeleteCallbackProc bufPreDeleteCB,
896 void *cbArg)
898 bufPreDeleteCallbackProc *newPreDeleteProcs;
899 void **newCBArgs;
900 int i;
902 newPreDeleteProcs = (bufPreDeleteCallbackProc *)
903 XtMalloc(sizeof(bufPreDeleteCallbackProc *) * (buf->nPreDeleteProcs+1));
904 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nPreDeleteProcs+1));
905 for (i=0; i<buf->nPreDeleteProcs; i++) {
906 newPreDeleteProcs[i] = buf->preDeleteProcs[i];
907 newCBArgs[i] = buf->preDeleteCbArgs[i];
909 if (buf->nPreDeleteProcs != 0) {
910 XtFree((char *)buf->preDeleteProcs);
911 XtFree((char *)buf->preDeleteCbArgs);
913 newPreDeleteProcs[buf->nPreDeleteProcs] = bufPreDeleteCB;
914 newCBArgs[buf->nPreDeleteProcs] = cbArg;
915 buf->nPreDeleteProcs++;
916 buf->preDeleteProcs = newPreDeleteProcs;
917 buf->preDeleteCbArgs = newCBArgs;
920 void BufRemovePreDeleteCB(textBuffer *buf, bufPreDeleteCallbackProc bufPreDeleteCB,
921 void *cbArg)
923 int i, toRemove = -1;
924 bufPreDeleteCallbackProc *newPreDeleteProcs;
925 void **newCBArgs;
927 /* find the matching callback to remove */
928 for (i=0; i<buf->nPreDeleteProcs; i++) {
929 if (buf->preDeleteProcs[i] == bufPreDeleteCB &&
930 buf->preDeleteCbArgs[i] == cbArg) {
931 toRemove = i;
932 break;
935 if (toRemove == -1) {
936 fprintf(stderr, "NEdit Internal Error: Can't find pre-delete CB to remove\n");
937 return;
940 /* Allocate new lists for remaining callback procs and args (if
941 any are left) */
942 buf->nPreDeleteProcs--;
943 if (buf->nPreDeleteProcs == 0) {
944 buf->nPreDeleteProcs = 0;
945 XtFree((char *)buf->preDeleteProcs);
946 buf->preDeleteProcs = NULL;
947 XtFree((char *)buf->preDeleteCbArgs);
948 buf->preDeleteCbArgs = NULL;
949 return;
951 newPreDeleteProcs = (bufPreDeleteCallbackProc *)
952 XtMalloc(sizeof(bufPreDeleteCallbackProc *) * (buf->nPreDeleteProcs));
953 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nPreDeleteProcs));
955 /* copy out the remaining members and free the old lists */
956 for (i=0; i<toRemove; i++) {
957 newPreDeleteProcs[i] = buf->preDeleteProcs[i];
958 newCBArgs[i] = buf->preDeleteCbArgs[i];
960 for (; i<buf->nPreDeleteProcs; i++) {
961 newPreDeleteProcs[i] = buf->preDeleteProcs[i+1];
962 newCBArgs[i] = buf->preDeleteCbArgs[i+1];
964 XtFree((char *)buf->preDeleteProcs);
965 XtFree((char *)buf->preDeleteCbArgs);
966 buf->preDeleteProcs = newPreDeleteProcs;
967 buf->preDeleteCbArgs = newCBArgs;
971 ** Return the text from the entire line containing position "pos"
973 char *BufGetLineText(textBuffer *buf, int pos)
975 return BufGetRange(buf, BufStartOfLine(buf, pos), BufEndOfLine(buf, pos));
979 ** Find the position of the start of the line containing position "pos"
981 int BufStartOfLine(textBuffer *buf, int pos)
983 int startPos;
985 if (!searchBackward(buf, pos, '\n', &startPos))
986 return 0;
987 return startPos + 1;
992 ** Find the position of the end of the line containing position "pos"
993 ** (which is either a pointer to the newline character ending the line,
994 ** or a pointer to one character beyond the end of the buffer)
996 int BufEndOfLine(textBuffer *buf, int pos)
998 int endPos;
1000 if (!searchForward(buf, pos, '\n', &endPos))
1001 endPos = buf->length;
1002 return endPos;
1006 ** Get a character from the text buffer expanded into it's screen
1007 ** representation (which may be several characters for a tab or a
1008 ** control code). Returns the number of characters written to "outStr".
1009 ** "indent" is the number of characters from the start of the line
1010 ** for figuring tabs. Output string is guranteed to be shorter or
1011 ** equal in length to MAX_EXP_CHAR_LEN
1013 int BufGetExpandedChar(textBuffer *buf, int pos, int indent, char *outStr)
1015 return BufExpandCharacter(BufGetCharacter(buf, pos), indent, outStr,
1016 buf->tabDist, buf->nullSubsChar);
1020 ** Expand a single character from the text buffer into it's screen
1021 ** representation (which may be several characters for a tab or a
1022 ** control code). Returns the number of characters added to "outStr".
1023 ** "indent" is the number of characters from the start of the line
1024 ** for figuring tabs. Output string is guranteed to be shorter or
1025 ** equal in length to MAX_EXP_CHAR_LEN
1027 int BufExpandCharacter(char c, int indent, char *outStr, int tabDist,
1028 char nullSubsChar)
1030 int i, nSpaces;
1032 /* Convert tabs to spaces */
1033 if (c == '\t') {
1034 nSpaces = tabDist - (indent % tabDist);
1035 for (i=0; i<nSpaces; i++)
1036 outStr[i] = ' ';
1037 return nSpaces;
1040 /* Convert ASCII (and EBCDIC in the __MVS__ (OS/390) case) control
1041 codes to readable character sequences */
1042 if (c == nullSubsChar) {
1043 sprintf(outStr, "<nul>");
1044 return 5;
1046 #ifdef __MVS__
1047 if (((unsigned char)c) <= 63) {
1048 sprintf(outStr, "<%s>", ControlCodeTable[(unsigned char)c]);
1049 return strlen(outStr);
1051 #else
1052 if (((unsigned char)c) <= 31) {
1053 sprintf(outStr, "<%s>", ControlCodeTable[(unsigned char)c]);
1054 return strlen(outStr);
1055 } else if (c == 127) {
1056 sprintf(outStr, "<del>");
1057 return 5;
1059 #endif
1061 /* Otherwise, just return the character */
1062 *outStr = c;
1063 return 1;
1067 ** Return the length in displayed characters of character "c" expanded
1068 ** for display (as discussed above in BufGetExpandedChar). If the
1069 ** buffer for which the character width is being measured is doing null
1070 ** substitution, nullSubsChar should be passed as that character (or nul
1071 ** to ignore).
1073 int BufCharWidth(char c, int indent, int tabDist, char nullSubsChar)
1075 /* Note, this code must parallel that in BufExpandCharacter */
1076 if (c == nullSubsChar)
1077 return 5;
1078 else if (c == '\t')
1079 return tabDist - (indent % tabDist);
1080 else if (((unsigned char)c) <= 31)
1081 return strlen(ControlCodeTable[(unsigned char)c]) + 2;
1082 else if (c == 127)
1083 return 5;
1084 return 1;
1088 ** Count the number of displayed characters between buffer position
1089 ** "lineStartPos" and "targetPos". (displayed characters are the characters
1090 ** shown on the screen to represent characters in the buffer, where tabs and
1091 ** control characters are expanded)
1093 int BufCountDispChars(textBuffer *buf, int lineStartPos, int targetPos)
1095 int pos, charCount = 0;
1096 char expandedChar[MAX_EXP_CHAR_LEN];
1098 pos = lineStartPos;
1099 while (pos < targetPos && pos < buf->length)
1100 charCount += BufGetExpandedChar(buf, pos++, charCount, expandedChar);
1101 return charCount;
1105 ** Count forward from buffer position "startPos" in displayed characters
1106 ** (displayed characters are the characters shown on the screen to represent
1107 ** characters in the buffer, where tabs and control characters are expanded)
1109 int BufCountForwardDispChars(textBuffer *buf, int lineStartPos, int nChars)
1111 int pos, charCount = 0;
1112 char c;
1114 pos = lineStartPos;
1115 while (charCount < nChars && pos < buf->length) {
1116 c = BufGetCharacter(buf, pos);
1117 if (c == '\n')
1118 return pos;
1119 charCount += BufCharWidth(c, charCount, buf->tabDist,buf->nullSubsChar);
1120 pos++;
1122 return pos;
1126 ** Count the number of newlines between startPos and endPos in buffer "buf".
1127 ** The character at position "endPos" is not counted.
1129 int BufCountLines(textBuffer *buf, int startPos, int endPos)
1131 int pos, gapLen = buf->gapEnd - buf->gapStart;
1132 int lineCount = 0;
1134 pos = startPos;
1135 while (pos < buf->gapStart) {
1136 if (pos == endPos)
1137 return lineCount;
1138 if (buf->buf[pos++] == '\n')
1139 lineCount++;
1141 while (pos < buf->length) {
1142 if (pos == endPos)
1143 return lineCount;
1144 if (buf->buf[pos++ + gapLen] == '\n')
1145 lineCount++;
1147 return lineCount;
1151 ** Find the first character of the line "nLines" forward from "startPos"
1152 ** in "buf" and return its position
1154 int BufCountForwardNLines(textBuffer *buf, int startPos, int nLines)
1156 int pos, gapLen = buf->gapEnd - buf->gapStart;
1157 int lineCount = 0;
1159 if (nLines == 0)
1160 return startPos;
1162 pos = startPos;
1163 while (pos < buf->gapStart) {
1164 if (buf->buf[pos++] == '\n') {
1165 lineCount++;
1166 if (lineCount == nLines)
1167 return pos;
1170 while (pos < buf->length) {
1171 if (buf->buf[pos++ + gapLen] == '\n') {
1172 lineCount++;
1173 if (lineCount >= nLines)
1174 return pos;
1177 return pos;
1181 ** Find the position of the first character of the line "nLines" backwards
1182 ** from "startPos" (not counting the character pointed to by "startpos" if
1183 ** that is a newline) in "buf". nLines == 0 means find the beginning of
1184 ** the line
1186 int BufCountBackwardNLines(textBuffer *buf, int startPos, int nLines)
1188 int pos, gapLen = buf->gapEnd - buf->gapStart;
1189 int lineCount = -1;
1191 pos = startPos - 1;
1192 if (pos <= 0)
1193 return 0;
1195 while (pos >= buf->gapStart) {
1196 if (buf->buf[pos + gapLen] == '\n') {
1197 if (++lineCount >= nLines)
1198 return pos + 1;
1200 pos--;
1202 while (pos >= 0) {
1203 if (buf->buf[pos] == '\n') {
1204 if (++lineCount >= nLines)
1205 return pos + 1;
1207 pos--;
1209 return 0;
1213 ** Search forwards in buffer "buf" for characters in "searchChars", starting
1214 ** with the character "startPos", and returning the result in "foundPos"
1215 ** returns True if found, False if not.
1217 int BufSearchForward(textBuffer *buf, int startPos, const char *searchChars,
1218 int *foundPos)
1220 int pos, gapLen = buf->gapEnd - buf->gapStart;
1221 const char *c;
1223 pos = startPos;
1224 while (pos < buf->gapStart) {
1225 for (c=searchChars; *c!='\0'; c++) {
1226 if (buf->buf[pos] == *c) {
1227 *foundPos = pos;
1228 return True;
1231 pos++;
1233 while (pos < buf->length) {
1234 for (c=searchChars; *c!='\0'; c++) {
1235 if (buf->buf[pos + gapLen] == *c) {
1236 *foundPos = pos;
1237 return True;
1240 pos++;
1242 *foundPos = buf->length;
1243 return False;
1247 ** Search backwards in buffer "buf" for characters in "searchChars", starting
1248 ** with the character BEFORE "startPos", returning the result in "foundPos"
1249 ** returns True if found, False if not.
1251 int BufSearchBackward(textBuffer *buf, int startPos, const char *searchChars,
1252 int *foundPos)
1254 int pos, gapLen = buf->gapEnd - buf->gapStart;
1255 const char *c;
1257 if (startPos == 0) {
1258 *foundPos = 0;
1259 return False;
1261 pos = startPos == 0 ? 0 : startPos - 1;
1262 while (pos >= buf->gapStart) {
1263 for (c=searchChars; *c!='\0'; c++) {
1264 if (buf->buf[pos + gapLen] == *c) {
1265 *foundPos = pos;
1266 return True;
1269 pos--;
1271 while (pos >= 0) {
1272 for (c=searchChars; *c!='\0'; c++) {
1273 if (buf->buf[pos] == *c) {
1274 *foundPos = pos;
1275 return True;
1278 pos--;
1280 *foundPos = 0;
1281 return False;
1285 ** A horrible design flaw in NEdit (from the very start, before we knew that
1286 ** NEdit would become so popular), is that it uses C NULL terminated strings
1287 ** to hold text. This means editing text containing NUL characters is not
1288 ** possible without special consideration. Here is the special consideration.
1289 ** The routines below maintain a special substitution-character which stands
1290 ** in for a null, and translates strings an buffers back and forth from/to
1291 ** the substituted form, figure out what to substitute, and figure out
1292 ** when we're in over our heads and no translation is possible.
1296 ** The primary routine for integrating new text into a text buffer with
1297 ** substitution of another character for ascii nuls. This substitutes null
1298 ** characters in the string in preparation for being copied or replaced
1299 ** into the buffer, and if neccessary, adjusts the buffer as well, in the
1300 ** event that the string contains the character it is currently using for
1301 ** substitution. Returns False, if substitution is no longer possible
1302 ** because all non-printable characters are already in use.
1304 int BufSubstituteNullChars(char *string, int length, textBuffer *buf)
1306 char histogram[256];
1308 /* Find out what characters the string contains */
1309 histogramCharacters(string, length, histogram, True);
1311 /* Does the string contain the null-substitute character? If so, re-
1312 histogram the buffer text to find a character which is ok in both the
1313 string and the buffer, and change the buffer's null-substitution
1314 character. If none can be found, give up and return False */
1315 if (histogram[(unsigned char)buf->nullSubsChar] != 0) {
1316 char *bufString, newSubsChar;
1317 bufString = BufGetAll(buf);
1318 histogramCharacters(bufString, buf->length, histogram, False);
1319 newSubsChar = chooseNullSubsChar(histogram);
1320 if (newSubsChar == '\0') {
1321 XtFree(bufString);
1322 return False;
1324 subsChars(bufString, buf->length, buf->nullSubsChar, newSubsChar);
1325 delete(buf, 0, buf->length);
1326 insert(buf, 0, bufString);
1327 XtFree(bufString);
1328 buf->nullSubsChar = newSubsChar;
1331 /* If the string contains null characters, substitute them with the
1332 buffer's null substitution character */
1333 if (histogram[0] != 0)
1334 subsChars(string, length, '\0', buf->nullSubsChar);
1335 return True;
1339 ** Convert strings obtained from buffers which contain null characters, which
1340 ** have been substituted for by a special substitution character, back to
1341 ** a null-containing string. There is no time penalty for calling this
1342 ** routine if no substitution has been done.
1344 void BufUnsubstituteNullChars(char *string, textBuffer *buf)
1346 register char *c, subsChar = buf->nullSubsChar;
1348 if (subsChar == '\0')
1349 return;
1350 for (c=string; *c != '\0'; c++)
1351 if (*c == subsChar)
1352 *c = '\0';
1356 ** Compares len Bytes contained in buf starting at Position pos with
1357 ** the contens of cmpText. Returns 0 if there are no differences,
1358 ** != 0 otherwise.
1361 int BufCmp(textBuffer * buf, int pos, int len, const char *cmpText)
1363 int posEnd;
1364 int part1Length;
1365 int result;
1367 posEnd = pos + len;
1368 if (posEnd > buf->length) {
1369 return (1);
1371 if (pos < 0) {
1372 return (-1);
1375 if (posEnd <= buf->gapStart) {
1376 return (strncmp(&(buf->buf[pos]), cmpText, len));
1377 } else if (pos >= buf->gapStart) {
1378 return (strncmp (&buf->buf[pos + (buf->gapEnd - buf->gapStart)],
1379 cmpText, len));
1380 } else {
1381 part1Length = buf->gapStart - pos;
1382 result = strncmp(&buf->buf[pos], cmpText, part1Length);
1383 if (result) {
1384 return (result);
1386 return (strncmp(&buf->buf[buf->gapEnd], &cmpText[part1Length],
1387 len - part1Length));
1392 ** Create a pseudo-histogram of the characters in a string (don't actually
1393 ** count, because we don't want overflow, just mark the character's presence
1394 ** with a 1). If init is true, initialize the histogram before acumulating.
1395 ** if not, add the new data to an existing histogram.
1397 static void histogramCharacters(const char *string, int length, char hist[256],
1398 int init)
1400 int i;
1401 const char *c;
1403 if (init)
1404 for (i=0; i<256; i++)
1405 hist[i] = 0;
1406 for (c=string; c < &string[length]; c++)
1407 hist[*((unsigned char *)c)] |= 1;
1411 ** Substitute fromChar with toChar in string.
1413 static void subsChars(char *string, int length, char fromChar, char toChar)
1415 char *c;
1417 for (c=string; c < &string[length]; c++)
1418 if (*c == fromChar) *c = toChar;
1422 ** Search through ascii control characters in histogram in order of least
1423 ** likelihood of use, find an unused character to use as a stand-in for a
1424 ** null. If the character set is full (no available characters outside of
1425 ** the printable set, return the null character.
1427 static char chooseNullSubsChar(char hist[256])
1429 #define N_REPLACEMENTS 25
1430 static char replacements[N_REPLACEMENTS] = {1,2,3,4,5,6,14,15,16,17,18,19,
1431 20,21,22,23,24,25,26,28,29,30,31,11,7};
1432 int i;
1433 for (i = 0; i < N_REPLACEMENTS; i++)
1434 if (hist[(unsigned char)replacements[i]] == 0)
1435 return replacements[i];
1436 return '\0';
1440 ** Internal (non-redisplaying) version of BufInsert. Returns the length of
1441 ** text inserted (this is just strlen(text), however this calculation can be
1442 ** expensive and the length will be required by any caller who will continue
1443 ** on to call redisplay). pos must be contiguous with the existing text in
1444 ** the buffer (i.e. not past the end).
1446 static int insert(textBuffer *buf, int pos, const char *text)
1448 int length = strlen(text);
1450 /* Prepare the buffer to receive the new text. If the new text fits in
1451 the current buffer, just move the gap (if necessary) to where
1452 the text should be inserted. If the new text is too large, reallocate
1453 the buffer with a gap large enough to accomodate the new text and a
1454 gap of PREFERRED_GAP_SIZE */
1455 if (length > buf->gapEnd - buf->gapStart)
1456 reallocateBuf(buf, pos, length + PREFERRED_GAP_SIZE);
1457 else if (pos != buf->gapStart)
1458 moveGap(buf, pos);
1460 /* Insert the new text (pos now corresponds to the start of the gap) */
1461 memcpy(&buf->buf[pos], text, length);
1462 buf->gapStart += length;
1463 buf->length += length;
1464 updateSelections(buf, pos, 0, length);
1466 return length;
1470 ** Internal (non-redisplaying) version of BufRemove. Removes the contents
1471 ** of the buffer between start and end (and moves the gap to the site of
1472 ** the delete).
1474 static void delete(textBuffer *buf, int start, int end)
1476 /* if the gap is not contiguous to the area to remove, move it there */
1477 if (start > buf->gapStart)
1478 moveGap(buf, start);
1479 else if (end < buf->gapStart)
1480 moveGap(buf, end);
1482 /* expand the gap to encompass the deleted characters */
1483 buf->gapEnd += end - buf->gapStart;
1484 buf->gapStart -= buf->gapStart - start;
1486 /* update the length */
1487 buf->length -= end - start;
1489 /* fix up any selections which might be affected by the change */
1490 updateSelections(buf, start, end-start, 0);
1494 ** Insert a column of text without calling the modify callbacks. Note that
1495 ** in some pathological cases, inserting can actually decrease the size of
1496 ** the buffer because of spaces being coalesced into tabs. "nDeleted" and
1497 ** "nInserted" return the number of characters deleted and inserted beginning
1498 ** at the start of the line containing "startPos". "endPos" returns buffer
1499 ** position of the lower left edge of the inserted column (as a hint for
1500 ** routines which need to set a cursor position).
1502 static void insertCol(textBuffer *buf, int column, int startPos,
1503 const char *insText, int *nDeleted, int *nInserted, int *endPos)
1505 int nLines, start, end, insWidth, lineStart, lineEnd;
1506 int expReplLen, expInsLen, len, endOffset;
1507 char *outStr, *outPtr, *line, *replText, *expText, *insLine;
1508 const char *insPtr;
1510 if (column < 0)
1511 column = 0;
1513 /* Allocate a buffer for the replacement string large enough to hold
1514 possibly expanded tabs in both the inserted text and the replaced
1515 area, as well as per line: 1) an additional 2*MAX_EXP_CHAR_LEN
1516 characters for padding where tabs and control characters cross the
1517 column of the selection, 2) up to "column" additional spaces per
1518 line for padding out to the position of "column", 3) padding up
1519 to the width of the inserted text if that must be padded to align
1520 the text beyond the inserted column. (Space for additional
1521 newlines if the inserted text extends beyond the end of the buffer
1522 is counted with the length of insText) */
1523 start = BufStartOfLine(buf, startPos);
1524 nLines = countLines(insText) + 1;
1525 insWidth = textWidth(insText, buf->tabDist, buf->nullSubsChar);
1526 end = BufEndOfLine(buf, BufCountForwardNLines(buf, start, nLines-1));
1527 replText = BufGetRange(buf, start, end);
1528 expText = expandTabs(replText, 0, buf->tabDist, buf->nullSubsChar,
1529 &expReplLen);
1530 XtFree(replText);
1531 XtFree(expText);
1532 expText = expandTabs(insText, 0, buf->tabDist, buf->nullSubsChar,
1533 &expInsLen);
1534 XtFree(expText);
1535 outStr = XtMalloc(expReplLen + expInsLen +
1536 nLines * (column + insWidth + MAX_EXP_CHAR_LEN) + 1);
1538 /* Loop over all lines in the buffer between start and end inserting
1539 text at column, splitting tabs and adding padding appropriately */
1540 outPtr = outStr;
1541 lineStart = start;
1542 insPtr = insText;
1543 while (True) {
1544 lineEnd = BufEndOfLine(buf, lineStart);
1545 line = BufGetRange(buf, lineStart, lineEnd);
1546 insLine = copyLine(insPtr, &len);
1547 insPtr += len;
1548 insertColInLine(line, insLine, column, insWidth, buf->tabDist,
1549 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1550 XtFree(line);
1551 XtFree(insLine);
1552 #if 0 /* Earlier comments claimed that trailing whitespace could multiply on
1553 the ends of lines, but insertColInLine looks like it should never
1554 add space unnecessarily, and this trimming interfered with
1555 paragraph filling, so lets see if it works without it. MWE */
1557 char *c;
1558 for (c=outPtr+len-1; c>outPtr && (*c == ' ' || *c == '\t'); c--)
1559 len--;
1561 #endif
1562 outPtr += len;
1563 *outPtr++ = '\n';
1564 lineStart = lineEnd < buf->length ? lineEnd + 1 : buf->length;
1565 if (*insPtr == '\0')
1566 break;
1567 insPtr++;
1569 if (outPtr != outStr)
1570 outPtr--; /* trim back off extra newline */
1571 *outPtr = '\0';
1573 /* replace the text between start and end with the new stuff */
1574 delete(buf, start, end);
1575 insert(buf, start, outStr);
1576 *nInserted = outPtr - outStr;
1577 *nDeleted = end - start;
1578 *endPos = start + (outPtr - outStr) - len + endOffset;
1579 XtFree(outStr);
1583 ** Delete a rectangle of text without calling the modify callbacks. Returns
1584 ** the number of characters replacing those between start and end. Note that
1585 ** in some pathological cases, deleting can actually increase the size of
1586 ** the buffer because of tab expansions. "endPos" returns the buffer position
1587 ** of the point in the last line where the text was removed (as a hint for
1588 ** routines which need to position the cursor after a delete operation)
1590 static void deleteRect(textBuffer *buf, int start, int end, int rectStart,
1591 int rectEnd, int *replaceLen, int *endPos)
1593 int nLines, lineStart, lineEnd, len, endOffset;
1594 char *outStr, *outPtr, *line, *text, *expText;
1596 /* allocate a buffer for the replacement string large enough to hold
1597 possibly expanded tabs as well as an additional MAX_EXP_CHAR_LEN * 2
1598 characters per line for padding where tabs and control characters cross
1599 the edges of the selection */
1600 start = BufStartOfLine(buf, start);
1601 end = BufEndOfLine(buf, end);
1602 nLines = BufCountLines(buf, start, end) + 1;
1603 text = BufGetRange(buf, start, end);
1604 expText = expandTabs(text, 0, buf->tabDist, buf->nullSubsChar, &len);
1605 XtFree(text);
1606 XtFree(expText);
1607 outStr = XtMalloc(len + nLines * MAX_EXP_CHAR_LEN * 2 + 1);
1609 /* loop over all lines in the buffer between start and end removing
1610 the text between rectStart and rectEnd and padding appropriately */
1611 lineStart = start;
1612 outPtr = outStr;
1613 while (lineStart <= buf->length && lineStart <= end) {
1614 lineEnd = BufEndOfLine(buf, lineStart);
1615 line = BufGetRange(buf, lineStart, lineEnd);
1616 deleteRectFromLine(line, rectStart, rectEnd, buf->tabDist,
1617 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1618 XtFree(line);
1619 outPtr += len;
1620 *outPtr++ = '\n';
1621 lineStart = lineEnd + 1;
1623 if (outPtr != outStr)
1624 outPtr--; /* trim back off extra newline */
1625 *outPtr = '\0';
1627 /* replace the text between start and end with the newly created string */
1628 delete(buf, start, end);
1629 insert(buf, start, outStr);
1630 *replaceLen = outPtr - outStr;
1631 *endPos = start + (outPtr - outStr) - len + endOffset;
1632 XtFree(outStr);
1636 ** Overlay a rectangular area of text without calling the modify callbacks.
1637 ** "nDeleted" and "nInserted" return the number of characters deleted and
1638 ** inserted beginning at the start of the line containing "startPos".
1639 ** "endPos" returns buffer position of the lower left edge of the inserted
1640 ** column (as a hint for routines which need to set a cursor position).
1642 static void overlayRect(textBuffer *buf, int startPos, int rectStart,
1643 int rectEnd, const char *insText,
1644 int *nDeleted, int *nInserted, int *endPos)
1646 int nLines, start, end, lineStart, lineEnd;
1647 int expInsLen, len, endOffset;
1648 char *c, *outStr, *outPtr, *line, *expText, *insLine;
1649 const char *insPtr;
1651 /* Allocate a buffer for the replacement string large enough to hold
1652 possibly expanded tabs in the inserted text, as well as per line: 1)
1653 an additional 2*MAX_EXP_CHAR_LEN characters for padding where tabs
1654 and control characters cross the column of the selection, 2) up to
1655 "column" additional spaces per line for padding out to the position
1656 of "column", 3) padding up to the width of the inserted text if that
1657 must be padded to align the text beyond the inserted column. (Space
1658 for additional newlines if the inserted text extends beyond the end
1659 of the buffer is counted with the length of insText) */
1660 start = BufStartOfLine(buf, startPos);
1661 nLines = countLines(insText) + 1;
1662 end = BufEndOfLine(buf, BufCountForwardNLines(buf, start, nLines-1));
1663 expText = expandTabs(insText, 0, buf->tabDist, buf->nullSubsChar,
1664 &expInsLen);
1665 XtFree(expText);
1666 outStr = XtMalloc(end-start + expInsLen +
1667 nLines * (rectEnd + MAX_EXP_CHAR_LEN) + 1);
1669 /* Loop over all lines in the buffer between start and end overlaying the
1670 text between rectStart and rectEnd and padding appropriately. Trim
1671 trailing space from line (whitespace at the ends of lines otherwise
1672 tends to multiply, since additional padding is added to maintain it */
1673 outPtr = outStr;
1674 lineStart = start;
1675 insPtr = insText;
1676 while (True) {
1677 lineEnd = BufEndOfLine(buf, lineStart);
1678 line = BufGetRange(buf, lineStart, lineEnd);
1679 insLine = copyLine(insPtr, &len);
1680 insPtr += len;
1681 overlayRectInLine(line, insLine, rectStart, rectEnd, buf->tabDist,
1682 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1683 XtFree(line);
1684 XtFree(insLine);
1685 for (c=outPtr+len-1; c>outPtr && (*c == ' ' || *c == '\t'); c--)
1686 len--;
1687 outPtr += len;
1688 *outPtr++ = '\n';
1689 lineStart = lineEnd < buf->length ? lineEnd + 1 : buf->length;
1690 if (*insPtr == '\0')
1691 break;
1692 insPtr++;
1694 if (outPtr != outStr)
1695 outPtr--; /* trim back off extra newline */
1696 *outPtr = '\0';
1698 /* replace the text between start and end with the new stuff */
1699 delete(buf, start, end);
1700 insert(buf, start, outStr);
1701 *nInserted = outPtr - outStr;
1702 *nDeleted = end - start;
1703 *endPos = start + (outPtr - outStr) - len + endOffset;
1704 XtFree(outStr);
1708 ** Insert characters from single-line string "insLine" in single-line string
1709 ** "line" at "column", leaving "insWidth" space before continuing line.
1710 ** "outLen" returns the number of characters written to "outStr", "endOffset"
1711 ** returns the number of characters from the beginning of the string to
1712 ** the right edge of the inserted text (as a hint for routines which need
1713 ** to position the cursor).
1715 static void insertColInLine(const char *line, const char *insLine,
1716 int column, int insWidth, int tabDist, int useTabs, char nullSubsChar,
1717 char *outStr, int *outLen, int *endOffset)
1719 char *c, *outPtr, *retabbedStr;
1720 const char *linePtr;
1721 int indent, toIndent, len, postColIndent;
1723 /* copy the line up to "column" */
1724 outPtr = outStr;
1725 indent = 0;
1726 for (linePtr=line; *linePtr!='\0'; linePtr++) {
1727 len = BufCharWidth(*linePtr, indent, tabDist, nullSubsChar);
1728 if (indent + len > column)
1729 break;
1730 indent += len;
1731 *outPtr++ = *linePtr;
1734 /* If "column" falls in the middle of a character, and the character is a
1735 tab, leave it off and leave the indent short and it will get padded
1736 later. If it's a control character, insert it and adjust indent
1737 accordingly. */
1738 if (indent < column && *linePtr != '\0') {
1739 postColIndent = indent + len;
1740 if (*linePtr == '\t')
1741 linePtr++;
1742 else {
1743 *outPtr++ = *linePtr++;
1744 indent += len;
1746 } else
1747 postColIndent = indent;
1749 /* If there's no text after the column and no text to insert, that's all */
1750 if (*insLine == '\0' && *linePtr == '\0') {
1751 *outLen = *endOffset = outPtr - outStr;
1752 return;
1755 /* pad out to column if text is too short */
1756 if (indent < column) {
1757 addPadding(outPtr, indent, column, tabDist, useTabs, nullSubsChar,&len);
1758 outPtr += len;
1759 indent = column;
1762 /* Copy the text from "insLine" (if any), recalculating the tabs as if
1763 the inserted string began at column 0 to its new column destination */
1764 if (*insLine != '\0') {
1765 retabbedStr = realignTabs(insLine, 0, indent, tabDist, useTabs,
1766 nullSubsChar, &len);
1767 for (c=retabbedStr; *c!='\0'; c++) {
1768 *outPtr++ = *c;
1769 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
1770 indent += len;
1772 XtFree(retabbedStr);
1775 /* If the original line did not extend past "column", that's all */
1776 if (*linePtr == '\0') {
1777 *outLen = *endOffset = outPtr - outStr;
1778 return;
1781 /* Pad out to column + width of inserted text + (additional original
1782 offset due to non-breaking character at column) */
1783 toIndent = column + insWidth + postColIndent-column;
1784 addPadding(outPtr, indent, toIndent, tabDist, useTabs, nullSubsChar, &len);
1785 outPtr += len;
1786 indent = toIndent;
1788 /* realign tabs for text beyond "column" and write it out */
1789 retabbedStr = realignTabs(linePtr, postColIndent, indent, tabDist,
1790 useTabs, nullSubsChar, &len);
1791 strcpy(outPtr, retabbedStr);
1792 XtFree(retabbedStr);
1793 *endOffset = outPtr - outStr;
1794 *outLen = (outPtr - outStr) + len;
1798 ** Remove characters in single-line string "line" between displayed positions
1799 ** "rectStart" and "rectEnd", and write the result to "outStr", which is
1800 ** assumed to be large enough to hold the returned string. Note that in
1801 ** certain cases, it is possible for the string to get longer due to
1802 ** expansion of tabs. "endOffset" returns the number of characters from
1803 ** the beginning of the string to the point where the characters were
1804 ** deleted (as a hint for routines which need to position the cursor).
1806 static void deleteRectFromLine(const char *line, int rectStart, int rectEnd,
1807 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
1808 int *endOffset)
1810 int indent, preRectIndent, postRectIndent, len;
1811 const char *c;
1812 char *outPtr;
1813 char *retabbedStr;
1815 /* copy the line up to rectStart */
1816 outPtr = outStr;
1817 indent = 0;
1818 for (c=line; *c!='\0'; c++) {
1819 if (indent > rectStart)
1820 break;
1821 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
1822 if (indent + len > rectStart && (indent == rectStart || *c == '\t'))
1823 break;
1824 indent += len;
1825 *outPtr++ = *c;
1827 preRectIndent = indent;
1829 /* skip the characters between rectStart and rectEnd */
1830 for(; *c!='\0' && indent<rectEnd; c++)
1831 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
1832 postRectIndent = indent;
1834 /* If the line ended before rectEnd, there's nothing more to do */
1835 if (*c == '\0') {
1836 *outPtr = '\0';
1837 *outLen = *endOffset = outPtr - outStr;
1838 return;
1841 /* fill in any space left by removed tabs or control characters
1842 which straddled the boundaries */
1843 indent = max(rectStart + postRectIndent-rectEnd, preRectIndent);
1844 addPadding(outPtr, preRectIndent, indent, tabDist, useTabs, nullSubsChar,
1845 &len);
1846 outPtr += len;
1848 /* Copy the rest of the line. If the indentation has changed, preserve
1849 the position of non-whitespace characters by converting tabs to
1850 spaces, then back to tabs with the correct offset */
1851 retabbedStr = realignTabs(c, postRectIndent, indent, tabDist, useTabs,
1852 nullSubsChar, &len);
1853 strcpy(outPtr, retabbedStr);
1854 XtFree(retabbedStr);
1855 *endOffset = outPtr - outStr;
1856 *outLen = (outPtr - outStr) + len;
1860 ** Overlay characters from single-line string "insLine" on single-line string
1861 ** "line" between displayed character offsets "rectStart" and "rectEnd".
1862 ** "outLen" returns the number of characters written to "outStr", "endOffset"
1863 ** returns the number of characters from the beginning of the string to
1864 ** the right edge of the inserted text (as a hint for routines which need
1865 ** to position the cursor).
1867 ** This code does not handle control characters very well, but oh well.
1869 static void overlayRectInLine(const char *line, const char *insLine,
1870 int rectStart, int rectEnd, int tabDist, int useTabs,
1871 char nullSubsChar, char *outStr, int *outLen, int *endOffset)
1873 char *c, *outPtr, *retabbedStr;
1874 const char *linePtr;
1875 int inIndent, outIndent, len, postRectIndent;
1877 /* copy the line up to "rectStart" or just before the char that
1878 contains it*/
1879 outPtr = outStr;
1880 inIndent = outIndent = 0;
1881 for (linePtr=line; *linePtr!='\0'; linePtr++) {
1882 len = BufCharWidth(*linePtr, inIndent, tabDist, nullSubsChar);
1883 if (inIndent + len > rectStart)
1884 break;
1885 inIndent += len;
1886 outIndent += len;
1887 *outPtr++ = *linePtr;
1890 /* If "rectStart" falls in the middle of a character, and the character
1891 is a tab, leave it off and leave the outIndent short and it will get
1892 padded later. If it's a control character, insert it and adjust
1893 outIndent accordingly. */
1894 if (inIndent < rectStart && *linePtr != '\0') {
1895 if (*linePtr == '\t') {
1896 /* Skip past the tab */
1897 linePtr++;
1898 inIndent += len;
1899 } else {
1900 *outPtr++ = *linePtr++;
1901 outIndent += len;
1902 inIndent += len;
1906 /* skip the characters between rectStart and rectEnd */
1907 for(; *linePtr!='\0' && inIndent < rectEnd; linePtr++)
1908 inIndent += BufCharWidth(*linePtr, inIndent, tabDist, nullSubsChar);
1909 postRectIndent = inIndent;
1911 /* After this inIndent is dead and linePtr is supposed to point at the
1912 character just past the last character that will be altered by
1913 the overlay, whether that's a \t or otherwise. postRectIndent is
1914 the position at which that character is supposed to appear */
1916 /* If there's no text after rectStart and no text to insert, that's all */
1917 if (*insLine == '\0' && *linePtr == '\0') {
1918 *outLen = *endOffset = outPtr - outStr;
1919 return;
1922 /* pad out to rectStart if text is too short */
1923 if (outIndent < rectStart) {
1924 addPadding(outPtr, outIndent, rectStart, tabDist, useTabs, nullSubsChar,
1925 &len);
1926 outPtr += len;
1928 outIndent = rectStart;
1930 /* Copy the text from "insLine" (if any), recalculating the tabs as if
1931 the inserted string began at column 0 to its new column destination */
1932 if (*insLine != '\0') {
1933 retabbedStr = realignTabs(insLine, 0, rectStart, tabDist, useTabs,
1934 nullSubsChar, &len);
1935 for (c=retabbedStr; *c!='\0'; c++) {
1936 *outPtr++ = *c;
1937 len = BufCharWidth(*c, outIndent, tabDist, nullSubsChar);
1938 outIndent += len;
1940 XtFree(retabbedStr);
1943 /* If the original line did not extend past "rectStart", that's all */
1944 if (*linePtr == '\0') {
1945 *outLen = *endOffset = outPtr - outStr;
1946 return;
1949 /* Pad out to rectEnd + (additional original offset
1950 due to non-breaking character at right boundary) */
1951 addPadding(outPtr, outIndent, postRectIndent, tabDist, useTabs,
1952 nullSubsChar, &len);
1953 outPtr += len;
1954 outIndent = postRectIndent;
1956 /* copy the text beyond "rectEnd" */
1957 strcpy(outPtr, linePtr);
1958 *endOffset = outPtr - outStr;
1959 *outLen = (outPtr - outStr) + strlen(linePtr);
1962 static void setSelection(selection *sel, int start, int end)
1964 sel->selected = start != end;
1965 sel->zeroWidth = (start == end) ? 1 : 0;
1966 sel->rectangular = False;
1967 sel->start = min(start, end);
1968 sel->end = max(start, end);
1971 static void setRectSelect(selection *sel, int start, int end,
1972 int rectStart, int rectEnd)
1974 sel->selected = rectStart < rectEnd;
1975 sel->zeroWidth = (rectStart == rectEnd) ? 1 : 0;
1976 sel->rectangular = True;
1977 sel->start = start;
1978 sel->end = end;
1979 sel->rectStart = rectStart;
1980 sel->rectEnd = rectEnd;
1983 static int getSelectionPos(selection *sel, int *start, int *end,
1984 int *isRect, int *rectStart, int *rectEnd)
1986 /* Always fill in the parameters (zero-width can be requested too). */
1987 *isRect = sel->rectangular;
1988 *start = sel->start;
1989 *end = sel->end;
1990 if (sel->rectangular) {
1991 *rectStart = sel->rectStart;
1992 *rectEnd = sel->rectEnd;
1994 return sel->selected;
1997 static char *getSelectionText(textBuffer *buf, selection *sel)
1999 int start, end, isRect, rectStart, rectEnd;
2000 char *text;
2002 /* If there's no selection, return an allocated empty string */
2003 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd)) {
2004 text = XtMalloc(1);
2005 *text = '\0';
2006 return text;
2009 /* If the selection is not rectangular, return the selected range */
2010 if (isRect)
2011 return BufGetTextInRect(buf, start, end, rectStart, rectEnd);
2012 else
2013 return BufGetRange(buf, start, end);
2016 static void removeSelected(textBuffer *buf, selection *sel)
2018 int start, end;
2019 int isRect, rectStart, rectEnd;
2021 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd))
2022 return;
2023 if (isRect)
2024 BufRemoveRect(buf, start, end, rectStart, rectEnd);
2025 else
2026 BufRemove(buf, start, end);
2029 static void replaceSelected(textBuffer *buf, selection *sel, const char *text)
2031 int start, end, isRect, rectStart, rectEnd;
2032 selection oldSelection = *sel;
2034 /* If there's no selection, return */
2035 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd))
2036 return;
2038 /* Do the appropriate type of replace */
2039 if (isRect)
2040 BufReplaceRect(buf, start, end, rectStart, rectEnd, text);
2041 else
2042 BufReplace(buf, start, end, text);
2044 /* Unselect (happens automatically in BufReplace, but BufReplaceRect
2045 can't detect when the contents of a selection goes away) */
2046 sel->selected = False;
2047 redisplaySelection(buf, &oldSelection, sel);
2050 static void addPadding(char *string, int startIndent, int toIndent,
2051 int tabDist, int useTabs, char nullSubsChar, int *charsAdded)
2053 char *outPtr;
2054 int len, indent;
2056 indent = startIndent;
2057 outPtr = string;
2058 if (useTabs) {
2059 while (indent < toIndent) {
2060 len = BufCharWidth('\t', indent, tabDist, nullSubsChar);
2061 if (len > 1 && indent + len <= toIndent) {
2062 *outPtr++ = '\t';
2063 indent += len;
2064 } else {
2065 *outPtr++ = ' ';
2066 indent++;
2069 } else {
2070 while (indent < toIndent) {
2071 *outPtr++ = ' ';
2072 indent++;
2075 *charsAdded = outPtr - string;
2079 ** Call the stored modify callback procedure(s) for this buffer to update the
2080 ** changed area(s) on the screen and any other listeners.
2082 static void callModifyCBs(textBuffer *buf, int pos, int nDeleted,
2083 int nInserted, int nRestyled, char *deletedText)
2085 int i;
2087 for (i=0; i<buf->nModifyProcs; i++)
2088 (*buf->modifyProcs[i])(pos, nInserted, nDeleted, nRestyled,
2089 deletedText, buf->cbArgs[i]);
2093 ** Call the stored pre-delete callback procedure(s) for this buffer to update
2094 ** the changed area(s) on the screen and any other listeners.
2096 static void callPreDeleteCBs(textBuffer *buf, int pos, int nDeleted)
2098 int i;
2100 for (i=0; i<buf->nPreDeleteProcs; i++)
2101 (*buf->preDeleteProcs[i])(pos, nDeleted, buf->preDeleteCbArgs[i]);
2105 ** Call the stored redisplay procedure(s) for this buffer to update the
2106 ** screen for a change in a selection.
2108 static void redisplaySelection(textBuffer *buf, selection *oldSelection,
2109 selection *newSelection)
2111 int oldStart, oldEnd, newStart, newEnd, ch1Start, ch1End, ch2Start, ch2End;
2113 /* If either selection is rectangular, add an additional character to
2114 the end of the selection to request the redraw routines to wipe out
2115 the parts of the selection beyond the end of the line */
2116 oldStart = oldSelection->start;
2117 newStart = newSelection->start;
2118 oldEnd = oldSelection->end;
2119 newEnd = newSelection->end;
2120 if (oldSelection->rectangular)
2121 oldEnd++;
2122 if (newSelection->rectangular)
2123 newEnd++;
2125 /* If the old or new selection is unselected, just redisplay the
2126 single area that is (was) selected and return */
2127 if (!oldSelection->selected && !newSelection->selected)
2128 return;
2129 if (!oldSelection->selected) {
2130 callModifyCBs(buf, newStart, 0, 0, newEnd-newStart, NULL);
2131 return;
2133 if (!newSelection->selected) {
2134 callModifyCBs(buf, oldStart, 0, 0, oldEnd-oldStart, NULL);
2135 return;
2138 /* If the selection changed from normal to rectangular or visa versa, or
2139 if a rectangular selection changed boundaries, redisplay everything */
2140 if ((oldSelection->rectangular && !newSelection->rectangular) ||
2141 (!oldSelection->rectangular && newSelection->rectangular) ||
2142 (oldSelection->rectangular && (
2143 (oldSelection->rectStart != newSelection->rectStart) ||
2144 (oldSelection->rectEnd != newSelection->rectEnd)))) {
2145 callModifyCBs(buf, min(oldStart, newStart), 0, 0,
2146 max(oldEnd, newEnd) - min(oldStart, newStart), NULL);
2147 return;
2150 /* If the selections are non-contiguous, do two separate updates
2151 and return */
2152 if (oldEnd < newStart || newEnd < oldStart) {
2153 callModifyCBs(buf, oldStart, 0, 0, oldEnd-oldStart, NULL);
2154 callModifyCBs(buf, newStart, 0, 0, newEnd-newStart, NULL);
2155 return;
2158 /* Otherwise, separate into 3 separate regions: ch1, and ch2 (the two
2159 changed areas), and the unchanged area of their intersection,
2160 and update only the changed area(s) */
2161 ch1Start = min(oldStart, newStart);
2162 ch2End = max(oldEnd, newEnd);
2163 ch1End = max(oldStart, newStart);
2164 ch2Start = min(oldEnd, newEnd);
2165 if (ch1Start != ch1End)
2166 callModifyCBs(buf, ch1Start, 0, 0, ch1End-ch1Start, NULL);
2167 if (ch2Start != ch2End)
2168 callModifyCBs(buf, ch2Start, 0, 0, ch2End-ch2Start, NULL);
2171 static void moveGap(textBuffer *buf, int pos)
2173 int gapLen = buf->gapEnd - buf->gapStart;
2175 if (pos > buf->gapStart)
2176 memmove(&buf->buf[buf->gapStart], &buf->buf[buf->gapEnd],
2177 pos - buf->gapStart);
2178 else
2179 memmove(&buf->buf[pos + gapLen], &buf->buf[pos], buf->gapStart - pos);
2180 buf->gapEnd += pos - buf->gapStart;
2181 buf->gapStart += pos - buf->gapStart;
2185 ** reallocate the text storage in "buf" to have a gap starting at "newGapStart"
2186 ** and a gap size of "newGapLen", preserving the buffer's current contents.
2188 static void reallocateBuf(textBuffer *buf, int newGapStart, int newGapLen)
2190 char *newBuf;
2191 int newGapEnd;
2193 newBuf = XtMalloc(buf->length + newGapLen);
2194 newGapEnd = newGapStart + newGapLen;
2195 if (newGapStart <= buf->gapStart) {
2196 memcpy(newBuf, buf->buf, newGapStart);
2197 memcpy(&newBuf[newGapEnd], &buf->buf[newGapStart],
2198 buf->gapStart - newGapStart);
2199 memcpy(&newBuf[newGapEnd + buf->gapStart - newGapStart],
2200 &buf->buf[buf->gapEnd], buf->length - buf->gapStart);
2201 } else { /* newGapStart > buf->gapStart */
2202 memcpy(newBuf, buf->buf, buf->gapStart);
2203 memcpy(&newBuf[buf->gapStart], &buf->buf[buf->gapEnd],
2204 newGapStart - buf->gapStart);
2205 memcpy(&newBuf[newGapEnd],
2206 &buf->buf[buf->gapEnd + newGapStart - buf->gapStart],
2207 buf->length - newGapStart);
2209 XtFree(buf->buf);
2210 buf->buf = newBuf;
2211 buf->gapStart = newGapStart;
2212 buf->gapEnd = newGapEnd;
2213 #ifdef PURIFY
2214 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
2215 #endif
2219 ** Update all of the selections in "buf" for changes in the buffer's text
2221 static void updateSelections(textBuffer *buf, int pos, int nDeleted,
2222 int nInserted)
2224 updateSelection(&buf->primary, pos, nDeleted, nInserted);
2225 updateSelection(&buf->secondary, pos, nDeleted, nInserted);
2226 updateSelection(&buf->highlight, pos, nDeleted, nInserted);
2230 ** Update an individual selection for changes in the corresponding text
2232 static void updateSelection(selection *sel, int pos, int nDeleted,
2233 int nInserted)
2235 if ((!sel->selected && !sel->zeroWidth) || pos > sel->end)
2236 return;
2237 if (pos+nDeleted <= sel->start) {
2238 sel->start += nInserted - nDeleted;
2239 sel->end += nInserted - nDeleted;
2240 } else if (pos <= sel->start && pos+nDeleted >= sel->end) {
2241 sel->start = pos;
2242 sel->end = pos;
2243 sel->selected = False;
2244 sel->zeroWidth = False;
2245 } else if (pos <= sel->start && pos+nDeleted < sel->end) {
2246 sel->start = pos;
2247 sel->end = nInserted + sel->end - nDeleted;
2248 } else if (pos < sel->end) {
2249 sel->end += nInserted - nDeleted;
2250 if (sel->end <= sel->start)
2251 sel->selected = False;
2256 ** Search forwards in buffer "buf" for character "searchChar", starting
2257 ** with the character "startPos", and returning the result in "foundPos"
2258 ** returns True if found, False if not. (The difference between this and
2259 ** BufSearchForward is that it's optimized for single characters. The
2260 ** overall performance of the text widget is dependent on its ability to
2261 ** count lines quickly, hence searching for a single character: newline)
2263 static int searchForward(textBuffer *buf, int startPos, char searchChar,
2264 int *foundPos)
2266 int pos, gapLen = buf->gapEnd - buf->gapStart;
2268 pos = startPos;
2269 while (pos < buf->gapStart) {
2270 if (buf->buf[pos] == searchChar) {
2271 *foundPos = pos;
2272 return True;
2274 pos++;
2276 while (pos < buf->length) {
2277 if (buf->buf[pos + gapLen] == searchChar) {
2278 *foundPos = pos;
2279 return True;
2281 pos++;
2283 *foundPos = buf->length;
2284 return False;
2288 ** Search backwards in buffer "buf" for character "searchChar", starting
2289 ** with the character BEFORE "startPos", returning the result in "foundPos"
2290 ** returns True if found, False if not. (The difference between this and
2291 ** BufSearchBackward is that it's optimized for single characters. The
2292 ** overall performance of the text widget is dependent on its ability to
2293 ** count lines quickly, hence searching for a single character: newline)
2295 static int searchBackward(textBuffer *buf, int startPos, char searchChar,
2296 int *foundPos)
2298 int pos, gapLen = buf->gapEnd - buf->gapStart;
2300 if (startPos == 0) {
2301 *foundPos = 0;
2302 return False;
2304 pos = startPos == 0 ? 0 : startPos - 1;
2305 while (pos >= buf->gapStart) {
2306 if (buf->buf[pos + gapLen] == searchChar) {
2307 *foundPos = pos;
2308 return True;
2310 pos--;
2312 while (pos >= 0) {
2313 if (buf->buf[pos] == searchChar) {
2314 *foundPos = pos;
2315 return True;
2317 pos--;
2319 *foundPos = 0;
2320 return False;
2324 ** Copy from "text" to end up to but not including newline (or end of "text")
2325 ** and return the copy as the function value, and the length of the line in
2326 ** "lineLen"
2328 static char *copyLine(const char *text, int *lineLen)
2330 int len = 0;
2331 const char *c;
2332 char *outStr;
2334 for (c=text; *c!='\0' && *c!='\n'; c++)
2335 len++;
2336 outStr = XtMalloc(len + 1);
2337 strncpy(outStr, text, len);
2338 outStr[len] = '\0';
2339 *lineLen = len;
2340 return outStr;
2344 ** Count the number of newlines in a null-terminated text string;
2346 static int countLines(const char *string)
2348 const char *c;
2349 int lineCount = 0;
2351 for (c=string; *c!='\0'; c++)
2352 if (*c == '\n') lineCount++;
2353 return lineCount;
2357 ** Measure the width in displayed characters of string "text"
2359 static int textWidth(const char *text, int tabDist, char nullSubsChar)
2361 int width = 0, maxWidth = 0;
2362 const char *c;
2364 for (c=text; *c!='\0'; c++) {
2365 if (*c == '\n') {
2366 if (width > maxWidth)
2367 maxWidth = width;
2368 width = 0;
2369 } else
2370 width += BufCharWidth(*c, width, tabDist, nullSubsChar);
2372 if (width > maxWidth)
2373 return width;
2374 return maxWidth;
2378 ** Find the first and last character position in a line withing a rectangular
2379 ** selection (for copying). Includes tabs which cross rectStart, but not
2380 ** control characters which do so. Leaves off tabs which cross rectEnd.
2382 ** Technically, the calling routine should convert tab characters which
2383 ** cross the right boundary of the selection to spaces which line up with
2384 ** the edge of the selection. Unfortunately, the additional memory
2385 ** management required in the parent routine to allow for the changes
2386 ** in string size is not worth all the extra work just for a couple of
2387 ** shifted characters, so if a tab protrudes, just lop it off and hope
2388 ** that there are other characters in the selection to establish the right
2389 ** margin for subsequent columnar pastes of this data.
2391 static void findRectSelBoundariesForCopy(textBuffer *buf, int lineStartPos,
2392 int rectStart, int rectEnd, int *selStart, int *selEnd)
2394 int pos, width, indent = 0;
2395 char c;
2397 /* find the start of the selection */
2398 for (pos=lineStartPos; pos<buf->length; pos++) {
2399 c = BufGetCharacter(buf, pos);
2400 if (c == '\n')
2401 break;
2402 width = BufCharWidth(c, indent, buf->tabDist, buf->nullSubsChar);
2403 if (indent + width > rectStart) {
2404 if (indent != rectStart && c != '\t') {
2405 pos++;
2406 indent += width;
2408 break;
2410 indent += width;
2412 *selStart = pos;
2414 /* find the end */
2415 for (; pos<buf->length; pos++) {
2416 c = BufGetCharacter(buf, pos);
2417 if (c == '\n')
2418 break;
2419 width = BufCharWidth(c, indent, buf->tabDist, buf->nullSubsChar);
2420 indent += width;
2421 if (indent > rectEnd) {
2422 if (indent-width != rectEnd && c != '\t')
2423 pos++;
2424 break;
2427 *selEnd = pos;
2431 ** Adjust the space and tab characters from string "text" so that non-white
2432 ** characters remain stationary when the text is shifted from starting at
2433 ** "origIndent" to starting at "newIndent". Returns an allocated string
2434 ** which must be freed by the caller with XtFree.
2436 static char *realignTabs(const char *text, int origIndent, int newIndent,
2437 int tabDist, int useTabs, char nullSubsChar, int *newLength)
2439 char *expStr, *outStr;
2440 int len;
2442 /* If the tabs settings are the same, retain original tabs */
2443 if (origIndent % tabDist == newIndent %tabDist) {
2444 len = strlen(text);
2445 outStr = XtMalloc(len + 1);
2446 strcpy(outStr, text);
2447 *newLength = len;
2448 return outStr;
2451 /* If the tab settings are not the same, brutally convert tabs to
2452 spaces, then back to tabs in the new position */
2453 expStr = expandTabs(text, origIndent, tabDist, nullSubsChar, &len);
2454 if (!useTabs) {
2455 *newLength = len;
2456 return expStr;
2458 outStr = unexpandTabs(expStr, newIndent, tabDist, nullSubsChar, newLength);
2459 XtFree(expStr);
2460 return outStr;
2464 ** Expand tabs to spaces for a block of text. The additional parameter
2465 ** "startIndent" if nonzero, indicates that the text is a rectangular selection
2466 ** beginning at column "startIndent"
2468 static char *expandTabs(const char *text, int startIndent, int tabDist,
2469 char nullSubsChar, int *newLen)
2471 char *outStr, *outPtr;
2472 const char *c;
2473 int indent, len, outLen = 0;
2475 /* rehearse the expansion to figure out length for output string */
2476 indent = startIndent;
2477 for (c=text; *c!='\0'; c++) {
2478 if (*c == '\t') {
2479 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
2480 outLen += len;
2481 indent += len;
2482 } else if (*c == '\n') {
2483 indent = startIndent;
2484 outLen++;
2485 } else {
2486 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
2487 outLen++;
2491 /* do the expansion */
2492 outStr = XtMalloc(outLen+1);
2493 outPtr = outStr;
2494 indent = startIndent;
2495 for (c=text; *c!= '\0'; c++) {
2496 if (*c == '\t') {
2497 len = BufExpandCharacter(*c, indent, outPtr, tabDist, nullSubsChar);
2498 outPtr += len;
2499 indent += len;
2500 } else if (*c == '\n') {
2501 indent = startIndent;
2502 *outPtr++ = *c;
2503 } else {
2504 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
2505 *outPtr++ = *c;
2508 outStr[outLen] = '\0';
2509 *newLen = outLen;
2510 return outStr;
2514 ** Convert sequences of spaces into tabs. The threshold for conversion is
2515 ** when 3 or more spaces can be converted into a single tab, this avoids
2516 ** converting double spaces after a period withing a block of text.
2518 static char *unexpandTabs(const char *text, int startIndent, int tabDist,
2519 char nullSubsChar, int *newLen)
2521 char *outStr, *outPtr, expandedChar[MAX_EXP_CHAR_LEN];
2522 const char *c;
2523 int indent, len;
2525 outStr = XtMalloc(strlen(text)+1);
2526 outPtr = outStr;
2527 indent = startIndent;
2528 for (c=text; *c!='\0';) {
2529 if (*c == ' ') {
2530 len = BufExpandCharacter('\t', indent, expandedChar, tabDist,
2531 nullSubsChar);
2532 if (len >= 3 && !strncmp(c, expandedChar, len)) {
2533 c += len;
2534 *outPtr++ = '\t';
2535 indent += len;
2536 } else {
2537 *outPtr++ = *c++;
2538 indent++;
2540 } else if (*c == '\n') {
2541 indent = startIndent;
2542 *outPtr++ = *c++;
2543 } else {
2544 *outPtr++ = *c++;
2545 indent++;
2548 *outPtr = '\0';
2549 *newLen = outPtr - outStr;
2550 return outStr;
2553 static int max(int i1, int i2)
2555 return i1 >= i2 ? i1 : i2;
2558 static int min(int i1, int i2)
2560 return i1 <= i2 ? i1 : i2;