Added OpenMotif 2.2.3 and Lesstif 0.93.94 to the "known good" lists.
[nedit.git] / source / textBuf.c
blob15e3c4466891f4c7793776b35145bb6cc8106ed7
1 static const char CVSID[] = "$Id: textBuf.c,v 1.31 2004/03/03 13:28:20 edg Exp $";
2 /*******************************************************************************
3 * *
4 * textBuf.c - Manage source text for one or more text areas *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * June 15, 1995 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "textBuf.h"
34 #include "rangeset.h"
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <ctype.h>
41 #ifdef HAVE_DEBUG_H
42 #include "../debug.h"
43 #endif
45 #define PREFERRED_GAP_SIZE 80 /* Initial size for the buffer gap (empty space
46 in the buffer where text might be inserted
47 if the user is typing sequential chars) */
49 static void histogramCharacters(const char *string, int length, char hist[256],
50 int init);
51 static void subsChars(char *string, int length, char fromChar, char toChar);
52 static char chooseNullSubsChar(char hist[256]);
53 static int insert(textBuffer *buf, int pos, const char *text);
54 static void delete(textBuffer *buf, int start, int end);
55 static void deleteRect(textBuffer *buf, int start, int end, int rectStart,
56 int rectEnd, int *replaceLen, int *endPos);
57 static void insertCol(textBuffer *buf, int column, int startPos, const char *insText,
58 int *nDeleted, int *nInserted, int *endPos);
59 static void overlayRect(textBuffer *buf, int startPos, int rectStart,
60 int rectEnd, const char *insText, int *nDeleted, int *nInserted, int *endPos);
61 static void insertColInLine(const char *line, const char *insLine, int column, int insWidth,
62 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
63 int *endOffset);
64 static void deleteRectFromLine(const char *line, int rectStart, int rectEnd,
65 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
66 int *endOffset);
67 static void overlayRectInLine(const char *line, const char *insLine, int rectStart,
68 int rectEnd, int tabDist, int useTabs, char nullSubsChar, char *outStr,
69 int *outLen, int *endOffset);
70 static void callPreDeleteCBs(textBuffer *buf, int pos, int nDeleted);
71 static void callModifyCBs(textBuffer *buf, int pos, int nDeleted,
72 int nInserted, int nRestyled, char *deletedText);
73 static void redisplaySelection(textBuffer *buf, selection *oldSelection,
74 selection *newSelection);
75 static void moveGap(textBuffer *buf, int pos);
76 static void reallocateBuf(textBuffer *buf, int newGapStart, int newGapLen);
77 static void setSelection(selection *sel, int start, int end);
78 static void setRectSelect(selection *sel, int start, int end,
79 int rectStart, int rectEnd);
80 static void updateSelections(textBuffer *buf, int pos, int nDeleted,
81 int nInserted);
82 static void updateSelection(selection *sel, int pos, int nDeleted,
83 int nInserted);
84 static int getSelectionPos(selection *sel, int *start, int *end,
85 int *isRect, int *rectStart, int *rectEnd);
86 static char *getSelectionText(textBuffer *buf, selection *sel);
87 static void removeSelected(textBuffer *buf, selection *sel);
88 static void replaceSelected(textBuffer *buf, selection *sel, const char *text);
89 static void addPadding(char *string, int startIndent, int toIndent,
90 int tabDist, int useTabs, char nullSubsChar, int *charsAdded);
91 static int searchForward(textBuffer *buf, int startPos, char searchChar,
92 int *foundPos);
93 static int searchBackward(textBuffer *buf, int startPos, char searchChar,
94 int *foundPos);
95 static char *copyLine(const char *text, int *lineLen);
96 static int countLines(const char *string);
97 static int textWidth(const char *text, int tabDist, char nullSubsChar);
98 static void findRectSelBoundariesForCopy(textBuffer *buf, int lineStartPos,
99 int rectStart, int rectEnd, int *selStart, int *selEnd);
100 static char *realignTabs(const char *text, int origIndent, int newIndent,
101 int tabDist, int useTabs, char nullSubsChar, int *newLength);
102 static char *expandTabs(const char *text, int startIndent, int tabDist,
103 char nullSubsChar, int *newLen);
104 static char *unexpandTabs(const char *text, int startIndent, int tabDist,
105 char nullSubsChar, int *newLen);
106 static int max(int i1, int i2);
107 static int min(int i1, int i2);
109 #ifdef __MVS__
110 static const char *ControlCodeTable[64] = {
111 "nul", "soh", "stx", "etx", "sel", "ht", "rnl", "del",
112 "ge", "sps", "rpt", "vt", "ff", "cr", "so", "si",
113 "dle", "dc1", "dc2", "dc3", "res", "nl", "bs", "poc",
114 "can", "em", "ubs", "cu1", "ifs", "igs", "irs", "ius",
115 "ds", "sos", "fs", "wus", "byp", "lf", "etb", "esc",
116 "sa", "sfe", "sm", "csp", "mfa", "enq", "ack", "bel",
117 "x30", "x31", "syn", "ir", "pp", "trn", "nbs", "eot",
118 "sbs", "it", "rff", "cu3", "dc4", "nak", "x3e", "sub"};
119 #else
120 static const char *ControlCodeTable[32] = {
121 "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel",
122 "bs", "ht", "nl", "vt", "np", "cr", "so", "si",
123 "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb",
124 "can", "em", "sub", "esc", "fs", "gs", "rs", "us"};
125 #endif
128 ** Create an empty text buffer
130 textBuffer *BufCreate(void)
132 textBuffer *buf = BufCreatePreallocated(0);
133 return buf;
137 ** Create an empty text buffer of a pre-determined size (use this to
138 ** avoid unnecessary re-allocation if you know exactly how much the buffer
139 ** will need to hold
141 textBuffer *BufCreatePreallocated(int requestedSize)
143 textBuffer *buf;
145 buf = (textBuffer *)XtMalloc(sizeof(textBuffer));
146 buf->length = 0;
147 buf->buf = XtMalloc(requestedSize + PREFERRED_GAP_SIZE);
148 buf->gapStart = 0;
149 buf->gapEnd = PREFERRED_GAP_SIZE;
150 buf->tabDist = 8;
151 buf->useTabs = True;
152 buf->primary.selected = False;
153 buf->primary.zeroWidth = False;
154 buf->primary.rectangular = False;
155 buf->primary.start = buf->primary.end = 0;
156 buf->secondary.selected = False;
157 buf->secondary.zeroWidth = False;
158 buf->secondary.start = buf->secondary.end = 0;
159 buf->secondary.rectangular = False;
160 buf->highlight.selected = False;
161 buf->highlight.zeroWidth = False;
162 buf->highlight.start = buf->highlight.end = 0;
163 buf->highlight.rectangular = False;
164 buf->modifyProcs = NULL;
165 buf->cbArgs = NULL;
166 buf->nModifyProcs = 0;
167 buf->preDeleteProcs = NULL;
168 buf->preDeleteCbArgs = NULL;
169 buf->nPreDeleteProcs = 0;
170 buf->nullSubsChar = '\0';
171 #ifdef PURIFY
172 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
173 #endif
174 buf->rangesetTable = NULL;
175 return buf;
179 ** Free a text buffer
181 void BufFree(textBuffer *buf)
183 XtFree(buf->buf);
184 if (buf->nModifyProcs != 0) {
185 XtFree((char *)buf->modifyProcs);
186 XtFree((char *)buf->cbArgs);
188 if (buf->rangesetTable)
189 RangesetTableFree(buf->rangesetTable);
190 if (buf->nPreDeleteProcs != 0) {
191 XtFree((char *)buf->preDeleteProcs);
192 XtFree((char *)buf->preDeleteCbArgs);
194 XtFree((char *)buf);
198 ** Get the entire contents of a text buffer. Memory is allocated to contain
199 ** the returned string, which the caller must free.
201 char *BufGetAll(textBuffer *buf)
203 char *text;
205 text = XtMalloc(buf->length+1);
206 memcpy(text, buf->buf, buf->gapStart);
207 memcpy(&text[buf->gapStart], &buf->buf[buf->gapEnd],
208 buf->length - buf->gapStart);
209 text[buf->length] = '\0';
210 return text;
214 ** Replace the entire contents of the text buffer
216 void BufSetAll(textBuffer *buf, const char *text)
218 int length, deletedLength;
219 char *deletedText;
220 length = strlen(text);
222 callPreDeleteCBs(buf, 0, buf->length);
224 /* Save information for redisplay, and get rid of the old buffer */
225 deletedText = BufGetAll(buf);
226 deletedLength = buf->length;
227 XtFree(buf->buf);
229 /* Start a new buffer with a gap of PREFERRED_GAP_SIZE in the center */
230 buf->buf = XtMalloc(length + PREFERRED_GAP_SIZE);
231 buf->length = length;
232 buf->gapStart = length/2;
233 buf->gapEnd = buf->gapStart + PREFERRED_GAP_SIZE;
234 memcpy(buf->buf, text, buf->gapStart);
235 memcpy(&buf->buf[buf->gapEnd], &text[buf->gapStart], length-buf->gapStart);
236 #ifdef PURIFY
237 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
238 #endif
240 /* Zero all of the existing selections */
241 updateSelections(buf, 0, deletedLength, 0);
243 /* Call the saved display routine(s) to update the screen */
244 callModifyCBs(buf, 0, deletedLength, length, 0, deletedText);
245 XtFree(deletedText);
249 ** Return a copy of the text between "start" and "end" character positions
250 ** from text buffer "buf". Positions start at 0, and the range does not
251 ** include the character pointed to by "end"
253 char *BufGetRange(textBuffer *buf, int start, int end)
255 char *text;
256 int length, part1Length;
258 /* Make sure start and end are ok, and allocate memory for returned string.
259 If start is bad, return "", if end is bad, adjust it. */
260 if (start < 0 || start > buf->length) {
261 text = XtMalloc(1);
262 text[0] = '\0';
263 return text;
265 if (end < start) {
266 int temp = start;
267 start = end;
268 end = temp;
270 if (end > buf->length)
271 end = buf->length;
272 length = end - start;
273 text = XtMalloc(length+1);
275 /* Copy the text from the buffer to the returned string */
276 if (end <= buf->gapStart) {
277 memcpy(text, &buf->buf[start], length);
278 } else if (start >= buf->gapStart) {
279 memcpy(text, &buf->buf[start+(buf->gapEnd-buf->gapStart)], length);
280 } else {
281 part1Length = buf->gapStart - start;
282 memcpy(text, &buf->buf[start], part1Length);
283 memcpy(&text[part1Length], &buf->buf[buf->gapEnd], length-part1Length);
285 text[length] = '\0';
286 return text;
290 ** Return the character at buffer position "pos". Positions start at 0.
292 char BufGetCharacter(textBuffer *buf, int pos)
294 if (pos < 0 || pos >= buf->length)
295 return '\0';
296 if (pos < buf->gapStart)
297 return buf->buf[pos];
298 else
299 return buf->buf[pos + buf->gapEnd-buf->gapStart];
303 ** Insert null-terminated string "text" at position "pos" in "buf"
305 void BufInsert(textBuffer *buf, int pos, const char *text)
307 int nInserted;
309 /* if pos is not contiguous to existing text, make it */
310 if (pos > buf->length) pos = buf->length;
311 if (pos < 0 ) pos = 0;
313 /* Even if nothing is deleted, we must call these callbacks */
314 callPreDeleteCBs(buf, pos, 0);
316 /* insert and redisplay */
317 nInserted = insert(buf, pos, text);
318 buf->cursorPosHint = pos + nInserted;
319 callModifyCBs(buf, pos, 0, nInserted, 0, NULL);
323 ** Delete the characters between "start" and "end", and insert the
324 ** null-terminated string "text" in their place in in "buf"
326 void BufReplace(textBuffer *buf, int start, int end, const char *text)
328 char *deletedText;
329 int nInserted = strlen(text);
331 callPreDeleteCBs(buf, start, end-start);
332 deletedText = BufGetRange(buf, start, end);
333 delete(buf, start, end);
334 insert(buf, start, text);
335 buf->cursorPosHint = start + nInserted;
336 callModifyCBs(buf, start, end-start, nInserted, 0, deletedText);
337 XtFree(deletedText);
340 void BufRemove(textBuffer *buf, int start, int end)
342 char *deletedText;
344 /* Make sure the arguments make sense */
345 if (start > end) {
346 int temp = start;
347 start = end;
348 end = temp;
350 if (start > buf->length) start = buf->length;
351 if (start < 0) start = 0;
352 if (end > buf->length) end = buf->length;
353 if (end < 0) end = 0;
355 callPreDeleteCBs(buf, start, end-start);
356 /* Remove and redisplay */
357 deletedText = BufGetRange(buf, start, end);
358 delete(buf, start, end);
359 buf->cursorPosHint = start;
360 callModifyCBs(buf, start, end-start, 0, 0, deletedText);
361 XtFree(deletedText);
364 void BufCopyFromBuf(textBuffer *fromBuf, textBuffer *toBuf, int fromStart,
365 int fromEnd, int toPos)
367 int length = fromEnd - fromStart;
368 int part1Length;
370 /* Prepare the buffer to receive the new text. If the new text fits in
371 the current buffer, just move the gap (if necessary) to where
372 the text should be inserted. If the new text is too large, reallocate
373 the buffer with a gap large enough to accomodate the new text and a
374 gap of PREFERRED_GAP_SIZE */
375 if (length > toBuf->gapEnd - toBuf->gapStart)
376 reallocateBuf(toBuf, toPos, length + PREFERRED_GAP_SIZE);
377 else if (toPos != toBuf->gapStart)
378 moveGap(toBuf, toPos);
380 /* Insert the new text (toPos now corresponds to the start of the gap) */
381 if (fromEnd <= fromBuf->gapStart) {
382 memcpy(&toBuf->buf[toPos], &fromBuf->buf[fromStart], length);
383 } else if (fromStart >= fromBuf->gapStart) {
384 memcpy(&toBuf->buf[toPos],
385 &fromBuf->buf[fromStart+(fromBuf->gapEnd-fromBuf->gapStart)],
386 length);
387 } else {
388 part1Length = fromBuf->gapStart - fromStart;
389 memcpy(&toBuf->buf[toPos], &fromBuf->buf[fromStart], part1Length);
390 memcpy(&toBuf->buf[toPos+part1Length], &fromBuf->buf[fromBuf->gapEnd],
391 length-part1Length);
393 toBuf->gapStart += length;
394 toBuf->length += length;
395 updateSelections(toBuf, toPos, 0, length);
399 ** Insert "text" columnwise into buffer starting at displayed character
400 ** position "column" on the line beginning at "startPos". Opens a rectangular
401 ** space the width and height of "text", by moving all text to the right of
402 ** "column" right. If charsInserted and charsDeleted are not NULL, the
403 ** number of characters inserted and deleted in the operation (beginning
404 ** at startPos) are returned in these arguments
406 void BufInsertCol(textBuffer *buf, int column, int startPos, const char *text,
407 int *charsInserted, int *charsDeleted)
409 int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
410 char *deletedText;
412 nLines = countLines(text);
413 lineStartPos = BufStartOfLine(buf, startPos);
414 nDeleted = BufEndOfLine(buf, BufCountForwardNLines(buf, startPos, nLines)) -
415 lineStartPos;
416 callPreDeleteCBs(buf, lineStartPos, nDeleted);
417 deletedText = BufGetRange(buf, lineStartPos, lineStartPos + nDeleted);
418 insertCol(buf, column, lineStartPos, text, &insertDeleted, &nInserted,
419 &buf->cursorPosHint);
420 if (nDeleted != insertDeleted)
421 fprintf(stderr, "NEdit internal consistency check ins1 failed");
422 callModifyCBs(buf, lineStartPos, nDeleted, nInserted, 0, deletedText);
423 XtFree(deletedText);
424 if (charsInserted != NULL)
425 *charsInserted = nInserted;
426 if (charsDeleted != NULL)
427 *charsDeleted = nDeleted;
431 ** Overlay "text" between displayed character positions "rectStart" and
432 ** "rectEnd" on the line beginning at "startPos". If charsInserted and
433 ** charsDeleted are not NULL, the number of characters inserted and deleted
434 ** in the operation (beginning at startPos) are returned in these arguments.
435 ** If rectEnd equals -1, the width of the inserted text is measured first.
437 void BufOverlayRect(textBuffer *buf, int startPos, int rectStart,
438 int rectEnd, const char *text, int *charsInserted, int *charsDeleted)
440 int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
441 char *deletedText;
443 nLines = countLines(text);
444 lineStartPos = BufStartOfLine(buf, startPos);
445 if(rectEnd == -1)
446 rectEnd = rectStart + textWidth(text, buf->tabDist, buf->nullSubsChar);
447 lineStartPos = BufStartOfLine(buf, startPos);
448 nDeleted = BufEndOfLine(buf, BufCountForwardNLines(buf, startPos, nLines)) -
449 lineStartPos;
450 callPreDeleteCBs(buf, lineStartPos, nDeleted);
451 deletedText = BufGetRange(buf, lineStartPos, lineStartPos + nDeleted);
452 overlayRect(buf, lineStartPos, rectStart, rectEnd, text, &insertDeleted,
453 &nInserted, &buf->cursorPosHint);
454 if (nDeleted != insertDeleted)
455 fprintf(stderr, "NEdit internal consistency check ovly1 failed");
456 callModifyCBs(buf, lineStartPos, nDeleted, nInserted, 0, deletedText);
457 XtFree(deletedText);
458 if (charsInserted != NULL)
459 *charsInserted = nInserted;
460 if (charsDeleted != NULL)
461 *charsDeleted = nDeleted;
465 ** Replace a rectangular area in buf, given by "start", "end", "rectStart",
466 ** and "rectEnd", with "text". If "text" is vertically longer than the
467 ** rectangle, add extra lines to make room for it.
469 void BufReplaceRect(textBuffer *buf, int start, int end, int rectStart,
470 int rectEnd, const char *text)
472 char *deletedText;
473 char *insText=NULL;
474 int i, nInsertedLines, nDeletedLines, insLen, hint;
475 int insertDeleted, insertInserted, deleteInserted;
476 int linesPadded = 0;
478 /* Make sure start and end refer to complete lines, since the
479 columnar delete and insert operations will replace whole lines */
480 start = BufStartOfLine(buf, start);
481 end = BufEndOfLine(buf, end);
483 callPreDeleteCBs(buf, start, end-start);
485 /* If more lines will be deleted than inserted, pad the inserted text
486 with newlines to make it as long as the number of deleted lines. This
487 will indent all of the text to the right of the rectangle to the same
488 column. If more lines will be inserted than deleted, insert extra
489 lines in the buffer at the end of the rectangle to make room for the
490 additional lines in "text" */
491 nInsertedLines = countLines(text);
492 nDeletedLines = BufCountLines(buf, start, end);
493 if (nInsertedLines < nDeletedLines) {
494 char *insPtr;
496 insLen = strlen(text);
497 insText = XtMalloc(insLen + nDeletedLines - nInsertedLines + 1);
498 strcpy(insText, text);
499 insPtr = insText + insLen;
500 for (i=0; i<nDeletedLines-nInsertedLines; i++)
501 *insPtr++ = '\n';
502 *insPtr = '\0';
503 } else if (nDeletedLines < nInsertedLines) {
504 linesPadded = nInsertedLines-nDeletedLines;
505 for (i=0; i<linesPadded; i++)
506 insert(buf, end, "\n");
507 } else /* nDeletedLines == nInsertedLines */ {
510 /* Save a copy of the text which will be modified for the modify CBs */
511 deletedText = BufGetRange(buf, start, end);
513 /* Delete then insert */
514 deleteRect(buf, start, end, rectStart, rectEnd, &deleteInserted, &hint);
515 if (insText) {
516 insertCol(buf, rectStart, start, insText, &insertDeleted, &insertInserted,
517 &buf->cursorPosHint);
518 XtFree(insText);
520 else
521 insertCol(buf, rectStart, start, text, &insertDeleted, &insertInserted,
522 &buf->cursorPosHint);
524 /* Figure out how many chars were inserted and call modify callbacks */
525 if (insertDeleted != deleteInserted + linesPadded)
526 fprintf(stderr, "NEdit: internal consistency check repl1 failed\n");
527 callModifyCBs(buf, start, end-start, insertInserted, 0, deletedText);
528 XtFree(deletedText);
532 ** Remove a rectangular swath of characters between character positions start
533 ** and end and horizontal displayed-character offsets rectStart and rectEnd.
535 void BufRemoveRect(textBuffer *buf, int start, int end, int rectStart,
536 int rectEnd)
538 char *deletedText;
539 int nInserted;
541 start = BufStartOfLine(buf, start);
542 end = BufEndOfLine(buf, end);
543 callPreDeleteCBs(buf, start, end-start);
544 deletedText = BufGetRange(buf, start, end);
545 deleteRect(buf, start, end, rectStart, rectEnd, &nInserted,
546 &buf->cursorPosHint);
547 callModifyCBs(buf, start, end-start, nInserted, 0, deletedText);
548 XtFree(deletedText);
552 ** Clear a rectangular "hole" out of the buffer between character positions
553 ** start and end and horizontal displayed-character offsets rectStart and
554 ** rectEnd.
556 void BufClearRect(textBuffer *buf, int start, int end, int rectStart,
557 int rectEnd)
559 int i, nLines;
560 char *newlineString;
562 nLines = BufCountLines(buf, start, end);
563 newlineString = XtMalloc(nLines+1);
564 for (i=0; i<nLines; i++)
565 newlineString[i] = '\n';
566 newlineString[i] = '\0';
567 BufOverlayRect(buf, start, rectStart, rectEnd, newlineString,
568 NULL, NULL);
569 XtFree(newlineString);
572 char *BufGetTextInRect(textBuffer *buf, int start, int end,
573 int rectStart, int rectEnd)
575 int lineStart, selLeft, selRight, len;
576 char *textOut, *textIn, *outPtr, *retabbedStr;
578 start = BufStartOfLine(buf, start);
579 end = BufEndOfLine(buf, end);
580 textOut = XtMalloc((end - start) + 1);
581 lineStart = start;
582 outPtr = textOut;
583 while (lineStart <= end) {
584 findRectSelBoundariesForCopy(buf, lineStart, rectStart, rectEnd,
585 &selLeft, &selRight);
586 textIn = BufGetRange(buf, selLeft, selRight);
587 len = selRight - selLeft;
588 memcpy(outPtr, textIn, len);
589 XtFree(textIn);
590 outPtr += len;
591 lineStart = BufEndOfLine(buf, selRight) + 1;
592 *outPtr++ = '\n';
594 if (outPtr != textOut)
595 outPtr--; /* don't leave trailing newline */
596 *outPtr = '\0';
598 /* If necessary, realign the tabs in the selection as if the text were
599 positioned at the left margin */
600 retabbedStr = realignTabs(textOut, rectStart, 0, buf->tabDist,
601 buf->useTabs, buf->nullSubsChar, &len);
602 XtFree(textOut);
603 return retabbedStr;
607 ** Get the hardware tab distance used by all displays for this buffer,
608 ** and used in computing offsets for rectangular selection operations.
610 int BufGetTabDistance(textBuffer *buf)
612 return buf->tabDist;
616 ** Set the hardware tab distance used by all displays for this buffer,
617 ** and used in computing offsets for rectangular selection operations.
619 void BufSetTabDistance(textBuffer *buf, int tabDist)
621 char *deletedText;
623 /* First call the pre-delete callbacks with the previous tab setting
624 still active. */
625 callPreDeleteCBs(buf, 0, buf->length);
627 /* Change the tab setting */
628 buf->tabDist = tabDist;
630 /* Force any display routines to redisplay everything (unfortunately,
631 this means copying the whole buffer contents to provide "deletedText" */
632 deletedText = BufGetAll(buf);
633 callModifyCBs(buf, 0, buf->length, buf->length, 0, deletedText);
634 XtFree(deletedText);
637 void BufCheckDisplay(textBuffer *buf, int start, int end)
639 /* just to make sure colors in the selected region are up to date */
640 callModifyCBs(buf, start, 0, 0, end-start, NULL);
643 void BufSelect(textBuffer *buf, int start, int end)
645 selection oldSelection = buf->primary;
647 setSelection(&buf->primary, start, end);
648 redisplaySelection(buf, &oldSelection, &buf->primary);
651 void BufUnselect(textBuffer *buf)
653 selection oldSelection = buf->primary;
655 buf->primary.selected = False;
656 buf->primary.zeroWidth = False;
657 redisplaySelection(buf, &oldSelection, &buf->primary);
660 void BufRectSelect(textBuffer *buf, int start, int end, int rectStart,
661 int rectEnd)
663 selection oldSelection = buf->primary;
665 setRectSelect(&buf->primary, start, end, rectStart, rectEnd);
666 redisplaySelection(buf, &oldSelection, &buf->primary);
669 int BufGetSelectionPos(textBuffer *buf, int *start, int *end,
670 int *isRect, int *rectStart, int *rectEnd)
672 return getSelectionPos(&buf->primary, start, end, isRect, rectStart,
673 rectEnd);
676 /* Same as above, but also returns TRUE for empty selections */
677 int BufGetEmptySelectionPos(textBuffer *buf, int *start, int *end,
678 int *isRect, int *rectStart, int *rectEnd)
680 return getSelectionPos(&buf->primary, start, end, isRect, rectStart,
681 rectEnd) || buf->primary.zeroWidth;
684 char *BufGetSelectionText(textBuffer *buf)
686 return getSelectionText(buf, &buf->primary);
689 void BufRemoveSelected(textBuffer *buf)
691 removeSelected(buf, &buf->primary);
694 void BufReplaceSelected(textBuffer *buf, const char *text)
696 replaceSelected(buf, &buf->primary, text);
699 void BufSecondarySelect(textBuffer *buf, int start, int end)
701 selection oldSelection = buf->secondary;
703 setSelection(&buf->secondary, start, end);
704 redisplaySelection(buf, &oldSelection, &buf->secondary);
707 void BufSecondaryUnselect(textBuffer *buf)
709 selection oldSelection = buf->secondary;
711 buf->secondary.selected = False;
712 buf->secondary.zeroWidth = False;
713 redisplaySelection(buf, &oldSelection, &buf->secondary);
716 void BufSecRectSelect(textBuffer *buf, int start, int end,
717 int rectStart, int rectEnd)
719 selection oldSelection = buf->secondary;
721 setRectSelect(&buf->secondary, start, end, rectStart, rectEnd);
722 redisplaySelection(buf, &oldSelection, &buf->secondary);
725 int BufGetSecSelectPos(textBuffer *buf, int *start, int *end,
726 int *isRect, int *rectStart, int *rectEnd)
728 return getSelectionPos(&buf->secondary, start, end, isRect, rectStart,
729 rectEnd);
732 char *BufGetSecSelectText(textBuffer *buf)
734 return getSelectionText(buf, &buf->secondary);
737 void BufRemoveSecSelect(textBuffer *buf)
739 removeSelected(buf, &buf->secondary);
742 void BufReplaceSecSelect(textBuffer *buf, const char *text)
744 replaceSelected(buf, &buf->secondary, text);
747 void BufHighlight(textBuffer *buf, int start, int end)
749 selection oldSelection = buf->highlight;
751 setSelection(&buf->highlight, start, end);
752 redisplaySelection(buf, &oldSelection, &buf->highlight);
755 void BufUnhighlight(textBuffer *buf)
757 selection oldSelection = buf->highlight;
759 buf->highlight.selected = False;
760 buf->highlight.zeroWidth = False;
761 redisplaySelection(buf, &oldSelection, &buf->highlight);
764 void BufRectHighlight(textBuffer *buf, int start, int end,
765 int rectStart, int rectEnd)
767 selection oldSelection = buf->highlight;
769 setRectSelect(&buf->highlight, start, end, rectStart, rectEnd);
770 redisplaySelection(buf, &oldSelection, &buf->highlight);
773 int BufGetHighlightPos(textBuffer *buf, int *start, int *end,
774 int *isRect, int *rectStart, int *rectEnd)
776 return getSelectionPos(&buf->highlight, start, end, isRect, rectStart,
777 rectEnd);
780 char *BufGetHighlightText(textBuffer *buf)
782 return getSelectionText(buf, &buf->highlight);
786 ** Add a callback routine to be called when the buffer is modified
788 void BufAddModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
789 void *cbArg)
791 bufModifyCallbackProc *newModifyProcs;
792 void **newCBArgs;
793 int i;
795 newModifyProcs = (bufModifyCallbackProc *)
796 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs+1));
797 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs+1));
798 for (i=0; i<buf->nModifyProcs; i++) {
799 newModifyProcs[i] = buf->modifyProcs[i];
800 newCBArgs[i] = buf->cbArgs[i];
802 if (buf->nModifyProcs != 0) {
803 XtFree((char *)buf->modifyProcs);
804 XtFree((char *)buf->cbArgs);
806 newModifyProcs[buf->nModifyProcs] = bufModifiedCB;
807 newCBArgs[buf->nModifyProcs] = cbArg;
808 buf->nModifyProcs++;
809 buf->modifyProcs = newModifyProcs;
810 buf->cbArgs = newCBArgs;
814 ** Similar to the above, but makes sure that the callback is called before
815 ** normal priority callbacks.
817 void BufAddHighPriorityModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
818 void *cbArg)
820 bufModifyCallbackProc *newModifyProcs;
821 void **newCBArgs;
822 int i;
824 newModifyProcs = (bufModifyCallbackProc *)
825 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs+1));
826 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs+1));
827 for (i=0; i<buf->nModifyProcs; i++) {
828 newModifyProcs[i+1] = buf->modifyProcs[i];
829 newCBArgs[i+1] = buf->cbArgs[i];
831 if (buf->nModifyProcs != 0) {
832 XtFree((char *)buf->modifyProcs);
833 XtFree((char *)buf->cbArgs);
835 newModifyProcs[0] = bufModifiedCB;
836 newCBArgs[0] = cbArg;
837 buf->nModifyProcs++;
838 buf->modifyProcs = newModifyProcs;
839 buf->cbArgs = newCBArgs;
842 void BufRemoveModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
843 void *cbArg)
845 int i, toRemove = -1;
846 bufModifyCallbackProc *newModifyProcs;
847 void **newCBArgs;
849 /* find the matching callback to remove */
850 for (i=0; i<buf->nModifyProcs; i++) {
851 if (buf->modifyProcs[i] == bufModifiedCB && buf->cbArgs[i] == cbArg) {
852 toRemove = i;
853 break;
856 if (toRemove == -1) {
857 fprintf(stderr, "NEdit Internal Error: Can't find modify CB to remove\n");
858 return;
861 /* Allocate new lists for remaining callback procs and args (if
862 any are left) */
863 buf->nModifyProcs--;
864 if (buf->nModifyProcs == 0) {
865 buf->nModifyProcs = 0;
866 XtFree((char *)buf->modifyProcs);
867 buf->modifyProcs = NULL;
868 XtFree((char *)buf->cbArgs);
869 buf->cbArgs = NULL;
870 return;
872 newModifyProcs = (bufModifyCallbackProc *)
873 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs));
874 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs));
876 /* copy out the remaining members and free the old lists */
877 for (i=0; i<toRemove; i++) {
878 newModifyProcs[i] = buf->modifyProcs[i];
879 newCBArgs[i] = buf->cbArgs[i];
881 for (; i<buf->nModifyProcs; i++) {
882 newModifyProcs[i] = buf->modifyProcs[i+1];
883 newCBArgs[i] = buf->cbArgs[i+1];
885 XtFree((char *)buf->modifyProcs);
886 XtFree((char *)buf->cbArgs);
887 buf->modifyProcs = newModifyProcs;
888 buf->cbArgs = newCBArgs;
892 ** Add a callback routine to be called before text is deleted from the buffer.
894 void BufAddPreDeleteCB(textBuffer *buf, bufPreDeleteCallbackProc bufPreDeleteCB,
895 void *cbArg)
897 bufPreDeleteCallbackProc *newPreDeleteProcs;
898 void **newCBArgs;
899 int i;
901 newPreDeleteProcs = (bufPreDeleteCallbackProc *)
902 XtMalloc(sizeof(bufPreDeleteCallbackProc *) * (buf->nPreDeleteProcs+1));
903 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nPreDeleteProcs+1));
904 for (i=0; i<buf->nPreDeleteProcs; i++) {
905 newPreDeleteProcs[i] = buf->preDeleteProcs[i];
906 newCBArgs[i] = buf->preDeleteCbArgs[i];
908 if (buf->nPreDeleteProcs != 0) {
909 XtFree((char *)buf->preDeleteProcs);
910 XtFree((char *)buf->preDeleteCbArgs);
912 newPreDeleteProcs[buf->nPreDeleteProcs] = bufPreDeleteCB;
913 newCBArgs[buf->nPreDeleteProcs] = cbArg;
914 buf->nPreDeleteProcs++;
915 buf->preDeleteProcs = newPreDeleteProcs;
916 buf->preDeleteCbArgs = newCBArgs;
919 void BufRemovePreDeleteCB(textBuffer *buf, bufPreDeleteCallbackProc bufPreDeleteCB,
920 void *cbArg)
922 int i, toRemove = -1;
923 bufPreDeleteCallbackProc *newPreDeleteProcs;
924 void **newCBArgs;
926 /* find the matching callback to remove */
927 for (i=0; i<buf->nPreDeleteProcs; i++) {
928 if (buf->preDeleteProcs[i] == bufPreDeleteCB &&
929 buf->preDeleteCbArgs[i] == cbArg) {
930 toRemove = i;
931 break;
934 if (toRemove == -1) {
935 fprintf(stderr, "NEdit Internal Error: Can't find pre-delete CB to remove\n");
936 return;
939 /* Allocate new lists for remaining callback procs and args (if
940 any are left) */
941 buf->nPreDeleteProcs--;
942 if (buf->nPreDeleteProcs == 0) {
943 buf->nPreDeleteProcs = 0;
944 XtFree((char *)buf->preDeleteProcs);
945 buf->preDeleteProcs = NULL;
946 XtFree((char *)buf->preDeleteCbArgs);
947 buf->preDeleteCbArgs = NULL;
948 return;
950 newPreDeleteProcs = (bufPreDeleteCallbackProc *)
951 XtMalloc(sizeof(bufPreDeleteCallbackProc *) * (buf->nPreDeleteProcs));
952 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nPreDeleteProcs));
954 /* copy out the remaining members and free the old lists */
955 for (i=0; i<toRemove; i++) {
956 newPreDeleteProcs[i] = buf->preDeleteProcs[i];
957 newCBArgs[i] = buf->preDeleteCbArgs[i];
959 for (; i<buf->nPreDeleteProcs; i++) {
960 newPreDeleteProcs[i] = buf->preDeleteProcs[i+1];
961 newCBArgs[i] = buf->preDeleteCbArgs[i+1];
963 XtFree((char *)buf->preDeleteProcs);
964 XtFree((char *)buf->preDeleteCbArgs);
965 buf->preDeleteProcs = newPreDeleteProcs;
966 buf->preDeleteCbArgs = newCBArgs;
970 ** Return the text from the entire line containing position "pos"
972 char *BufGetLineText(textBuffer *buf, int pos)
974 return BufGetRange(buf, BufStartOfLine(buf, pos), BufEndOfLine(buf, pos));
978 ** Find the position of the start of the line containing position "pos"
980 int BufStartOfLine(textBuffer *buf, int pos)
982 int startPos;
984 if (!searchBackward(buf, pos, '\n', &startPos))
985 return 0;
986 return startPos + 1;
991 ** Find the position of the end of the line containing position "pos"
992 ** (which is either a pointer to the newline character ending the line,
993 ** or a pointer to one character beyond the end of the buffer)
995 int BufEndOfLine(textBuffer *buf, int pos)
997 int endPos;
999 if (!searchForward(buf, pos, '\n', &endPos))
1000 endPos = buf->length;
1001 return endPos;
1005 ** Get a character from the text buffer expanded into it's screen
1006 ** representation (which may be several characters for a tab or a
1007 ** control code). Returns the number of characters written to "outStr".
1008 ** "indent" is the number of characters from the start of the line
1009 ** for figuring tabs. Output string is guranteed to be shorter or
1010 ** equal in length to MAX_EXP_CHAR_LEN
1012 int BufGetExpandedChar(textBuffer *buf, int pos, int indent, char *outStr)
1014 return BufExpandCharacter(BufGetCharacter(buf, pos), indent, outStr,
1015 buf->tabDist, buf->nullSubsChar);
1019 ** Expand a single character from the text buffer into it's screen
1020 ** representation (which may be several characters for a tab or a
1021 ** control code). Returns the number of characters added to "outStr".
1022 ** "indent" is the number of characters from the start of the line
1023 ** for figuring tabs. Output string is guranteed to be shorter or
1024 ** equal in length to MAX_EXP_CHAR_LEN
1026 int BufExpandCharacter(char c, int indent, char *outStr, int tabDist,
1027 char nullSubsChar)
1029 int i, nSpaces;
1031 /* Convert tabs to spaces */
1032 if (c == '\t') {
1033 nSpaces = tabDist - (indent % tabDist);
1034 for (i=0; i<nSpaces; i++)
1035 outStr[i] = ' ';
1036 return nSpaces;
1039 /* Convert ASCII (and EBCDIC in the __MVS__ (OS/390) case) control
1040 codes to readable character sequences */
1041 if (c == nullSubsChar) {
1042 sprintf(outStr, "<nul>");
1043 return 5;
1045 #ifdef __MVS__
1046 if (((unsigned char)c) <= 63) {
1047 sprintf(outStr, "<%s>", ControlCodeTable[(unsigned char)c]);
1048 return strlen(outStr);
1050 #else
1051 if (((unsigned char)c) <= 31) {
1052 sprintf(outStr, "<%s>", ControlCodeTable[(unsigned char)c]);
1053 return strlen(outStr);
1054 } else if (c == 127) {
1055 sprintf(outStr, "<del>");
1056 return 5;
1058 #endif
1060 /* Otherwise, just return the character */
1061 *outStr = c;
1062 return 1;
1066 ** Return the length in displayed characters of character "c" expanded
1067 ** for display (as discussed above in BufGetExpandedChar). If the
1068 ** buffer for which the character width is being measured is doing null
1069 ** substitution, nullSubsChar should be passed as that character (or nul
1070 ** to ignore).
1072 int BufCharWidth(char c, int indent, int tabDist, char nullSubsChar)
1074 /* Note, this code must parallel that in BufExpandCharacter */
1075 if (c == nullSubsChar)
1076 return 5;
1077 else if (c == '\t')
1078 return tabDist - (indent % tabDist);
1079 else if (((unsigned char)c) <= 31)
1080 return strlen(ControlCodeTable[(unsigned char)c]) + 2;
1081 else if (c == 127)
1082 return 5;
1083 return 1;
1087 ** Count the number of displayed characters between buffer position
1088 ** "lineStartPos" and "targetPos". (displayed characters are the characters
1089 ** shown on the screen to represent characters in the buffer, where tabs and
1090 ** control characters are expanded)
1092 int BufCountDispChars(textBuffer *buf, int lineStartPos, int targetPos)
1094 int pos, charCount = 0;
1095 char expandedChar[MAX_EXP_CHAR_LEN];
1097 pos = lineStartPos;
1098 while (pos < targetPos && pos < buf->length)
1099 charCount += BufGetExpandedChar(buf, pos++, charCount, expandedChar);
1100 return charCount;
1104 ** Count forward from buffer position "startPos" in displayed characters
1105 ** (displayed characters are the characters shown on the screen to represent
1106 ** characters in the buffer, where tabs and control characters are expanded)
1108 int BufCountForwardDispChars(textBuffer *buf, int lineStartPos, int nChars)
1110 int pos, charCount = 0;
1111 char c;
1113 pos = lineStartPos;
1114 while (charCount < nChars && pos < buf->length) {
1115 c = BufGetCharacter(buf, pos);
1116 if (c == '\n')
1117 return pos;
1118 charCount += BufCharWidth(c, charCount, buf->tabDist,buf->nullSubsChar);
1119 pos++;
1121 return pos;
1125 ** Count the number of newlines between startPos and endPos in buffer "buf".
1126 ** The character at position "endPos" is not counted.
1128 int BufCountLines(textBuffer *buf, int startPos, int endPos)
1130 int pos, gapLen = buf->gapEnd - buf->gapStart;
1131 int lineCount = 0;
1133 pos = startPos;
1134 while (pos < buf->gapStart) {
1135 if (pos == endPos)
1136 return lineCount;
1137 if (buf->buf[pos++] == '\n')
1138 lineCount++;
1140 while (pos < buf->length) {
1141 if (pos == endPos)
1142 return lineCount;
1143 if (buf->buf[pos++ + gapLen] == '\n')
1144 lineCount++;
1146 return lineCount;
1150 ** Find the first character of the line "nLines" forward from "startPos"
1151 ** in "buf" and return its position
1153 int BufCountForwardNLines(textBuffer *buf, int startPos, int nLines)
1155 int pos, gapLen = buf->gapEnd - buf->gapStart;
1156 int lineCount = 0;
1158 if (nLines == 0)
1159 return startPos;
1161 pos = startPos;
1162 while (pos < buf->gapStart) {
1163 if (buf->buf[pos++] == '\n') {
1164 lineCount++;
1165 if (lineCount == nLines)
1166 return pos;
1169 while (pos < buf->length) {
1170 if (buf->buf[pos++ + gapLen] == '\n') {
1171 lineCount++;
1172 if (lineCount >= nLines)
1173 return pos;
1176 return pos;
1180 ** Find the position of the first character of the line "nLines" backwards
1181 ** from "startPos" (not counting the character pointed to by "startpos" if
1182 ** that is a newline) in "buf". nLines == 0 means find the beginning of
1183 ** the line
1185 int BufCountBackwardNLines(textBuffer *buf, int startPos, int nLines)
1187 int pos, gapLen = buf->gapEnd - buf->gapStart;
1188 int lineCount = -1;
1190 pos = startPos - 1;
1191 if (pos <= 0)
1192 return 0;
1194 while (pos >= buf->gapStart) {
1195 if (buf->buf[pos + gapLen] == '\n') {
1196 if (++lineCount >= nLines)
1197 return pos + 1;
1199 pos--;
1201 while (pos >= 0) {
1202 if (buf->buf[pos] == '\n') {
1203 if (++lineCount >= nLines)
1204 return pos + 1;
1206 pos--;
1208 return 0;
1212 ** Search forwards in buffer "buf" for characters in "searchChars", starting
1213 ** with the character "startPos", and returning the result in "foundPos"
1214 ** returns True if found, False if not.
1216 int BufSearchForward(textBuffer *buf, int startPos, const char *searchChars,
1217 int *foundPos)
1219 int pos, gapLen = buf->gapEnd - buf->gapStart;
1220 const char *c;
1222 pos = startPos;
1223 while (pos < buf->gapStart) {
1224 for (c=searchChars; *c!='\0'; c++) {
1225 if (buf->buf[pos] == *c) {
1226 *foundPos = pos;
1227 return True;
1230 pos++;
1232 while (pos < buf->length) {
1233 for (c=searchChars; *c!='\0'; c++) {
1234 if (buf->buf[pos + gapLen] == *c) {
1235 *foundPos = pos;
1236 return True;
1239 pos++;
1241 *foundPos = buf->length;
1242 return False;
1246 ** Search backwards in buffer "buf" for characters in "searchChars", starting
1247 ** with the character BEFORE "startPos", returning the result in "foundPos"
1248 ** returns True if found, False if not.
1250 int BufSearchBackward(textBuffer *buf, int startPos, const char *searchChars,
1251 int *foundPos)
1253 int pos, gapLen = buf->gapEnd - buf->gapStart;
1254 const char *c;
1256 if (startPos == 0) {
1257 *foundPos = 0;
1258 return False;
1260 pos = startPos == 0 ? 0 : startPos - 1;
1261 while (pos >= buf->gapStart) {
1262 for (c=searchChars; *c!='\0'; c++) {
1263 if (buf->buf[pos + gapLen] == *c) {
1264 *foundPos = pos;
1265 return True;
1268 pos--;
1270 while (pos >= 0) {
1271 for (c=searchChars; *c!='\0'; c++) {
1272 if (buf->buf[pos] == *c) {
1273 *foundPos = pos;
1274 return True;
1277 pos--;
1279 *foundPos = 0;
1280 return False;
1284 ** A horrible design flaw in NEdit (from the very start, before we knew that
1285 ** NEdit would become so popular), is that it uses C NULL terminated strings
1286 ** to hold text. This means editing text containing NUL characters is not
1287 ** possible without special consideration. Here is the special consideration.
1288 ** The routines below maintain a special substitution-character which stands
1289 ** in for a null, and translates strings an buffers back and forth from/to
1290 ** the substituted form, figure out what to substitute, and figure out
1291 ** when we're in over our heads and no translation is possible.
1295 ** The primary routine for integrating new text into a text buffer with
1296 ** substitution of another character for ascii nuls. This substitutes null
1297 ** characters in the string in preparation for being copied or replaced
1298 ** into the buffer, and if neccessary, adjusts the buffer as well, in the
1299 ** event that the string contains the character it is currently using for
1300 ** substitution. Returns False, if substitution is no longer possible
1301 ** because all non-printable characters are already in use.
1303 int BufSubstituteNullChars(char *string, int length, textBuffer *buf)
1305 char histogram[256];
1307 /* Find out what characters the string contains */
1308 histogramCharacters(string, length, histogram, True);
1310 /* Does the string contain the null-substitute character? If so, re-
1311 histogram the buffer text to find a character which is ok in both the
1312 string and the buffer, and change the buffer's null-substitution
1313 character. If none can be found, give up and return False */
1314 if (histogram[(unsigned char)buf->nullSubsChar] != 0) {
1315 char *bufString, newSubsChar;
1316 bufString = BufGetAll(buf);
1317 histogramCharacters(bufString, buf->length, histogram, False);
1318 newSubsChar = chooseNullSubsChar(histogram);
1319 if (newSubsChar == '\0') {
1320 XtFree(bufString);
1321 return False;
1323 subsChars(bufString, buf->length, buf->nullSubsChar, newSubsChar);
1324 delete(buf, 0, buf->length);
1325 insert(buf, 0, bufString);
1326 XtFree(bufString);
1327 buf->nullSubsChar = newSubsChar;
1330 /* If the string contains null characters, substitute them with the
1331 buffer's null substitution character */
1332 if (histogram[0] != 0)
1333 subsChars(string, length, '\0', buf->nullSubsChar);
1334 return True;
1338 ** Convert strings obtained from buffers which contain null characters, which
1339 ** have been substituted for by a special substitution character, back to
1340 ** a null-containing string. There is no time penalty for calling this
1341 ** routine if no substitution has been done.
1343 void BufUnsubstituteNullChars(char *string, textBuffer *buf)
1345 register char *c, subsChar = buf->nullSubsChar;
1347 if (subsChar == '\0')
1348 return;
1349 for (c=string; *c != '\0'; c++)
1350 if (*c == subsChar)
1351 *c = '\0';
1355 ** Compares len Bytes contained in buf starting at Position pos with
1356 ** the contens of cmpText. Returns 0 if there are no differences,
1357 ** != 0 otherwise.
1360 int BufCmp(textBuffer * buf, int pos, int len, const char *cmpText)
1362 int posEnd;
1363 int part1Length;
1364 int result;
1366 posEnd = pos + len;
1367 if (posEnd > buf->length) {
1368 return (1);
1370 if (pos < 0) {
1371 return (-1);
1374 if (posEnd <= buf->gapStart) {
1375 return (strncmp(&(buf->buf[pos]), cmpText, len));
1376 } else if (pos >= buf->gapStart) {
1377 return (strncmp (&buf->buf[pos + (buf->gapEnd - buf->gapStart)],
1378 cmpText, len));
1379 } else {
1380 part1Length = buf->gapStart - pos;
1381 result = strncmp(&buf->buf[pos], cmpText, part1Length);
1382 if (result) {
1383 return (result);
1385 return (strncmp(&buf->buf[buf->gapEnd], &cmpText[part1Length],
1386 len - part1Length));
1391 ** Create a pseudo-histogram of the characters in a string (don't actually
1392 ** count, because we don't want overflow, just mark the character's presence
1393 ** with a 1). If init is true, initialize the histogram before acumulating.
1394 ** if not, add the new data to an existing histogram.
1396 static void histogramCharacters(const char *string, int length, char hist[256],
1397 int init)
1399 int i;
1400 const char *c;
1402 if (init)
1403 for (i=0; i<256; i++)
1404 hist[i] = 0;
1405 for (c=string; c < &string[length]; c++)
1406 hist[*((unsigned char *)c)] |= 1;
1410 ** Substitute fromChar with toChar in string.
1412 static void subsChars(char *string, int length, char fromChar, char toChar)
1414 char *c;
1416 for (c=string; c < &string[length]; c++)
1417 if (*c == fromChar) *c = toChar;
1421 ** Search through ascii control characters in histogram in order of least
1422 ** likelihood of use, find an unused character to use as a stand-in for a
1423 ** null. If the character set is full (no available characters outside of
1424 ** the printable set, return the null character.
1426 static char chooseNullSubsChar(char hist[256])
1428 #define N_REPLACEMENTS 25
1429 static char replacements[N_REPLACEMENTS] = {1,2,3,4,5,6,14,15,16,17,18,19,
1430 20,21,22,23,24,25,26,28,29,30,31,11,7};
1431 int i;
1432 for (i = 0; i < N_REPLACEMENTS; i++)
1433 if (hist[(unsigned char)replacements[i]] == 0)
1434 return replacements[i];
1435 return '\0';
1439 ** Internal (non-redisplaying) version of BufInsert. Returns the length of
1440 ** text inserted (this is just strlen(text), however this calculation can be
1441 ** expensive and the length will be required by any caller who will continue
1442 ** on to call redisplay). pos must be contiguous with the existing text in
1443 ** the buffer (i.e. not past the end).
1445 static int insert(textBuffer *buf, int pos, const char *text)
1447 int length = strlen(text);
1449 /* Prepare the buffer to receive the new text. If the new text fits in
1450 the current buffer, just move the gap (if necessary) to where
1451 the text should be inserted. If the new text is too large, reallocate
1452 the buffer with a gap large enough to accomodate the new text and a
1453 gap of PREFERRED_GAP_SIZE */
1454 if (length > buf->gapEnd - buf->gapStart)
1455 reallocateBuf(buf, pos, length + PREFERRED_GAP_SIZE);
1456 else if (pos != buf->gapStart)
1457 moveGap(buf, pos);
1459 /* Insert the new text (pos now corresponds to the start of the gap) */
1460 memcpy(&buf->buf[pos], text, length);
1461 buf->gapStart += length;
1462 buf->length += length;
1463 updateSelections(buf, pos, 0, length);
1465 return length;
1469 ** Internal (non-redisplaying) version of BufRemove. Removes the contents
1470 ** of the buffer between start and end (and moves the gap to the site of
1471 ** the delete).
1473 static void delete(textBuffer *buf, int start, int end)
1475 /* if the gap is not contiguous to the area to remove, move it there */
1476 if (start > buf->gapStart)
1477 moveGap(buf, start);
1478 else if (end < buf->gapStart)
1479 moveGap(buf, end);
1481 /* expand the gap to encompass the deleted characters */
1482 buf->gapEnd += end - buf->gapStart;
1483 buf->gapStart -= buf->gapStart - start;
1485 /* update the length */
1486 buf->length -= end - start;
1488 /* fix up any selections which might be affected by the change */
1489 updateSelections(buf, start, end-start, 0);
1493 ** Insert a column of text without calling the modify callbacks. Note that
1494 ** in some pathological cases, inserting can actually decrease the size of
1495 ** the buffer because of spaces being coalesced into tabs. "nDeleted" and
1496 ** "nInserted" return the number of characters deleted and inserted beginning
1497 ** at the start of the line containing "startPos". "endPos" returns buffer
1498 ** position of the lower left edge of the inserted column (as a hint for
1499 ** routines which need to set a cursor position).
1501 static void insertCol(textBuffer *buf, int column, int startPos,
1502 const char *insText, int *nDeleted, int *nInserted, int *endPos)
1504 int nLines, start, end, insWidth, lineStart, lineEnd;
1505 int expReplLen, expInsLen, len, endOffset;
1506 char *outStr, *outPtr, *line, *replText, *expText, *insLine;
1507 const char *insPtr;
1509 if (column < 0)
1510 column = 0;
1512 /* Allocate a buffer for the replacement string large enough to hold
1513 possibly expanded tabs in both the inserted text and the replaced
1514 area, as well as per line: 1) an additional 2*MAX_EXP_CHAR_LEN
1515 characters for padding where tabs and control characters cross the
1516 column of the selection, 2) up to "column" additional spaces per
1517 line for padding out to the position of "column", 3) padding up
1518 to the width of the inserted text if that must be padded to align
1519 the text beyond the inserted column. (Space for additional
1520 newlines if the inserted text extends beyond the end of the buffer
1521 is counted with the length of insText) */
1522 start = BufStartOfLine(buf, startPos);
1523 nLines = countLines(insText) + 1;
1524 insWidth = textWidth(insText, buf->tabDist, buf->nullSubsChar);
1525 end = BufEndOfLine(buf, BufCountForwardNLines(buf, start, nLines-1));
1526 replText = BufGetRange(buf, start, end);
1527 expText = expandTabs(replText, 0, buf->tabDist, buf->nullSubsChar,
1528 &expReplLen);
1529 XtFree(replText);
1530 XtFree(expText);
1531 expText = expandTabs(insText, 0, buf->tabDist, buf->nullSubsChar,
1532 &expInsLen);
1533 XtFree(expText);
1534 outStr = XtMalloc(expReplLen + expInsLen +
1535 nLines * (column + insWidth + MAX_EXP_CHAR_LEN) + 1);
1537 /* Loop over all lines in the buffer between start and end inserting
1538 text at column, splitting tabs and adding padding appropriately */
1539 outPtr = outStr;
1540 lineStart = start;
1541 insPtr = insText;
1542 while (True) {
1543 lineEnd = BufEndOfLine(buf, lineStart);
1544 line = BufGetRange(buf, lineStart, lineEnd);
1545 insLine = copyLine(insPtr, &len);
1546 insPtr += len;
1547 insertColInLine(line, insLine, column, insWidth, buf->tabDist,
1548 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1549 XtFree(line);
1550 XtFree(insLine);
1551 #if 0 /* Earlier comments claimed that trailing whitespace could multiply on
1552 the ends of lines, but insertColInLine looks like it should never
1553 add space unnecessarily, and this trimming interfered with
1554 paragraph filling, so lets see if it works without it. MWE */
1556 char *c;
1557 for (c=outPtr+len-1; c>outPtr && (*c == ' ' || *c == '\t'); c--)
1558 len--;
1560 #endif
1561 outPtr += len;
1562 *outPtr++ = '\n';
1563 lineStart = lineEnd < buf->length ? lineEnd + 1 : buf->length;
1564 if (*insPtr == '\0')
1565 break;
1566 insPtr++;
1568 if (outPtr != outStr)
1569 outPtr--; /* trim back off extra newline */
1570 *outPtr = '\0';
1572 /* replace the text between start and end with the new stuff */
1573 delete(buf, start, end);
1574 insert(buf, start, outStr);
1575 *nInserted = outPtr - outStr;
1576 *nDeleted = end - start;
1577 *endPos = start + (outPtr - outStr) - len + endOffset;
1578 XtFree(outStr);
1582 ** Delete a rectangle of text without calling the modify callbacks. Returns
1583 ** the number of characters replacing those between start and end. Note that
1584 ** in some pathological cases, deleting can actually increase the size of
1585 ** the buffer because of tab expansions. "endPos" returns the buffer position
1586 ** of the point in the last line where the text was removed (as a hint for
1587 ** routines which need to position the cursor after a delete operation)
1589 static void deleteRect(textBuffer *buf, int start, int end, int rectStart,
1590 int rectEnd, int *replaceLen, int *endPos)
1592 int nLines, lineStart, lineEnd, len, endOffset;
1593 char *outStr, *outPtr, *line, *text, *expText;
1595 /* allocate a buffer for the replacement string large enough to hold
1596 possibly expanded tabs as well as an additional MAX_EXP_CHAR_LEN * 2
1597 characters per line for padding where tabs and control characters cross
1598 the edges of the selection */
1599 start = BufStartOfLine(buf, start);
1600 end = BufEndOfLine(buf, end);
1601 nLines = BufCountLines(buf, start, end) + 1;
1602 text = BufGetRange(buf, start, end);
1603 expText = expandTabs(text, 0, buf->tabDist, buf->nullSubsChar, &len);
1604 XtFree(text);
1605 XtFree(expText);
1606 outStr = XtMalloc(len + nLines * MAX_EXP_CHAR_LEN * 2 + 1);
1608 /* loop over all lines in the buffer between start and end removing
1609 the text between rectStart and rectEnd and padding appropriately */
1610 lineStart = start;
1611 outPtr = outStr;
1612 while (lineStart <= buf->length && lineStart <= end) {
1613 lineEnd = BufEndOfLine(buf, lineStart);
1614 line = BufGetRange(buf, lineStart, lineEnd);
1615 deleteRectFromLine(line, rectStart, rectEnd, buf->tabDist,
1616 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1617 XtFree(line);
1618 outPtr += len;
1619 *outPtr++ = '\n';
1620 lineStart = lineEnd + 1;
1622 if (outPtr != outStr)
1623 outPtr--; /* trim back off extra newline */
1624 *outPtr = '\0';
1626 /* replace the text between start and end with the newly created string */
1627 delete(buf, start, end);
1628 insert(buf, start, outStr);
1629 *replaceLen = outPtr - outStr;
1630 *endPos = start + (outPtr - outStr) - len + endOffset;
1631 XtFree(outStr);
1635 ** Overlay a rectangular area of text without calling the modify callbacks.
1636 ** "nDeleted" and "nInserted" return the number of characters deleted and
1637 ** inserted beginning at the start of the line containing "startPos".
1638 ** "endPos" returns buffer position of the lower left edge of the inserted
1639 ** column (as a hint for routines which need to set a cursor position).
1641 static void overlayRect(textBuffer *buf, int startPos, int rectStart,
1642 int rectEnd, const char *insText,
1643 int *nDeleted, int *nInserted, int *endPos)
1645 int nLines, start, end, lineStart, lineEnd;
1646 int expInsLen, len, endOffset;
1647 char *c, *outStr, *outPtr, *line, *expText, *insLine;
1648 const char *insPtr;
1650 /* Allocate a buffer for the replacement string large enough to hold
1651 possibly expanded tabs in the inserted text, as well as per line: 1)
1652 an additional 2*MAX_EXP_CHAR_LEN characters for padding where tabs
1653 and control characters cross the column of the selection, 2) up to
1654 "column" additional spaces per line for padding out to the position
1655 of "column", 3) padding up to the width of the inserted text if that
1656 must be padded to align the text beyond the inserted column. (Space
1657 for additional newlines if the inserted text extends beyond the end
1658 of the buffer is counted with the length of insText) */
1659 start = BufStartOfLine(buf, startPos);
1660 nLines = countLines(insText) + 1;
1661 end = BufEndOfLine(buf, BufCountForwardNLines(buf, start, nLines-1));
1662 expText = expandTabs(insText, 0, buf->tabDist, buf->nullSubsChar,
1663 &expInsLen);
1664 XtFree(expText);
1665 outStr = XtMalloc(end-start + expInsLen +
1666 nLines * (rectEnd + MAX_EXP_CHAR_LEN) + 1);
1668 /* Loop over all lines in the buffer between start and end overlaying the
1669 text between rectStart and rectEnd and padding appropriately. Trim
1670 trailing space from line (whitespace at the ends of lines otherwise
1671 tends to multiply, since additional padding is added to maintain it */
1672 outPtr = outStr;
1673 lineStart = start;
1674 insPtr = insText;
1675 while (True) {
1676 lineEnd = BufEndOfLine(buf, lineStart);
1677 line = BufGetRange(buf, lineStart, lineEnd);
1678 insLine = copyLine(insPtr, &len);
1679 insPtr += len;
1680 overlayRectInLine(line, insLine, rectStart, rectEnd, buf->tabDist,
1681 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1682 XtFree(line);
1683 XtFree(insLine);
1684 for (c=outPtr+len-1; c>outPtr && (*c == ' ' || *c == '\t'); c--)
1685 len--;
1686 outPtr += len;
1687 *outPtr++ = '\n';
1688 lineStart = lineEnd < buf->length ? lineEnd + 1 : buf->length;
1689 if (*insPtr == '\0')
1690 break;
1691 insPtr++;
1693 if (outPtr != outStr)
1694 outPtr--; /* trim back off extra newline */
1695 *outPtr = '\0';
1697 /* replace the text between start and end with the new stuff */
1698 delete(buf, start, end);
1699 insert(buf, start, outStr);
1700 *nInserted = outPtr - outStr;
1701 *nDeleted = end - start;
1702 *endPos = start + (outPtr - outStr) - len + endOffset;
1703 XtFree(outStr);
1707 ** Insert characters from single-line string "insLine" in single-line string
1708 ** "line" at "column", leaving "insWidth" space before continuing line.
1709 ** "outLen" returns the number of characters written to "outStr", "endOffset"
1710 ** returns the number of characters from the beginning of the string to
1711 ** the right edge of the inserted text (as a hint for routines which need
1712 ** to position the cursor).
1714 static void insertColInLine(const char *line, const char *insLine,
1715 int column, int insWidth, int tabDist, int useTabs, char nullSubsChar,
1716 char *outStr, int *outLen, int *endOffset)
1718 char *c, *outPtr, *retabbedStr;
1719 const char *linePtr;
1720 int indent, toIndent, len, postColIndent;
1722 /* copy the line up to "column" */
1723 outPtr = outStr;
1724 indent = 0;
1725 for (linePtr=line; *linePtr!='\0'; linePtr++) {
1726 len = BufCharWidth(*linePtr, indent, tabDist, nullSubsChar);
1727 if (indent + len > column)
1728 break;
1729 indent += len;
1730 *outPtr++ = *linePtr;
1733 /* If "column" falls in the middle of a character, and the character is a
1734 tab, leave it off and leave the indent short and it will get padded
1735 later. If it's a control character, insert it and adjust indent
1736 accordingly. */
1737 if (indent < column && *linePtr != '\0') {
1738 postColIndent = indent + len;
1739 if (*linePtr == '\t')
1740 linePtr++;
1741 else {
1742 *outPtr++ = *linePtr++;
1743 indent += len;
1745 } else
1746 postColIndent = indent;
1748 /* If there's no text after the column and no text to insert, that's all */
1749 if (*insLine == '\0' && *linePtr == '\0') {
1750 *outLen = *endOffset = outPtr - outStr;
1751 return;
1754 /* pad out to column if text is too short */
1755 if (indent < column) {
1756 addPadding(outPtr, indent, column, tabDist, useTabs, nullSubsChar,&len);
1757 outPtr += len;
1758 indent = column;
1761 /* Copy the text from "insLine" (if any), recalculating the tabs as if
1762 the inserted string began at column 0 to its new column destination */
1763 if (*insLine != '\0') {
1764 retabbedStr = realignTabs(insLine, 0, indent, tabDist, useTabs,
1765 nullSubsChar, &len);
1766 for (c=retabbedStr; *c!='\0'; c++) {
1767 *outPtr++ = *c;
1768 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
1769 indent += len;
1771 XtFree(retabbedStr);
1774 /* If the original line did not extend past "column", that's all */
1775 if (*linePtr == '\0') {
1776 *outLen = *endOffset = outPtr - outStr;
1777 return;
1780 /* Pad out to column + width of inserted text + (additional original
1781 offset due to non-breaking character at column) */
1782 toIndent = column + insWidth + postColIndent-column;
1783 addPadding(outPtr, indent, toIndent, tabDist, useTabs, nullSubsChar, &len);
1784 outPtr += len;
1785 indent = toIndent;
1787 /* realign tabs for text beyond "column" and write it out */
1788 retabbedStr = realignTabs(linePtr, postColIndent, indent, tabDist,
1789 useTabs, nullSubsChar, &len);
1790 strcpy(outPtr, retabbedStr);
1791 XtFree(retabbedStr);
1792 *endOffset = outPtr - outStr;
1793 *outLen = (outPtr - outStr) + len;
1797 ** Remove characters in single-line string "line" between displayed positions
1798 ** "rectStart" and "rectEnd", and write the result to "outStr", which is
1799 ** assumed to be large enough to hold the returned string. Note that in
1800 ** certain cases, it is possible for the string to get longer due to
1801 ** expansion of tabs. "endOffset" returns the number of characters from
1802 ** the beginning of the string to the point where the characters were
1803 ** deleted (as a hint for routines which need to position the cursor).
1805 static void deleteRectFromLine(const char *line, int rectStart, int rectEnd,
1806 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
1807 int *endOffset)
1809 int indent, preRectIndent, postRectIndent, len;
1810 const char *c;
1811 char *outPtr;
1812 char *retabbedStr;
1814 /* copy the line up to rectStart */
1815 outPtr = outStr;
1816 indent = 0;
1817 for (c=line; *c!='\0'; c++) {
1818 if (indent > rectStart)
1819 break;
1820 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
1821 if (indent + len > rectStart && (indent == rectStart || *c == '\t'))
1822 break;
1823 indent += len;
1824 *outPtr++ = *c;
1826 preRectIndent = indent;
1828 /* skip the characters between rectStart and rectEnd */
1829 for(; *c!='\0' && indent<rectEnd; c++)
1830 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
1831 postRectIndent = indent;
1833 /* If the line ended before rectEnd, there's nothing more to do */
1834 if (*c == '\0') {
1835 *outPtr = '\0';
1836 *outLen = *endOffset = outPtr - outStr;
1837 return;
1840 /* fill in any space left by removed tabs or control characters
1841 which straddled the boundaries */
1842 indent = max(rectStart + postRectIndent-rectEnd, preRectIndent);
1843 addPadding(outPtr, preRectIndent, indent, tabDist, useTabs, nullSubsChar,
1844 &len);
1845 outPtr += len;
1847 /* Copy the rest of the line. If the indentation has changed, preserve
1848 the position of non-whitespace characters by converting tabs to
1849 spaces, then back to tabs with the correct offset */
1850 retabbedStr = realignTabs(c, postRectIndent, indent, tabDist, useTabs,
1851 nullSubsChar, &len);
1852 strcpy(outPtr, retabbedStr);
1853 XtFree(retabbedStr);
1854 *endOffset = outPtr - outStr;
1855 *outLen = (outPtr - outStr) + len;
1859 ** Overlay characters from single-line string "insLine" on single-line string
1860 ** "line" between displayed character offsets "rectStart" and "rectEnd".
1861 ** "outLen" returns the number of characters written to "outStr", "endOffset"
1862 ** returns the number of characters from the beginning of the string to
1863 ** the right edge of the inserted text (as a hint for routines which need
1864 ** to position the cursor).
1866 ** This code does not handle control characters very well, but oh well.
1868 static void overlayRectInLine(const char *line, const char *insLine,
1869 int rectStart, int rectEnd, int tabDist, int useTabs,
1870 char nullSubsChar, char *outStr, int *outLen, int *endOffset)
1872 char *c, *outPtr, *retabbedStr;
1873 const char *linePtr;
1874 int inIndent, outIndent, len, postRectIndent;
1876 /* copy the line up to "rectStart" or just before the char that
1877 contains it*/
1878 outPtr = outStr;
1879 inIndent = outIndent = 0;
1880 for (linePtr=line; *linePtr!='\0'; linePtr++) {
1881 len = BufCharWidth(*linePtr, inIndent, tabDist, nullSubsChar);
1882 if (inIndent + len > rectStart)
1883 break;
1884 inIndent += len;
1885 outIndent += len;
1886 *outPtr++ = *linePtr;
1889 /* If "rectStart" falls in the middle of a character, and the character
1890 is a tab, leave it off and leave the outIndent short and it will get
1891 padded later. If it's a control character, insert it and adjust
1892 outIndent accordingly. */
1893 if (inIndent < rectStart && *linePtr != '\0') {
1894 if (*linePtr == '\t') {
1895 /* Skip past the tab */
1896 linePtr++;
1897 inIndent += len;
1898 } else {
1899 *outPtr++ = *linePtr++;
1900 outIndent += len;
1901 inIndent += len;
1905 /* skip the characters between rectStart and rectEnd */
1906 for(; *linePtr!='\0' && inIndent < rectEnd; linePtr++)
1907 inIndent += BufCharWidth(*linePtr, inIndent, tabDist, nullSubsChar);
1908 postRectIndent = inIndent;
1910 /* After this inIndent is dead and linePtr is supposed to point at the
1911 character just past the last character that will be altered by
1912 the overlay, whether that's a \t or otherwise. postRectIndent is
1913 the position at which that character is supposed to appear */
1915 /* If there's no text after rectStart and no text to insert, that's all */
1916 if (*insLine == '\0' && *linePtr == '\0') {
1917 *outLen = *endOffset = outPtr - outStr;
1918 return;
1921 /* pad out to rectStart if text is too short */
1922 if (outIndent < rectStart) {
1923 addPadding(outPtr, outIndent, rectStart, tabDist, useTabs, nullSubsChar,
1924 &len);
1925 outPtr += len;
1927 outIndent = rectStart;
1929 /* Copy the text from "insLine" (if any), recalculating the tabs as if
1930 the inserted string began at column 0 to its new column destination */
1931 if (*insLine != '\0') {
1932 retabbedStr = realignTabs(insLine, 0, rectStart, tabDist, useTabs,
1933 nullSubsChar, &len);
1934 for (c=retabbedStr; *c!='\0'; c++) {
1935 *outPtr++ = *c;
1936 len = BufCharWidth(*c, outIndent, tabDist, nullSubsChar);
1937 outIndent += len;
1939 XtFree(retabbedStr);
1942 /* If the original line did not extend past "rectStart", that's all */
1943 if (*linePtr == '\0') {
1944 *outLen = *endOffset = outPtr - outStr;
1945 return;
1948 /* Pad out to rectEnd + (additional original offset
1949 due to non-breaking character at right boundary) */
1950 addPadding(outPtr, outIndent, postRectIndent, tabDist, useTabs,
1951 nullSubsChar, &len);
1952 outPtr += len;
1953 outIndent = postRectIndent;
1955 /* copy the text beyond "rectEnd" */
1956 strcpy(outPtr, linePtr);
1957 *endOffset = outPtr - outStr;
1958 *outLen = (outPtr - outStr) + strlen(linePtr);
1961 static void setSelection(selection *sel, int start, int end)
1963 sel->selected = start != end;
1964 sel->zeroWidth = (start == end) ? 1 : 0;
1965 sel->rectangular = False;
1966 sel->start = min(start, end);
1967 sel->end = max(start, end);
1970 static void setRectSelect(selection *sel, int start, int end,
1971 int rectStart, int rectEnd)
1973 sel->selected = rectStart < rectEnd;
1974 sel->zeroWidth = (rectStart == rectEnd) ? 1 : 0;
1975 sel->rectangular = True;
1976 sel->start = start;
1977 sel->end = end;
1978 sel->rectStart = rectStart;
1979 sel->rectEnd = rectEnd;
1982 static int getSelectionPos(selection *sel, int *start, int *end,
1983 int *isRect, int *rectStart, int *rectEnd)
1985 /* Always fill in the parameters (zero-width can be requested too). */
1986 *isRect = sel->rectangular;
1987 *start = sel->start;
1988 *end = sel->end;
1989 if (sel->rectangular) {
1990 *rectStart = sel->rectStart;
1991 *rectEnd = sel->rectEnd;
1993 return sel->selected;
1996 static char *getSelectionText(textBuffer *buf, selection *sel)
1998 int start, end, isRect, rectStart, rectEnd;
1999 char *text;
2001 /* If there's no selection, return an allocated empty string */
2002 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd)) {
2003 text = XtMalloc(1);
2004 *text = '\0';
2005 return text;
2008 /* If the selection is not rectangular, return the selected range */
2009 if (isRect)
2010 return BufGetTextInRect(buf, start, end, rectStart, rectEnd);
2011 else
2012 return BufGetRange(buf, start, end);
2015 static void removeSelected(textBuffer *buf, selection *sel)
2017 int start, end;
2018 int isRect, rectStart, rectEnd;
2020 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd))
2021 return;
2022 if (isRect)
2023 BufRemoveRect(buf, start, end, rectStart, rectEnd);
2024 else
2025 BufRemove(buf, start, end);
2028 static void replaceSelected(textBuffer *buf, selection *sel, const char *text)
2030 int start, end, isRect, rectStart, rectEnd;
2031 selection oldSelection = *sel;
2033 /* If there's no selection, return */
2034 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd))
2035 return;
2037 /* Do the appropriate type of replace */
2038 if (isRect)
2039 BufReplaceRect(buf, start, end, rectStart, rectEnd, text);
2040 else
2041 BufReplace(buf, start, end, text);
2043 /* Unselect (happens automatically in BufReplace, but BufReplaceRect
2044 can't detect when the contents of a selection goes away) */
2045 sel->selected = False;
2046 redisplaySelection(buf, &oldSelection, sel);
2049 static void addPadding(char *string, int startIndent, int toIndent,
2050 int tabDist, int useTabs, char nullSubsChar, int *charsAdded)
2052 char *outPtr;
2053 int len, indent;
2055 indent = startIndent;
2056 outPtr = string;
2057 if (useTabs) {
2058 while (indent < toIndent) {
2059 len = BufCharWidth('\t', indent, tabDist, nullSubsChar);
2060 if (len > 1 && indent + len <= toIndent) {
2061 *outPtr++ = '\t';
2062 indent += len;
2063 } else {
2064 *outPtr++ = ' ';
2065 indent++;
2068 } else {
2069 while (indent < toIndent) {
2070 *outPtr++ = ' ';
2071 indent++;
2074 *charsAdded = outPtr - string;
2078 ** Call the stored modify callback procedure(s) for this buffer to update the
2079 ** changed area(s) on the screen and any other listeners.
2081 static void callModifyCBs(textBuffer *buf, int pos, int nDeleted,
2082 int nInserted, int nRestyled, char *deletedText)
2084 int i;
2086 for (i=0; i<buf->nModifyProcs; i++)
2087 (*buf->modifyProcs[i])(pos, nInserted, nDeleted, nRestyled,
2088 deletedText, buf->cbArgs[i]);
2092 ** Call the stored pre-delete callback procedure(s) for this buffer to update
2093 ** the changed area(s) on the screen and any other listeners.
2095 static void callPreDeleteCBs(textBuffer *buf, int pos, int nDeleted)
2097 int i;
2099 for (i=0; i<buf->nPreDeleteProcs; i++)
2100 (*buf->preDeleteProcs[i])(pos, nDeleted, buf->preDeleteCbArgs[i]);
2104 ** Call the stored redisplay procedure(s) for this buffer to update the
2105 ** screen for a change in a selection.
2107 static void redisplaySelection(textBuffer *buf, selection *oldSelection,
2108 selection *newSelection)
2110 int oldStart, oldEnd, newStart, newEnd, ch1Start, ch1End, ch2Start, ch2End;
2112 /* If either selection is rectangular, add an additional character to
2113 the end of the selection to request the redraw routines to wipe out
2114 the parts of the selection beyond the end of the line */
2115 oldStart = oldSelection->start;
2116 newStart = newSelection->start;
2117 oldEnd = oldSelection->end;
2118 newEnd = newSelection->end;
2119 if (oldSelection->rectangular)
2120 oldEnd++;
2121 if (newSelection->rectangular)
2122 newEnd++;
2124 /* If the old or new selection is unselected, just redisplay the
2125 single area that is (was) selected and return */
2126 if (!oldSelection->selected && !newSelection->selected)
2127 return;
2128 if (!oldSelection->selected) {
2129 callModifyCBs(buf, newStart, 0, 0, newEnd-newStart, NULL);
2130 return;
2132 if (!newSelection->selected) {
2133 callModifyCBs(buf, oldStart, 0, 0, oldEnd-oldStart, NULL);
2134 return;
2137 /* If the selection changed from normal to rectangular or visa versa, or
2138 if a rectangular selection changed boundaries, redisplay everything */
2139 if ((oldSelection->rectangular && !newSelection->rectangular) ||
2140 (!oldSelection->rectangular && newSelection->rectangular) ||
2141 (oldSelection->rectangular && (
2142 (oldSelection->rectStart != newSelection->rectStart) ||
2143 (oldSelection->rectEnd != newSelection->rectEnd)))) {
2144 callModifyCBs(buf, min(oldStart, newStart), 0, 0,
2145 max(oldEnd, newEnd) - min(oldStart, newStart), NULL);
2146 return;
2149 /* If the selections are non-contiguous, do two separate updates
2150 and return */
2151 if (oldEnd < newStart || newEnd < oldStart) {
2152 callModifyCBs(buf, oldStart, 0, 0, oldEnd-oldStart, NULL);
2153 callModifyCBs(buf, newStart, 0, 0, newEnd-newStart, NULL);
2154 return;
2157 /* Otherwise, separate into 3 separate regions: ch1, and ch2 (the two
2158 changed areas), and the unchanged area of their intersection,
2159 and update only the changed area(s) */
2160 ch1Start = min(oldStart, newStart);
2161 ch2End = max(oldEnd, newEnd);
2162 ch1End = max(oldStart, newStart);
2163 ch2Start = min(oldEnd, newEnd);
2164 if (ch1Start != ch1End)
2165 callModifyCBs(buf, ch1Start, 0, 0, ch1End-ch1Start, NULL);
2166 if (ch2Start != ch2End)
2167 callModifyCBs(buf, ch2Start, 0, 0, ch2End-ch2Start, NULL);
2170 static void moveGap(textBuffer *buf, int pos)
2172 int gapLen = buf->gapEnd - buf->gapStart;
2174 if (pos > buf->gapStart)
2175 memmove(&buf->buf[buf->gapStart], &buf->buf[buf->gapEnd],
2176 pos - buf->gapStart);
2177 else
2178 memmove(&buf->buf[pos + gapLen], &buf->buf[pos], buf->gapStart - pos);
2179 buf->gapEnd += pos - buf->gapStart;
2180 buf->gapStart += pos - buf->gapStart;
2184 ** reallocate the text storage in "buf" to have a gap starting at "newGapStart"
2185 ** and a gap size of "newGapLen", preserving the buffer's current contents.
2187 static void reallocateBuf(textBuffer *buf, int newGapStart, int newGapLen)
2189 char *newBuf;
2190 int newGapEnd;
2192 newBuf = XtMalloc(buf->length + newGapLen);
2193 newGapEnd = newGapStart + newGapLen;
2194 if (newGapStart <= buf->gapStart) {
2195 memcpy(newBuf, buf->buf, newGapStart);
2196 memcpy(&newBuf[newGapEnd], &buf->buf[newGapStart],
2197 buf->gapStart - newGapStart);
2198 memcpy(&newBuf[newGapEnd + buf->gapStart - newGapStart],
2199 &buf->buf[buf->gapEnd], buf->length - buf->gapStart);
2200 } else { /* newGapStart > buf->gapStart */
2201 memcpy(newBuf, buf->buf, buf->gapStart);
2202 memcpy(&newBuf[buf->gapStart], &buf->buf[buf->gapEnd],
2203 newGapStart - buf->gapStart);
2204 memcpy(&newBuf[newGapEnd],
2205 &buf->buf[buf->gapEnd + newGapStart - buf->gapStart],
2206 buf->length - newGapStart);
2208 XtFree(buf->buf);
2209 buf->buf = newBuf;
2210 buf->gapStart = newGapStart;
2211 buf->gapEnd = newGapEnd;
2212 #ifdef PURIFY
2213 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
2214 #endif
2218 ** Update all of the selections in "buf" for changes in the buffer's text
2220 static void updateSelections(textBuffer *buf, int pos, int nDeleted,
2221 int nInserted)
2223 updateSelection(&buf->primary, pos, nDeleted, nInserted);
2224 updateSelection(&buf->secondary, pos, nDeleted, nInserted);
2225 updateSelection(&buf->highlight, pos, nDeleted, nInserted);
2229 ** Update an individual selection for changes in the corresponding text
2231 static void updateSelection(selection *sel, int pos, int nDeleted,
2232 int nInserted)
2234 if ((!sel->selected && !sel->zeroWidth) || pos > sel->end)
2235 return;
2236 if (pos+nDeleted <= sel->start) {
2237 sel->start += nInserted - nDeleted;
2238 sel->end += nInserted - nDeleted;
2239 } else if (pos <= sel->start && pos+nDeleted >= sel->end) {
2240 sel->start = pos;
2241 sel->end = pos;
2242 sel->selected = False;
2243 sel->zeroWidth = False;
2244 } else if (pos <= sel->start && pos+nDeleted < sel->end) {
2245 sel->start = pos;
2246 sel->end = nInserted + sel->end - nDeleted;
2247 } else if (pos < sel->end) {
2248 sel->end += nInserted - nDeleted;
2249 if (sel->end <= sel->start)
2250 sel->selected = False;
2255 ** Search forwards in buffer "buf" for character "searchChar", starting
2256 ** with the character "startPos", and returning the result in "foundPos"
2257 ** returns True if found, False if not. (The difference between this and
2258 ** BufSearchForward is that it's optimized for single characters. The
2259 ** overall performance of the text widget is dependent on its ability to
2260 ** count lines quickly, hence searching for a single character: newline)
2262 static int searchForward(textBuffer *buf, int startPos, char searchChar,
2263 int *foundPos)
2265 int pos, gapLen = buf->gapEnd - buf->gapStart;
2267 pos = startPos;
2268 while (pos < buf->gapStart) {
2269 if (buf->buf[pos] == searchChar) {
2270 *foundPos = pos;
2271 return True;
2273 pos++;
2275 while (pos < buf->length) {
2276 if (buf->buf[pos + gapLen] == searchChar) {
2277 *foundPos = pos;
2278 return True;
2280 pos++;
2282 *foundPos = buf->length;
2283 return False;
2287 ** Search backwards in buffer "buf" for character "searchChar", starting
2288 ** with the character BEFORE "startPos", returning the result in "foundPos"
2289 ** returns True if found, False if not. (The difference between this and
2290 ** BufSearchBackward is that it's optimized for single characters. The
2291 ** overall performance of the text widget is dependent on its ability to
2292 ** count lines quickly, hence searching for a single character: newline)
2294 static int searchBackward(textBuffer *buf, int startPos, char searchChar,
2295 int *foundPos)
2297 int pos, gapLen = buf->gapEnd - buf->gapStart;
2299 if (startPos == 0) {
2300 *foundPos = 0;
2301 return False;
2303 pos = startPos == 0 ? 0 : startPos - 1;
2304 while (pos >= buf->gapStart) {
2305 if (buf->buf[pos + gapLen] == searchChar) {
2306 *foundPos = pos;
2307 return True;
2309 pos--;
2311 while (pos >= 0) {
2312 if (buf->buf[pos] == searchChar) {
2313 *foundPos = pos;
2314 return True;
2316 pos--;
2318 *foundPos = 0;
2319 return False;
2323 ** Copy from "text" to end up to but not including newline (or end of "text")
2324 ** and return the copy as the function value, and the length of the line in
2325 ** "lineLen"
2327 static char *copyLine(const char *text, int *lineLen)
2329 int len = 0;
2330 const char *c;
2331 char *outStr;
2333 for (c=text; *c!='\0' && *c!='\n'; c++)
2334 len++;
2335 outStr = XtMalloc(len + 1);
2336 strncpy(outStr, text, len);
2337 outStr[len] = '\0';
2338 *lineLen = len;
2339 return outStr;
2343 ** Count the number of newlines in a null-terminated text string;
2345 static int countLines(const char *string)
2347 const char *c;
2348 int lineCount = 0;
2350 for (c=string; *c!='\0'; c++)
2351 if (*c == '\n') lineCount++;
2352 return lineCount;
2356 ** Measure the width in displayed characters of string "text"
2358 static int textWidth(const char *text, int tabDist, char nullSubsChar)
2360 int width = 0, maxWidth = 0;
2361 const char *c;
2363 for (c=text; *c!='\0'; c++) {
2364 if (*c == '\n') {
2365 if (width > maxWidth)
2366 maxWidth = width;
2367 width = 0;
2368 } else
2369 width += BufCharWidth(*c, width, tabDist, nullSubsChar);
2371 if (width > maxWidth)
2372 return width;
2373 return maxWidth;
2377 ** Find the first and last character position in a line withing a rectangular
2378 ** selection (for copying). Includes tabs which cross rectStart, but not
2379 ** control characters which do so. Leaves off tabs which cross rectEnd.
2381 ** Technically, the calling routine should convert tab characters which
2382 ** cross the right boundary of the selection to spaces which line up with
2383 ** the edge of the selection. Unfortunately, the additional memory
2384 ** management required in the parent routine to allow for the changes
2385 ** in string size is not worth all the extra work just for a couple of
2386 ** shifted characters, so if a tab protrudes, just lop it off and hope
2387 ** that there are other characters in the selection to establish the right
2388 ** margin for subsequent columnar pastes of this data.
2390 static void findRectSelBoundariesForCopy(textBuffer *buf, int lineStartPos,
2391 int rectStart, int rectEnd, int *selStart, int *selEnd)
2393 int pos, width, indent = 0;
2394 char c;
2396 /* find the start of the selection */
2397 for (pos=lineStartPos; pos<buf->length; pos++) {
2398 c = BufGetCharacter(buf, pos);
2399 if (c == '\n')
2400 break;
2401 width = BufCharWidth(c, indent, buf->tabDist, buf->nullSubsChar);
2402 if (indent + width > rectStart) {
2403 if (indent != rectStart && c != '\t') {
2404 pos++;
2405 indent += width;
2407 break;
2409 indent += width;
2411 *selStart = pos;
2413 /* find the end */
2414 for (; pos<buf->length; pos++) {
2415 c = BufGetCharacter(buf, pos);
2416 if (c == '\n')
2417 break;
2418 width = BufCharWidth(c, indent, buf->tabDist, buf->nullSubsChar);
2419 indent += width;
2420 if (indent > rectEnd) {
2421 if (indent-width != rectEnd && c != '\t')
2422 pos++;
2423 break;
2426 *selEnd = pos;
2430 ** Adjust the space and tab characters from string "text" so that non-white
2431 ** characters remain stationary when the text is shifted from starting at
2432 ** "origIndent" to starting at "newIndent". Returns an allocated string
2433 ** which must be freed by the caller with XtFree.
2435 static char *realignTabs(const char *text, int origIndent, int newIndent,
2436 int tabDist, int useTabs, char nullSubsChar, int *newLength)
2438 char *expStr, *outStr;
2439 int len;
2441 /* If the tabs settings are the same, retain original tabs */
2442 if (origIndent % tabDist == newIndent %tabDist) {
2443 len = strlen(text);
2444 outStr = XtMalloc(len + 1);
2445 strcpy(outStr, text);
2446 *newLength = len;
2447 return outStr;
2450 /* If the tab settings are not the same, brutally convert tabs to
2451 spaces, then back to tabs in the new position */
2452 expStr = expandTabs(text, origIndent, tabDist, nullSubsChar, &len);
2453 if (!useTabs) {
2454 *newLength = len;
2455 return expStr;
2457 outStr = unexpandTabs(expStr, newIndent, tabDist, nullSubsChar, newLength);
2458 XtFree(expStr);
2459 return outStr;
2463 ** Expand tabs to spaces for a block of text. The additional parameter
2464 ** "startIndent" if nonzero, indicates that the text is a rectangular selection
2465 ** beginning at column "startIndent"
2467 static char *expandTabs(const char *text, int startIndent, int tabDist,
2468 char nullSubsChar, int *newLen)
2470 char *outStr, *outPtr;
2471 const char *c;
2472 int indent, len, outLen = 0;
2474 /* rehearse the expansion to figure out length for output string */
2475 indent = startIndent;
2476 for (c=text; *c!='\0'; c++) {
2477 if (*c == '\t') {
2478 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
2479 outLen += len;
2480 indent += len;
2481 } else if (*c == '\n') {
2482 indent = startIndent;
2483 outLen++;
2484 } else {
2485 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
2486 outLen++;
2490 /* do the expansion */
2491 outStr = XtMalloc(outLen+1);
2492 outPtr = outStr;
2493 indent = startIndent;
2494 for (c=text; *c!= '\0'; c++) {
2495 if (*c == '\t') {
2496 len = BufExpandCharacter(*c, indent, outPtr, tabDist, nullSubsChar);
2497 outPtr += len;
2498 indent += len;
2499 } else if (*c == '\n') {
2500 indent = startIndent;
2501 *outPtr++ = *c;
2502 } else {
2503 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
2504 *outPtr++ = *c;
2507 outStr[outLen] = '\0';
2508 *newLen = outLen;
2509 return outStr;
2513 ** Convert sequences of spaces into tabs. The threshold for conversion is
2514 ** when 3 or more spaces can be converted into a single tab, this avoids
2515 ** converting double spaces after a period withing a block of text.
2517 static char *unexpandTabs(const char *text, int startIndent, int tabDist,
2518 char nullSubsChar, int *newLen)
2520 char *outStr, *outPtr, expandedChar[MAX_EXP_CHAR_LEN];
2521 const char *c;
2522 int indent, len;
2524 outStr = XtMalloc(strlen(text)+1);
2525 outPtr = outStr;
2526 indent = startIndent;
2527 for (c=text; *c!='\0';) {
2528 if (*c == ' ') {
2529 len = BufExpandCharacter('\t', indent, expandedChar, tabDist,
2530 nullSubsChar);
2531 if (len >= 3 && !strncmp(c, expandedChar, len)) {
2532 c += len;
2533 *outPtr++ = '\t';
2534 indent += len;
2535 } else {
2536 *outPtr++ = *c++;
2537 indent++;
2539 } else if (*c == '\n') {
2540 indent = startIndent;
2541 *outPtr++ = *c++;
2542 } else {
2543 *outPtr++ = *c++;
2544 indent++;
2547 *outPtr = '\0';
2548 *newLen = outPtr - outStr;
2549 return outStr;
2552 static int max(int i1, int i2)
2554 return i1 >= i2 ? i1 : i2;
2557 static int min(int i1, int i2)
2559 return i1 <= i2 ? i1 : i2;