Committed patch #628933: New rangeset API. (Including the fix of
[nedit.git] / source / textBuf.c
blob34d84887a73e4eb10180424cdff0d8ed4872c338
1 static const char CVSID[] = "$Id: textBuf.c,v 1.27 2003/05/02 18:18:45 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 #include <Xm/Xm.h>
43 #ifdef HAVE_DEBUG_H
44 #include "../debug.h"
45 #endif
47 #define PREFERRED_GAP_SIZE 80 /* Initial size for the buffer gap (empty space
48 in the buffer where text might be inserted
49 if the user is typing sequential chars) */
51 static void histogramCharacters(const char *string, int length, char hist[256],
52 int init);
53 static void subsChars(char *string, int length, char fromChar, char toChar);
54 static char chooseNullSubsChar(char hist[256]);
55 static int insert(textBuffer *buf, int pos, const char *text);
56 static void delete(textBuffer *buf, int start, int end);
57 static void deleteRect(textBuffer *buf, int start, int end, int rectStart,
58 int rectEnd, int *replaceLen, int *endPos);
59 static void insertCol(textBuffer *buf, int column, int startPos, const char *insText,
60 int *nDeleted, int *nInserted, int *endPos);
61 static void overlayRect(textBuffer *buf, int startPos, int rectStart,
62 int rectEnd, const char *insText, int *nDeleted, int *nInserted, int *endPos);
63 static void insertColInLine(const char *line, const char *insLine, int column, int insWidth,
64 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
65 int *endOffset);
66 static void deleteRectFromLine(const char *line, int rectStart, int rectEnd,
67 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
68 int *endOffset);
69 static void overlayRectInLine(const char *line, const char *insLine, int rectStart,
70 int rectEnd, int tabDist, int useTabs, char nullSubsChar, char *outStr,
71 int *outLen, int *endOffset);
72 static void callPreDeleteCBs(textBuffer *buf, int pos, int nDeleted);
73 static void callModifyCBs(textBuffer *buf, int pos, int nDeleted,
74 int nInserted, int nRestyled, char *deletedText);
75 static void redisplaySelection(textBuffer *buf, selection *oldSelection,
76 selection *newSelection);
77 static void moveGap(textBuffer *buf, int pos);
78 static void reallocateBuf(textBuffer *buf, int newGapStart, int newGapLen);
79 static void setSelection(selection *sel, int start, int end);
80 static void setRectSelect(selection *sel, int start, int end,
81 int rectStart, int rectEnd);
82 static void updateSelections(textBuffer *buf, int pos, int nDeleted,
83 int nInserted);
84 static void updateSelection(selection *sel, int pos, int nDeleted,
85 int nInserted);
86 static int getSelectionPos(selection *sel, int *start, int *end,
87 int *isRect, int *rectStart, int *rectEnd);
88 static char *getSelectionText(textBuffer *buf, selection *sel);
89 static void removeSelected(textBuffer *buf, selection *sel);
90 static void replaceSelected(textBuffer *buf, selection *sel, const char *text);
91 static void addPadding(char *string, int startIndent, int toIndent,
92 int tabDist, int useTabs, char nullSubsChar, int *charsAdded);
93 static int searchForward(textBuffer *buf, int startPos, char searchChar,
94 int *foundPos);
95 static int searchBackward(textBuffer *buf, int startPos, char searchChar,
96 int *foundPos);
97 static char *copyLine(const char *text, int *lineLen);
98 static int countLines(const char *string);
99 static int textWidth(const char *text, int tabDist, char nullSubsChar);
100 static void findRectSelBoundariesForCopy(textBuffer *buf, int lineStartPos,
101 int rectStart, int rectEnd, int *selStart, int *selEnd);
102 static char *realignTabs(const char *text, int origIndent, int newIndent,
103 int tabDist, int useTabs, char nullSubsChar, int *newLength);
104 static char *expandTabs(const char *text, int startIndent, int tabDist,
105 char nullSubsChar, int *newLen);
106 static char *unexpandTabs(const char *text, int startIndent, int tabDist,
107 char nullSubsChar, int *newLen);
108 static int max(int i1, int i2);
109 static int min(int i1, int i2);
111 #ifdef __MVS__
112 static const char *ControlCodeTable[64] = {
113 "nul", "soh", "stx", "etx", "sel", "ht", "rnl", "del",
114 "ge", "sps", "rpt", "vt", "ff", "cr", "so", "si",
115 "dle", "dc1", "dc2", "dc3", "res", "nl", "bs", "poc",
116 "can", "em", "ubs", "cu1", "ifs", "igs", "irs", "ius",
117 "ds", "sos", "fs", "wus", "byp", "lf", "etb", "esc",
118 "sa", "sfe", "sm", "csp", "mfa", "enq", "ack", "bel",
119 "x30", "x31", "syn", "ir", "pp", "trn", "nbs", "eot",
120 "sbs", "it", "rff", "cu3", "dc4", "nak", "x3e", "sub"};
121 #else
122 static const char *ControlCodeTable[32] = {
123 "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel",
124 "bs", "ht", "nl", "vt", "np", "cr", "so", "si",
125 "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb",
126 "can", "em", "sub", "esc", "fs", "gs", "rs", "us"};
127 #endif
130 ** Create an empty text buffer
132 textBuffer *BufCreate(void)
134 textBuffer *buf = BufCreatePreallocated(0);
135 BufAddModifyCB(buf, RangesetBufModifiedCB, buf);
136 return buf;
140 ** Create an empty text buffer of a pre-determined size (use this to
141 ** avoid unnecessary re-allocation if you know exactly how much the buffer
142 ** will need to hold
144 textBuffer *BufCreatePreallocated(int requestedSize)
146 textBuffer *buf;
148 buf = (textBuffer *)XtMalloc(sizeof(textBuffer));
149 buf->length = 0;
150 buf->buf = XtMalloc(requestedSize + PREFERRED_GAP_SIZE);
151 buf->gapStart = 0;
152 buf->gapEnd = PREFERRED_GAP_SIZE;
153 buf->tabDist = 8;
154 buf->useTabs = True;
155 buf->primary.selected = False;
156 buf->primary.zeroWidth = False;
157 buf->primary.rectangular = False;
158 buf->primary.start = buf->primary.end = 0;
159 buf->secondary.selected = False;
160 buf->secondary.zeroWidth = False;
161 buf->secondary.start = buf->secondary.end = 0;
162 buf->secondary.rectangular = False;
163 buf->highlight.selected = False;
164 buf->highlight.zeroWidth = False;
165 buf->highlight.start = buf->highlight.end = 0;
166 buf->highlight.rectangular = False;
167 buf->modifyProcs = NULL;
168 buf->cbArgs = NULL;
169 buf->nModifyProcs = 0;
170 buf->preDeleteProcs = NULL;
171 buf->preDeleteCbArgs = NULL;
172 buf->nPreDeleteProcs = 0;
173 buf->nullSubsChar = '\0';
174 #ifdef PURIFY
175 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
176 #endif
177 buf->rangesetTable = NULL;
178 return buf;
182 ** Free a text buffer
184 void BufFree(textBuffer *buf)
186 XtFree(buf->buf);
187 if (buf->nModifyProcs != 0) {
188 XtFree((char *)buf->modifyProcs);
189 XtFree((char *)buf->cbArgs);
191 if (buf->rangesetTable)
192 RangesetTableFree(buf->rangesetTable);
193 if (buf->nPreDeleteProcs != 0) {
194 XtFree((char *)buf->preDeleteProcs);
195 XtFree((char *)buf->preDeleteCbArgs);
197 XtFree((char *)buf);
201 ** Get the entire contents of a text buffer. Memory is allocated to contain
202 ** the returned string, which the caller must free.
204 char *BufGetAll(textBuffer *buf)
206 char *text;
208 text = XtMalloc(buf->length+1);
209 memcpy(text, buf->buf, buf->gapStart);
210 memcpy(&text[buf->gapStart], &buf->buf[buf->gapEnd],
211 buf->length - buf->gapStart);
212 text[buf->length] = '\0';
213 return text;
217 ** Replace the entire contents of the text buffer
219 void BufSetAll(textBuffer *buf, const char *text)
221 int length, deletedLength;
222 char *deletedText;
223 length = strlen(text);
225 callPreDeleteCBs(buf, 0, buf->length);
227 /* Save information for redisplay, and get rid of the old buffer */
228 deletedText = BufGetAll(buf);
229 deletedLength = buf->length;
230 XtFree(buf->buf);
232 /* Start a new buffer with a gap of PREFERRED_GAP_SIZE in the center */
233 buf->buf = XtMalloc(length + PREFERRED_GAP_SIZE);
234 buf->length = length;
235 buf->gapStart = length/2;
236 buf->gapEnd = buf->gapStart + PREFERRED_GAP_SIZE;
237 memcpy(buf->buf, text, buf->gapStart);
238 memcpy(&buf->buf[buf->gapEnd], &text[buf->gapStart], length-buf->gapStart);
239 #ifdef PURIFY
240 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
241 #endif
243 /* Zero all of the existing selections */
244 updateSelections(buf, 0, deletedLength, 0);
246 /* Call the saved display routine(s) to update the screen */
247 callModifyCBs(buf, 0, deletedLength, length, 0, deletedText);
248 XtFree(deletedText);
252 ** Return a copy of the text between "start" and "end" character positions
253 ** from text buffer "buf". Positions start at 0, and the range does not
254 ** include the character pointed to by "end"
256 char *BufGetRange(textBuffer *buf, int start, int end)
258 char *text;
259 int length, part1Length;
261 /* Make sure start and end are ok, and allocate memory for returned string.
262 If start is bad, return "", if end is bad, adjust it. */
263 if (start < 0 || start > buf->length) {
264 text = XtMalloc(1);
265 text[0] = '\0';
266 return text;
268 if (end < start) {
269 int temp = start;
270 start = end;
271 end = temp;
273 if (end > buf->length)
274 end = buf->length;
275 length = end - start;
276 text = XtMalloc(length+1);
278 /* Copy the text from the buffer to the returned string */
279 if (end <= buf->gapStart) {
280 memcpy(text, &buf->buf[start], length);
281 } else if (start >= buf->gapStart) {
282 memcpy(text, &buf->buf[start+(buf->gapEnd-buf->gapStart)], length);
283 } else {
284 part1Length = buf->gapStart - start;
285 memcpy(text, &buf->buf[start], part1Length);
286 memcpy(&text[part1Length], &buf->buf[buf->gapEnd], length-part1Length);
288 text[length] = '\0';
289 return text;
293 ** Return the character at buffer position "pos". Positions start at 0.
295 char BufGetCharacter(textBuffer *buf, int pos)
297 if (pos < 0 || pos >= buf->length)
298 return '\0';
299 if (pos < buf->gapStart)
300 return buf->buf[pos];
301 else
302 return buf->buf[pos + buf->gapEnd-buf->gapStart];
306 ** Insert null-terminated string "text" at position "pos" in "buf"
308 void BufInsert(textBuffer *buf, int pos, const char *text)
310 int nInserted;
312 /* if pos is not contiguous to existing text, make it */
313 if (pos > buf->length) pos = buf->length;
314 if (pos < 0 ) pos = 0;
316 /* Even if nothing is deleted, we must call these callbacks */
317 callPreDeleteCBs(buf, pos, 0);
319 /* insert and redisplay */
320 nInserted = insert(buf, pos, text);
321 buf->cursorPosHint = pos + nInserted;
322 callModifyCBs(buf, pos, 0, nInserted, 0, NULL);
326 ** Delete the characters between "start" and "end", and insert the
327 ** null-terminated string "text" in their place in in "buf"
329 void BufReplace(textBuffer *buf, int start, int end, const char *text)
331 char *deletedText;
332 int nInserted = strlen(text);
334 callPreDeleteCBs(buf, start, end-start);
335 deletedText = BufGetRange(buf, start, end);
336 delete(buf, start, end);
337 insert(buf, start, text);
338 buf->cursorPosHint = start + nInserted;
339 callModifyCBs(buf, start, end-start, nInserted, 0, deletedText);
340 XtFree(deletedText);
343 void BufRemove(textBuffer *buf, int start, int end)
345 char *deletedText;
347 /* Make sure the arguments make sense */
348 if (start > end) {
349 int temp = start;
350 start = end;
351 end = temp;
353 if (start > buf->length) start = buf->length;
354 if (start < 0) start = 0;
355 if (end > buf->length) end = buf->length;
356 if (end < 0) end = 0;
358 callPreDeleteCBs(buf, start, end-start);
359 /* Remove and redisplay */
360 deletedText = BufGetRange(buf, start, end);
361 delete(buf, start, end);
362 buf->cursorPosHint = start;
363 callModifyCBs(buf, start, end-start, 0, 0, deletedText);
364 XtFree(deletedText);
367 void BufCopyFromBuf(textBuffer *fromBuf, textBuffer *toBuf, int fromStart,
368 int fromEnd, int toPos)
370 int length = fromEnd - fromStart;
371 int part1Length;
373 /* Prepare the buffer to receive the new text. If the new text fits in
374 the current buffer, just move the gap (if necessary) to where
375 the text should be inserted. If the new text is too large, reallocate
376 the buffer with a gap large enough to accomodate the new text and a
377 gap of PREFERRED_GAP_SIZE */
378 if (length > toBuf->gapEnd - toBuf->gapStart)
379 reallocateBuf(toBuf, toPos, length + PREFERRED_GAP_SIZE);
380 else if (toPos != toBuf->gapStart)
381 moveGap(toBuf, toPos);
383 /* Insert the new text (toPos now corresponds to the start of the gap) */
384 if (fromEnd <= fromBuf->gapStart) {
385 memcpy(&toBuf->buf[toPos], &fromBuf->buf[fromStart], length);
386 } else if (fromStart >= fromBuf->gapStart) {
387 memcpy(&toBuf->buf[toPos],
388 &fromBuf->buf[fromStart+(fromBuf->gapEnd-fromBuf->gapStart)],
389 length);
390 } else {
391 part1Length = fromBuf->gapStart - fromStart;
392 memcpy(&toBuf->buf[toPos], &fromBuf->buf[fromStart], part1Length);
393 memcpy(&toBuf->buf[toPos+part1Length], &fromBuf->buf[fromBuf->gapEnd],
394 length-part1Length);
396 toBuf->gapStart += length;
397 toBuf->length += length;
398 updateSelections(toBuf, toPos, 0, length);
402 ** Insert "text" columnwise into buffer starting at displayed character
403 ** position "column" on the line beginning at "startPos". Opens a rectangular
404 ** space the width and height of "text", by moving all text to the right of
405 ** "column" right. If charsInserted and charsDeleted are not NULL, the
406 ** number of characters inserted and deleted in the operation (beginning
407 ** at startPos) are returned in these arguments
409 void BufInsertCol(textBuffer *buf, int column, int startPos, const char *text,
410 int *charsInserted, int *charsDeleted)
412 int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
413 char *deletedText;
415 nLines = countLines(text);
416 lineStartPos = BufStartOfLine(buf, startPos);
417 nDeleted = BufEndOfLine(buf, BufCountForwardNLines(buf, startPos, nLines)) -
418 lineStartPos;
419 callPreDeleteCBs(buf, lineStartPos, nDeleted);
420 deletedText = BufGetRange(buf, lineStartPos, lineStartPos + nDeleted);
421 insertCol(buf, column, lineStartPos, text, &insertDeleted, &nInserted,
422 &buf->cursorPosHint);
423 if (nDeleted != insertDeleted)
424 fprintf(stderr, "NEdit internal consistency check ins1 failed");
425 callModifyCBs(buf, lineStartPos, nDeleted, nInserted, 0, deletedText);
426 XtFree(deletedText);
427 if (charsInserted != NULL)
428 *charsInserted = nInserted;
429 if (charsDeleted != NULL)
430 *charsDeleted = nDeleted;
434 ** Overlay "text" between displayed character positions "rectStart" and
435 ** "rectEnd" on the line beginning at "startPos". If charsInserted and
436 ** charsDeleted are not NULL, the number of characters inserted and deleted
437 ** in the operation (beginning at startPos) are returned in these arguments.
438 ** If rectEnd equals -1, the width of the inserted text is measured first.
440 void BufOverlayRect(textBuffer *buf, int startPos, int rectStart,
441 int rectEnd, const char *text, int *charsInserted, int *charsDeleted)
443 int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
444 char *deletedText;
446 nLines = countLines(text);
447 lineStartPos = BufStartOfLine(buf, startPos);
448 if(rectEnd == -1)
449 rectEnd = rectStart + textWidth(text, buf->tabDist, buf->nullSubsChar);
450 lineStartPos = BufStartOfLine(buf, startPos);
451 nDeleted = BufEndOfLine(buf, BufCountForwardNLines(buf, startPos, nLines)) -
452 lineStartPos;
453 callPreDeleteCBs(buf, lineStartPos, nDeleted);
454 deletedText = BufGetRange(buf, lineStartPos, lineStartPos + nDeleted);
455 overlayRect(buf, lineStartPos, rectStart, rectEnd, text, &insertDeleted,
456 &nInserted, &buf->cursorPosHint);
457 if (nDeleted != insertDeleted)
458 fprintf(stderr, "NEdit internal consistency check ovly1 failed");
459 callModifyCBs(buf, lineStartPos, nDeleted, nInserted, 0, deletedText);
460 XtFree(deletedText);
461 if (charsInserted != NULL)
462 *charsInserted = nInserted;
463 if (charsDeleted != NULL)
464 *charsDeleted = nDeleted;
468 ** Replace a rectangular area in buf, given by "start", "end", "rectStart",
469 ** and "rectEnd", with "text". If "text" is vertically longer than the
470 ** rectangle, add extra lines to make room for it.
472 void BufReplaceRect(textBuffer *buf, int start, int end, int rectStart,
473 int rectEnd, const char *text)
475 char *deletedText;
476 char *insText=NULL;
477 int i, nInsertedLines, nDeletedLines, insLen, hint;
478 int insertDeleted, insertInserted, deleteInserted;
479 int linesPadded = 0;
481 /* Make sure start and end refer to complete lines, since the
482 columnar delete and insert operations will replace whole lines */
483 start = BufStartOfLine(buf, start);
484 end = BufEndOfLine(buf, end);
486 callPreDeleteCBs(buf, start, end-start);
488 /* If more lines will be deleted than inserted, pad the inserted text
489 with newlines to make it as long as the number of deleted lines. This
490 will indent all of the text to the right of the rectangle to the same
491 column. If more lines will be inserted than deleted, insert extra
492 lines in the buffer at the end of the rectangle to make room for the
493 additional lines in "text" */
494 nInsertedLines = countLines(text);
495 nDeletedLines = BufCountLines(buf, start, end);
496 if (nInsertedLines < nDeletedLines) {
497 char *insPtr;
499 insLen = strlen(text);
500 insText = XtMalloc(insLen + nDeletedLines - nInsertedLines + 1);
501 strcpy(insText, text);
502 insPtr = insText + insLen;
503 for (i=0; i<nDeletedLines-nInsertedLines; i++)
504 *insPtr++ = '\n';
505 *insPtr = '\0';
506 } else if (nDeletedLines < nInsertedLines) {
507 linesPadded = nInsertedLines-nDeletedLines;
508 for (i=0; i<linesPadded; i++)
509 insert(buf, end, "\n");
510 } else /* nDeletedLines == nInsertedLines */ {
513 /* Save a copy of the text which will be modified for the modify CBs */
514 deletedText = BufGetRange(buf, start, end);
516 /* Delete then insert */
517 deleteRect(buf, start, end, rectStart, rectEnd, &deleteInserted, &hint);
518 if (insText) {
519 insertCol(buf, rectStart, start, insText, &insertDeleted, &insertInserted,
520 &buf->cursorPosHint);
521 XtFree(insText);
523 else
524 insertCol(buf, rectStart, start, text, &insertDeleted, &insertInserted,
525 &buf->cursorPosHint);
527 /* Figure out how many chars were inserted and call modify callbacks */
528 if (insertDeleted != deleteInserted + linesPadded)
529 fprintf(stderr, "NEdit: internal consistency check repl1 failed\n");
530 callModifyCBs(buf, start, end-start, insertInserted, 0, deletedText);
531 XtFree(deletedText);
535 ** Remove a rectangular swath of characters between character positions start
536 ** and end and horizontal displayed-character offsets rectStart and rectEnd.
538 void BufRemoveRect(textBuffer *buf, int start, int end, int rectStart,
539 int rectEnd)
541 char *deletedText;
542 int nInserted;
544 start = BufStartOfLine(buf, start);
545 end = BufEndOfLine(buf, end);
546 callPreDeleteCBs(buf, start, end-start);
547 deletedText = BufGetRange(buf, start, end);
548 deleteRect(buf, start, end, rectStart, rectEnd, &nInserted,
549 &buf->cursorPosHint);
550 callModifyCBs(buf, start, end-start, nInserted, 0, deletedText);
551 XtFree(deletedText);
555 ** Clear a rectangular "hole" out of the buffer between character positions
556 ** start and end and horizontal displayed-character offsets rectStart and
557 ** rectEnd.
559 void BufClearRect(textBuffer *buf, int start, int end, int rectStart,
560 int rectEnd)
562 int i, nLines;
563 char *newlineString;
565 nLines = BufCountLines(buf, start, end);
566 newlineString = XtMalloc(nLines+1);
567 for (i=0; i<nLines; i++)
568 newlineString[i] = '\n';
569 newlineString[i] = '\0';
570 BufOverlayRect(buf, start, rectStart, rectEnd, newlineString,
571 NULL, NULL);
572 XtFree(newlineString);
575 char *BufGetTextInRect(textBuffer *buf, int start, int end,
576 int rectStart, int rectEnd)
578 int lineStart, selLeft, selRight, len;
579 char *textOut, *textIn, *outPtr, *retabbedStr;
581 start = BufStartOfLine(buf, start);
582 end = BufEndOfLine(buf, end);
583 textOut = XtMalloc((end - start) + 1);
584 lineStart = start;
585 outPtr = textOut;
586 while (lineStart <= end) {
587 findRectSelBoundariesForCopy(buf, lineStart, rectStart, rectEnd,
588 &selLeft, &selRight);
589 textIn = BufGetRange(buf, selLeft, selRight);
590 len = selRight - selLeft;
591 memcpy(outPtr, textIn, len);
592 XtFree(textIn);
593 outPtr += len;
594 lineStart = BufEndOfLine(buf, selRight) + 1;
595 *outPtr++ = '\n';
597 if (outPtr != textOut)
598 outPtr--; /* don't leave trailing newline */
599 *outPtr = '\0';
601 /* If necessary, realign the tabs in the selection as if the text were
602 positioned at the left margin */
603 retabbedStr = realignTabs(textOut, rectStart, 0, buf->tabDist,
604 buf->useTabs, buf->nullSubsChar, &len);
605 XtFree(textOut);
606 return retabbedStr;
610 ** Get the hardware tab distance used by all displays for this buffer,
611 ** and used in computing offsets for rectangular selection operations.
613 int BufGetTabDistance(textBuffer *buf)
615 return buf->tabDist;
619 ** Set the hardware tab distance used by all displays for this buffer,
620 ** and used in computing offsets for rectangular selection operations.
622 void BufSetTabDistance(textBuffer *buf, int tabDist)
624 char *deletedText;
626 /* First call the pre-delete callbacks with the previous tab setting
627 still active. */
628 callPreDeleteCBs(buf, 0, buf->length);
630 /* Change the tab setting */
631 buf->tabDist = tabDist;
633 /* Force any display routines to redisplay everything (unfortunately,
634 this means copying the whole buffer contents to provide "deletedText" */
635 deletedText = BufGetAll(buf);
636 callModifyCBs(buf, 0, buf->length, buf->length, 0, deletedText);
637 XtFree(deletedText);
640 void BufCheckDisplay(textBuffer *buf, int start, int end)
642 /* just to make sure colors in the selected region are up to date */
643 callModifyCBs(buf, start, 0, 0, end-start, NULL);
646 void BufSelect(textBuffer *buf, int start, int end)
648 selection oldSelection = buf->primary;
650 setSelection(&buf->primary, start, end);
651 redisplaySelection(buf, &oldSelection, &buf->primary);
654 void BufUnselect(textBuffer *buf)
656 selection oldSelection = buf->primary;
658 buf->primary.selected = False;
659 buf->primary.zeroWidth = False;
660 redisplaySelection(buf, &oldSelection, &buf->primary);
663 void BufRectSelect(textBuffer *buf, int start, int end, int rectStart,
664 int rectEnd)
666 selection oldSelection = buf->primary;
668 setRectSelect(&buf->primary, start, end, rectStart, rectEnd);
669 redisplaySelection(buf, &oldSelection, &buf->primary);
672 int BufGetSelectionPos(textBuffer *buf, int *start, int *end,
673 int *isRect, int *rectStart, int *rectEnd)
675 return getSelectionPos(&buf->primary, start, end, isRect, rectStart,
676 rectEnd);
679 /* Same as above, but also returns TRUE for empty selections */
680 int BufGetEmptySelectionPos(textBuffer *buf, int *start, int *end,
681 int *isRect, int *rectStart, int *rectEnd)
683 return getSelectionPos(&buf->primary, start, end, isRect, rectStart,
684 rectEnd) || buf->primary.zeroWidth;
687 char *BufGetSelectionText(textBuffer *buf)
689 return getSelectionText(buf, &buf->primary);
692 void BufRemoveSelected(textBuffer *buf)
694 removeSelected(buf, &buf->primary);
697 void BufReplaceSelected(textBuffer *buf, const char *text)
699 replaceSelected(buf, &buf->primary, text);
702 void BufSecondarySelect(textBuffer *buf, int start, int end)
704 selection oldSelection = buf->secondary;
706 setSelection(&buf->secondary, start, end);
707 redisplaySelection(buf, &oldSelection, &buf->secondary);
710 void BufSecondaryUnselect(textBuffer *buf)
712 selection oldSelection = buf->secondary;
714 buf->secondary.selected = False;
715 buf->secondary.zeroWidth = False;
716 redisplaySelection(buf, &oldSelection, &buf->secondary);
719 void BufSecRectSelect(textBuffer *buf, int start, int end,
720 int rectStart, int rectEnd)
722 selection oldSelection = buf->secondary;
724 setRectSelect(&buf->secondary, start, end, rectStart, rectEnd);
725 redisplaySelection(buf, &oldSelection, &buf->secondary);
728 int BufGetSecSelectPos(textBuffer *buf, int *start, int *end,
729 int *isRect, int *rectStart, int *rectEnd)
731 return getSelectionPos(&buf->secondary, start, end, isRect, rectStart,
732 rectEnd);
735 char *BufGetSecSelectText(textBuffer *buf)
737 return getSelectionText(buf, &buf->secondary);
740 void BufRemoveSecSelect(textBuffer *buf)
742 removeSelected(buf, &buf->secondary);
745 void BufReplaceSecSelect(textBuffer *buf, const char *text)
747 replaceSelected(buf, &buf->secondary, text);
750 void BufHighlight(textBuffer *buf, int start, int end)
752 selection oldSelection = buf->highlight;
754 setSelection(&buf->highlight, start, end);
755 redisplaySelection(buf, &oldSelection, &buf->highlight);
758 void BufUnhighlight(textBuffer *buf)
760 selection oldSelection = buf->highlight;
762 buf->highlight.selected = False;
763 buf->highlight.zeroWidth = False;
764 redisplaySelection(buf, &oldSelection, &buf->highlight);
767 void BufRectHighlight(textBuffer *buf, int start, int end,
768 int rectStart, int rectEnd)
770 selection oldSelection = buf->highlight;
772 setRectSelect(&buf->highlight, start, end, rectStart, rectEnd);
773 redisplaySelection(buf, &oldSelection, &buf->highlight);
776 int BufGetHighlightPos(textBuffer *buf, int *start, int *end,
777 int *isRect, int *rectStart, int *rectEnd)
779 return getSelectionPos(&buf->highlight, start, end, isRect, rectStart,
780 rectEnd);
783 char *BufGetHighlightText(textBuffer *buf)
785 return getSelectionText(buf, &buf->highlight);
789 ** Add a callback routine to be called when the buffer is modified
791 void BufAddModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
792 void *cbArg)
794 bufModifyCallbackProc *newModifyProcs;
795 void **newCBArgs;
796 int i;
798 newModifyProcs = (bufModifyCallbackProc *)
799 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs+1));
800 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs+1));
801 for (i=0; i<buf->nModifyProcs; i++) {
802 newModifyProcs[i] = buf->modifyProcs[i];
803 newCBArgs[i] = buf->cbArgs[i];
805 if (buf->nModifyProcs != 0) {
806 XtFree((char *)buf->modifyProcs);
807 XtFree((char *)buf->cbArgs);
809 newModifyProcs[buf->nModifyProcs] = bufModifiedCB;
810 newCBArgs[buf->nModifyProcs] = cbArg;
811 buf->nModifyProcs++;
812 buf->modifyProcs = newModifyProcs;
813 buf->cbArgs = newCBArgs;
816 void BufRemoveModifyCB(textBuffer *buf, bufModifyCallbackProc bufModifiedCB,
817 void *cbArg)
819 int i, toRemove = -1;
820 bufModifyCallbackProc *newModifyProcs;
821 void **newCBArgs;
823 /* find the matching callback to remove */
824 for (i=0; i<buf->nModifyProcs; i++) {
825 if (buf->modifyProcs[i] == bufModifiedCB && buf->cbArgs[i] == cbArg) {
826 toRemove = i;
827 break;
830 if (toRemove == -1) {
831 fprintf(stderr, "NEdit Internal Error: Can't find modify CB to remove\n");
832 return;
835 /* Allocate new lists for remaining callback procs and args (if
836 any are left) */
837 buf->nModifyProcs--;
838 if (buf->nModifyProcs == 0) {
839 buf->nModifyProcs = 0;
840 XtFree((char *)buf->modifyProcs);
841 buf->modifyProcs = NULL;
842 XtFree((char *)buf->cbArgs);
843 buf->cbArgs = NULL;
844 return;
846 newModifyProcs = (bufModifyCallbackProc *)
847 XtMalloc(sizeof(bufModifyCallbackProc *) * (buf->nModifyProcs));
848 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nModifyProcs));
850 /* copy out the remaining members and free the old lists */
851 for (i=0; i<toRemove; i++) {
852 newModifyProcs[i] = buf->modifyProcs[i];
853 newCBArgs[i] = buf->cbArgs[i];
855 for (; i<buf->nModifyProcs; i++) {
856 newModifyProcs[i] = buf->modifyProcs[i+1];
857 newCBArgs[i] = buf->cbArgs[i+1];
859 XtFree((char *)buf->modifyProcs);
860 XtFree((char *)buf->cbArgs);
861 buf->modifyProcs = newModifyProcs;
862 buf->cbArgs = newCBArgs;
866 ** Add a callback routine to be called before text is deleted from the buffer.
868 void BufAddPreDeleteCB(textBuffer *buf, bufPreDeleteCallbackProc bufPreDeleteCB,
869 void *cbArg)
871 bufPreDeleteCallbackProc *newPreDeleteProcs;
872 void **newCBArgs;
873 int i;
875 newPreDeleteProcs = (bufPreDeleteCallbackProc *)
876 XtMalloc(sizeof(bufPreDeleteCallbackProc *) * (buf->nPreDeleteProcs+1));
877 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nPreDeleteProcs+1));
878 for (i=0; i<buf->nPreDeleteProcs; i++) {
879 newPreDeleteProcs[i] = buf->preDeleteProcs[i];
880 newCBArgs[i] = buf->preDeleteCbArgs[i];
882 if (buf->nPreDeleteProcs != 0) {
883 XtFree((char *)buf->preDeleteProcs);
884 XtFree((char *)buf->preDeleteCbArgs);
886 newPreDeleteProcs[buf->nPreDeleteProcs] = bufPreDeleteCB;
887 newCBArgs[buf->nPreDeleteProcs] = cbArg;
888 buf->nPreDeleteProcs++;
889 buf->preDeleteProcs = newPreDeleteProcs;
890 buf->preDeleteCbArgs = newCBArgs;
893 void BufRemovePreDeleteCB(textBuffer *buf, bufPreDeleteCallbackProc bufPreDeleteCB,
894 void *cbArg)
896 int i, toRemove = -1;
897 bufPreDeleteCallbackProc *newPreDeleteProcs;
898 void **newCBArgs;
900 /* find the matching callback to remove */
901 for (i=0; i<buf->nPreDeleteProcs; i++) {
902 if (buf->preDeleteProcs[i] == bufPreDeleteCB &&
903 buf->preDeleteCbArgs[i] == cbArg) {
904 toRemove = i;
905 break;
908 if (toRemove == -1) {
909 fprintf(stderr, "NEdit Internal Error: Can't find pre-delete CB to remove\n");
910 return;
913 /* Allocate new lists for remaining callback procs and args (if
914 any are left) */
915 buf->nPreDeleteProcs--;
916 if (buf->nPreDeleteProcs == 0) {
917 buf->nPreDeleteProcs = 0;
918 XtFree((char *)buf->preDeleteProcs);
919 buf->preDeleteProcs = NULL;
920 XtFree((char *)buf->preDeleteCbArgs);
921 buf->preDeleteCbArgs = NULL;
922 return;
924 newPreDeleteProcs = (bufPreDeleteCallbackProc *)
925 XtMalloc(sizeof(bufPreDeleteCallbackProc *) * (buf->nPreDeleteProcs));
926 newCBArgs = (void *)XtMalloc(sizeof(void *) * (buf->nPreDeleteProcs));
928 /* copy out the remaining members and free the old lists */
929 for (i=0; i<toRemove; i++) {
930 newPreDeleteProcs[i] = buf->preDeleteProcs[i];
931 newCBArgs[i] = buf->preDeleteCbArgs[i];
933 for (; i<buf->nPreDeleteProcs; i++) {
934 newPreDeleteProcs[i] = buf->preDeleteProcs[i+1];
935 newCBArgs[i] = buf->preDeleteCbArgs[i+1];
937 XtFree((char *)buf->preDeleteProcs);
938 XtFree((char *)buf->preDeleteCbArgs);
939 buf->preDeleteProcs = newPreDeleteProcs;
940 buf->preDeleteCbArgs = newCBArgs;
944 ** Return the text from the entire line containing position "pos"
946 char *BufGetLineText(textBuffer *buf, int pos)
948 return BufGetRange(buf, BufStartOfLine(buf, pos), BufEndOfLine(buf, pos));
952 ** Find the position of the start of the line containing position "pos"
954 int BufStartOfLine(textBuffer *buf, int pos)
956 int startPos;
958 if (!searchBackward(buf, pos, '\n', &startPos))
959 return 0;
960 return startPos + 1;
965 ** Find the position of the end of the line containing position "pos"
966 ** (which is either a pointer to the newline character ending the line,
967 ** or a pointer to one character beyond the end of the buffer)
969 int BufEndOfLine(textBuffer *buf, int pos)
971 int endPos;
973 if (!searchForward(buf, pos, '\n', &endPos))
974 endPos = buf->length;
975 return endPos;
979 ** Get a character from the text buffer expanded into it's screen
980 ** representation (which may be several characters for a tab or a
981 ** control code). Returns the number of characters written to "outStr".
982 ** "indent" is the number of characters from the start of the line
983 ** for figuring tabs. Output string is guranteed to be shorter or
984 ** equal in length to MAX_EXP_CHAR_LEN
986 int BufGetExpandedChar(textBuffer *buf, int pos, int indent, char *outStr)
988 return BufExpandCharacter(BufGetCharacter(buf, pos), indent, outStr,
989 buf->tabDist, buf->nullSubsChar);
993 ** Expand a single character from the text buffer into it's screen
994 ** representation (which may be several characters for a tab or a
995 ** control code). Returns the number of characters added to "outStr".
996 ** "indent" is the number of characters from the start of the line
997 ** for figuring tabs. Output string is guranteed to be shorter or
998 ** equal in length to MAX_EXP_CHAR_LEN
1000 int BufExpandCharacter(char c, int indent, char *outStr, int tabDist,
1001 char nullSubsChar)
1003 int i, nSpaces;
1005 /* Convert tabs to spaces */
1006 if (c == '\t') {
1007 nSpaces = tabDist - (indent % tabDist);
1008 for (i=0; i<nSpaces; i++)
1009 outStr[i] = ' ';
1010 return nSpaces;
1013 /* Convert ASCII (and EBCDIC in the __MVS__ (OS/390) case) control
1014 codes to readable character sequences */
1015 if (c == nullSubsChar) {
1016 sprintf(outStr, "<nul>");
1017 return 5;
1019 #ifdef __MVS__
1020 if (((unsigned char)c) <= 63) {
1021 sprintf(outStr, "<%s>", ControlCodeTable[(unsigned char)c]);
1022 return strlen(outStr);
1024 #else
1025 if (((unsigned char)c) <= 31) {
1026 sprintf(outStr, "<%s>", ControlCodeTable[(unsigned char)c]);
1027 return strlen(outStr);
1028 } else if (c == 127) {
1029 sprintf(outStr, "<del>");
1030 return 5;
1032 #endif
1034 /* Otherwise, just return the character */
1035 *outStr = c;
1036 return 1;
1040 ** Return the length in displayed characters of character "c" expanded
1041 ** for display (as discussed above in BufGetExpandedChar). If the
1042 ** buffer for which the character width is being measured is doing null
1043 ** substitution, nullSubsChar should be passed as that character (or nul
1044 ** to ignore).
1046 int BufCharWidth(char c, int indent, int tabDist, char nullSubsChar)
1048 /* Note, this code must parallel that in BufExpandCharacter */
1049 if (c == nullSubsChar)
1050 return 5;
1051 else if (c == '\t')
1052 return tabDist - (indent % tabDist);
1053 else if (((unsigned char)c) <= 31)
1054 return strlen(ControlCodeTable[(unsigned char)c]) + 2;
1055 else if (c == 127)
1056 return 5;
1057 return 1;
1061 ** Count the number of displayed characters between buffer position
1062 ** "lineStartPos" and "targetPos". (displayed characters are the characters
1063 ** shown on the screen to represent characters in the buffer, where tabs and
1064 ** control characters are expanded)
1066 int BufCountDispChars(textBuffer *buf, int lineStartPos, int targetPos)
1068 int pos, charCount = 0;
1069 char expandedChar[MAX_EXP_CHAR_LEN];
1071 pos = lineStartPos;
1072 while (pos < targetPos)
1073 charCount += BufGetExpandedChar(buf, pos++, charCount, expandedChar);
1074 return charCount;
1078 ** Count forward from buffer position "startPos" in displayed characters
1079 ** (displayed characters are the characters shown on the screen to represent
1080 ** characters in the buffer, where tabs and control characters are expanded)
1082 int BufCountForwardDispChars(textBuffer *buf, int lineStartPos, int nChars)
1084 int pos, charCount = 0;
1085 char c;
1087 pos = lineStartPos;
1088 while (charCount < nChars && pos < buf->length) {
1089 c = BufGetCharacter(buf, pos);
1090 if (c == '\n')
1091 return pos;
1092 charCount += BufCharWidth(c, charCount, buf->tabDist,buf->nullSubsChar);
1093 pos++;
1095 return pos;
1099 ** Count the number of newlines between startPos and endPos in buffer "buf".
1100 ** The character at position "endPos" is not counted.
1102 int BufCountLines(textBuffer *buf, int startPos, int endPos)
1104 int pos, gapLen = buf->gapEnd - buf->gapStart;
1105 int lineCount = 0;
1107 pos = startPos;
1108 while (pos < buf->gapStart) {
1109 if (pos == endPos)
1110 return lineCount;
1111 if (buf->buf[pos++] == '\n')
1112 lineCount++;
1114 while (pos < buf->length) {
1115 if (pos == endPos)
1116 return lineCount;
1117 if (buf->buf[pos++ + gapLen] == '\n')
1118 lineCount++;
1120 return lineCount;
1124 ** Find the first character of the line "nLines" forward from "startPos"
1125 ** in "buf" and return its position
1127 int BufCountForwardNLines(textBuffer *buf, int startPos, int nLines)
1129 int pos, gapLen = buf->gapEnd - buf->gapStart;
1130 int lineCount = 0;
1132 if (nLines == 0)
1133 return startPos;
1135 pos = startPos;
1136 while (pos < buf->gapStart) {
1137 if (buf->buf[pos++] == '\n') {
1138 lineCount++;
1139 if (lineCount == nLines)
1140 return pos;
1143 while (pos < buf->length) {
1144 if (buf->buf[pos++ + gapLen] == '\n') {
1145 lineCount++;
1146 if (lineCount >= nLines)
1147 return pos;
1150 return pos;
1154 ** Find the position of the first character of the line "nLines" backwards
1155 ** from "startPos" (not counting the character pointed to by "startpos" if
1156 ** that is a newline) in "buf". nLines == 0 means find the beginning of
1157 ** the line
1159 int BufCountBackwardNLines(textBuffer *buf, int startPos, int nLines)
1161 int pos, gapLen = buf->gapEnd - buf->gapStart;
1162 int lineCount = -1;
1164 pos = startPos - 1;
1165 if (pos <= 0)
1166 return 0;
1168 while (pos >= buf->gapStart) {
1169 if (buf->buf[pos + gapLen] == '\n') {
1170 if (++lineCount >= nLines)
1171 return pos + 1;
1173 pos--;
1175 while (pos >= 0) {
1176 if (buf->buf[pos] == '\n') {
1177 if (++lineCount >= nLines)
1178 return pos + 1;
1180 pos--;
1182 return 0;
1186 ** Search forwards in buffer "buf" for characters in "searchChars", starting
1187 ** with the character "startPos", and returning the result in "foundPos"
1188 ** returns True if found, False if not.
1190 int BufSearchForward(textBuffer *buf, int startPos, const char *searchChars,
1191 int *foundPos)
1193 int pos, gapLen = buf->gapEnd - buf->gapStart;
1194 const char *c;
1196 pos = startPos;
1197 while (pos < buf->gapStart) {
1198 for (c=searchChars; *c!='\0'; c++) {
1199 if (buf->buf[pos] == *c) {
1200 *foundPos = pos;
1201 return True;
1204 pos++;
1206 while (pos < buf->length) {
1207 for (c=searchChars; *c!='\0'; c++) {
1208 if (buf->buf[pos + gapLen] == *c) {
1209 *foundPos = pos;
1210 return True;
1213 pos++;
1215 *foundPos = buf->length;
1216 return False;
1220 ** Search backwards in buffer "buf" for characters in "searchChars", starting
1221 ** with the character BEFORE "startPos", returning the result in "foundPos"
1222 ** returns True if found, False if not.
1224 int BufSearchBackward(textBuffer *buf, int startPos, const char *searchChars,
1225 int *foundPos)
1227 int pos, gapLen = buf->gapEnd - buf->gapStart;
1228 const char *c;
1230 if (startPos == 0) {
1231 *foundPos = 0;
1232 return False;
1234 pos = startPos == 0 ? 0 : startPos - 1;
1235 while (pos >= buf->gapStart) {
1236 for (c=searchChars; *c!='\0'; c++) {
1237 if (buf->buf[pos + gapLen] == *c) {
1238 *foundPos = pos;
1239 return True;
1242 pos--;
1244 while (pos >= 0) {
1245 for (c=searchChars; *c!='\0'; c++) {
1246 if (buf->buf[pos] == *c) {
1247 *foundPos = pos;
1248 return True;
1251 pos--;
1253 *foundPos = 0;
1254 return False;
1258 ** A horrible design flaw in NEdit (from the very start, before we knew that
1259 ** NEdit would become so popular), is that it uses C NULL terminated strings
1260 ** to hold text. This means editing text containing NUL characters is not
1261 ** possible without special consideration. Here is the special consideration.
1262 ** The routines below maintain a special substitution-character which stands
1263 ** in for a null, and translates strings an buffers back and forth from/to
1264 ** the substituted form, figure out what to substitute, and figure out
1265 ** when we're in over our heads and no translation is possible.
1269 ** The primary routine for integrating new text into a text buffer with
1270 ** substitution of another character for ascii nuls. This substitutes null
1271 ** characters in the string in preparation for being copied or replaced
1272 ** into the buffer, and if neccessary, adjusts the buffer as well, in the
1273 ** event that the string contains the character it is currently using for
1274 ** substitution. Returns False, if substitution is no longer possible
1275 ** because all non-printable characters are already in use.
1277 int BufSubstituteNullChars(char *string, int length, textBuffer *buf)
1279 char histogram[256];
1281 /* Find out what characters the string contains */
1282 histogramCharacters(string, length, histogram, True);
1284 /* Does the string contain the null-substitute character? If so, re-
1285 histogram the buffer text to find a character which is ok in both the
1286 string and the buffer, and change the buffer's null-substitution
1287 character. If none can be found, give up and return False */
1288 if (histogram[(unsigned char)buf->nullSubsChar] != 0) {
1289 char *bufString, newSubsChar;
1290 bufString = BufGetAll(buf);
1291 histogramCharacters(bufString, buf->length, histogram, False);
1292 newSubsChar = chooseNullSubsChar(histogram);
1293 if (newSubsChar == '\0') {
1294 XtFree(bufString);
1295 return False;
1297 subsChars(bufString, buf->length, buf->nullSubsChar, newSubsChar);
1298 delete(buf, 0, buf->length);
1299 insert(buf, 0, bufString);
1300 XtFree(bufString);
1301 buf->nullSubsChar = newSubsChar;
1304 /* If the string contains null characters, substitute them with the
1305 buffer's null substitution character */
1306 if (histogram[0] != 0)
1307 subsChars(string, length, '\0', buf->nullSubsChar);
1308 return True;
1312 ** Convert strings obtained from buffers which contain null characters, which
1313 ** have been substituted for by a special substitution character, back to
1314 ** a null-containing string. There is no time penalty for calling this
1315 ** routine if no substitution has been done.
1317 void BufUnsubstituteNullChars(char *string, textBuffer *buf)
1319 register char *c, subsChar = buf->nullSubsChar;
1321 if (subsChar == '\0')
1322 return;
1323 for (c=string; *c != '\0'; c++)
1324 if (*c == subsChar)
1325 *c = '\0';
1329 ** Compares len Bytes contained in buf starting at Position pos with
1330 ** the contens of cmpText. Returns 0 if there are no differences,
1331 ** != 0 otherwise.
1334 int BufCmp(textBuffer * buf, int pos, int len, const char *cmpText)
1336 int posEnd;
1337 int part1Length;
1338 int result;
1340 posEnd = pos + len;
1341 if (posEnd > buf->length) {
1342 return (1);
1344 if (pos < 0) {
1345 return (-1);
1348 if (posEnd <= buf->gapStart) {
1349 return (strncmp(&(buf->buf[pos]), cmpText, len));
1350 } else if (pos >= buf->gapStart) {
1351 return (strncmp (&buf->buf[pos + (buf->gapEnd - buf->gapStart)],
1352 cmpText, len));
1353 } else {
1354 part1Length = buf->gapStart - pos;
1355 result = strncmp(&buf->buf[pos], cmpText, part1Length);
1356 if (result) {
1357 return (result);
1359 return (strncmp(&buf->buf[buf->gapEnd], &cmpText[part1Length],
1360 len - part1Length));
1365 ** Create a pseudo-histogram of the characters in a string (don't actually
1366 ** count, because we don't want overflow, just mark the character's presence
1367 ** with a 1). If init is true, initialize the histogram before acumulating.
1368 ** if not, add the new data to an existing histogram.
1370 static void histogramCharacters(const char *string, int length, char hist[256],
1371 int init)
1373 int i;
1374 const char *c;
1376 if (init)
1377 for (i=0; i<256; i++)
1378 hist[i] = 0;
1379 for (c=string; c < &string[length]; c++)
1380 hist[*((unsigned char *)c)] |= 1;
1384 ** Substitute fromChar with toChar in string.
1386 static void subsChars(char *string, int length, char fromChar, char toChar)
1388 char *c;
1390 for (c=string; c < &string[length]; c++)
1391 if (*c == fromChar) *c = toChar;
1395 ** Search through ascii control characters in histogram in order of least
1396 ** likelihood of use, find an unused character to use as a stand-in for a
1397 ** null. If the character set is full (no available characters outside of
1398 ** the printable set, return the null character.
1400 static char chooseNullSubsChar(char hist[256])
1402 #define N_REPLACEMENTS 25
1403 static char replacements[N_REPLACEMENTS] = {1,2,3,4,5,6,14,15,16,17,18,19,
1404 20,21,22,23,24,25,26,28,29,30,31,11,7};
1405 int i;
1406 for (i = 0; i < N_REPLACEMENTS; i++)
1407 if (hist[(unsigned char)replacements[i]] == 0)
1408 return replacements[i];
1409 return '\0';
1413 ** Internal (non-redisplaying) version of BufInsert. Returns the length of
1414 ** text inserted (this is just strlen(text), however this calculation can be
1415 ** expensive and the length will be required by any caller who will continue
1416 ** on to call redisplay). pos must be contiguous with the existing text in
1417 ** the buffer (i.e. not past the end).
1419 static int insert(textBuffer *buf, int pos, const char *text)
1421 int length = strlen(text);
1423 /* Prepare the buffer to receive the new text. If the new text fits in
1424 the current buffer, just move the gap (if necessary) to where
1425 the text should be inserted. If the new text is too large, reallocate
1426 the buffer with a gap large enough to accomodate the new text and a
1427 gap of PREFERRED_GAP_SIZE */
1428 if (length > buf->gapEnd - buf->gapStart)
1429 reallocateBuf(buf, pos, length + PREFERRED_GAP_SIZE);
1430 else if (pos != buf->gapStart)
1431 moveGap(buf, pos);
1433 /* Insert the new text (pos now corresponds to the start of the gap) */
1434 memcpy(&buf->buf[pos], text, length);
1435 buf->gapStart += length;
1436 buf->length += length;
1437 updateSelections(buf, pos, 0, length);
1439 return length;
1443 ** Internal (non-redisplaying) version of BufRemove. Removes the contents
1444 ** of the buffer between start and end (and moves the gap to the site of
1445 ** the delete).
1447 static void delete(textBuffer *buf, int start, int end)
1449 /* if the gap is not contiguous to the area to remove, move it there */
1450 if (start > buf->gapStart)
1451 moveGap(buf, start);
1452 else if (end < buf->gapStart)
1453 moveGap(buf, end);
1455 /* expand the gap to encompass the deleted characters */
1456 buf->gapEnd += end - buf->gapStart;
1457 buf->gapStart -= buf->gapStart - start;
1459 /* update the length */
1460 buf->length -= end - start;
1462 /* fix up any selections which might be affected by the change */
1463 updateSelections(buf, start, end-start, 0);
1467 ** Insert a column of text without calling the modify callbacks. Note that
1468 ** in some pathological cases, inserting can actually decrease the size of
1469 ** the buffer because of spaces being coalesced into tabs. "nDeleted" and
1470 ** "nInserted" return the number of characters deleted and inserted beginning
1471 ** at the start of the line containing "startPos". "endPos" returns buffer
1472 ** position of the lower left edge of the inserted column (as a hint for
1473 ** routines which need to set a cursor position).
1475 static void insertCol(textBuffer *buf, int column, int startPos,
1476 const char *insText, int *nDeleted, int *nInserted, int *endPos)
1478 int nLines, start, end, insWidth, lineStart, lineEnd;
1479 int expReplLen, expInsLen, len, endOffset;
1480 char *outStr, *outPtr, *line, *replText, *expText, *insLine;
1481 const char *insPtr;
1483 if (column < 0)
1484 column = 0;
1486 /* Allocate a buffer for the replacement string large enough to hold
1487 possibly expanded tabs in both the inserted text and the replaced
1488 area, as well as per line: 1) an additional 2*MAX_EXP_CHAR_LEN
1489 characters for padding where tabs and control characters cross the
1490 column of the selection, 2) up to "column" additional spaces per
1491 line for padding out to the position of "column", 3) padding up
1492 to the width of the inserted text if that must be padded to align
1493 the text beyond the inserted column. (Space for additional
1494 newlines if the inserted text extends beyond the end of the buffer
1495 is counted with the length of insText) */
1496 start = BufStartOfLine(buf, startPos);
1497 nLines = countLines(insText) + 1;
1498 insWidth = textWidth(insText, buf->tabDist, buf->nullSubsChar);
1499 end = BufEndOfLine(buf, BufCountForwardNLines(buf, start, nLines-1));
1500 replText = BufGetRange(buf, start, end);
1501 expText = expandTabs(replText, 0, buf->tabDist, buf->nullSubsChar,
1502 &expReplLen);
1503 XtFree(replText);
1504 XtFree(expText);
1505 expText = expandTabs(insText, 0, buf->tabDist, buf->nullSubsChar,
1506 &expInsLen);
1507 XtFree(expText);
1508 outStr = XtMalloc(expReplLen + expInsLen +
1509 nLines * (column + insWidth + MAX_EXP_CHAR_LEN) + 1);
1511 /* Loop over all lines in the buffer between start and end inserting
1512 text at column, splitting tabs and adding padding appropriately */
1513 outPtr = outStr;
1514 lineStart = start;
1515 insPtr = insText;
1516 while (True) {
1517 lineEnd = BufEndOfLine(buf, lineStart);
1518 line = BufGetRange(buf, lineStart, lineEnd);
1519 insLine = copyLine(insPtr, &len);
1520 insPtr += len;
1521 insertColInLine(line, insLine, column, insWidth, buf->tabDist,
1522 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1523 XtFree(line);
1524 XtFree(insLine);
1525 #if 0 /* Earlier comments claimed that trailing whitespace could multiply on
1526 the ends of lines, but insertColInLine looks like it should never
1527 add space unnecessarily, and this trimming interfered with
1528 paragraph filling, so lets see if it works without it. MWE */
1530 char *c;
1531 for (c=outPtr+len-1; c>outPtr && (*c == ' ' || *c == '\t'); c--)
1532 len--;
1534 #endif
1535 outPtr += len;
1536 *outPtr++ = '\n';
1537 lineStart = lineEnd < buf->length ? lineEnd + 1 : buf->length;
1538 if (*insPtr == '\0')
1539 break;
1540 insPtr++;
1542 if (outPtr != outStr)
1543 outPtr--; /* trim back off extra newline */
1544 *outPtr = '\0';
1546 /* replace the text between start and end with the new stuff */
1547 delete(buf, start, end);
1548 insert(buf, start, outStr);
1549 *nInserted = outPtr - outStr;
1550 *nDeleted = end - start;
1551 *endPos = start + (outPtr - outStr) - len + endOffset;
1552 XtFree(outStr);
1556 ** Delete a rectangle of text without calling the modify callbacks. Returns
1557 ** the number of characters replacing those between start and end. Note that
1558 ** in some pathological cases, deleting can actually increase the size of
1559 ** the buffer because of tab expansions. "endPos" returns the buffer position
1560 ** of the point in the last line where the text was removed (as a hint for
1561 ** routines which need to position the cursor after a delete operation)
1563 static void deleteRect(textBuffer *buf, int start, int end, int rectStart,
1564 int rectEnd, int *replaceLen, int *endPos)
1566 int nLines, lineStart, lineEnd, len, endOffset;
1567 char *outStr, *outPtr, *line, *text, *expText;
1569 /* allocate a buffer for the replacement string large enough to hold
1570 possibly expanded tabs as well as an additional MAX_EXP_CHAR_LEN * 2
1571 characters per line for padding where tabs and control characters cross
1572 the edges of the selection */
1573 start = BufStartOfLine(buf, start);
1574 end = BufEndOfLine(buf, end);
1575 nLines = BufCountLines(buf, start, end) + 1;
1576 text = BufGetRange(buf, start, end);
1577 expText = expandTabs(text, 0, buf->tabDist, buf->nullSubsChar, &len);
1578 XtFree(text);
1579 XtFree(expText);
1580 outStr = XtMalloc(len + nLines * MAX_EXP_CHAR_LEN * 2 + 1);
1582 /* loop over all lines in the buffer between start and end removing
1583 the text between rectStart and rectEnd and padding appropriately */
1584 lineStart = start;
1585 outPtr = outStr;
1586 while (lineStart <= buf->length && lineStart <= end) {
1587 lineEnd = BufEndOfLine(buf, lineStart);
1588 line = BufGetRange(buf, lineStart, lineEnd);
1589 deleteRectFromLine(line, rectStart, rectEnd, buf->tabDist,
1590 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1591 XtFree(line);
1592 outPtr += len;
1593 *outPtr++ = '\n';
1594 lineStart = lineEnd + 1;
1596 if (outPtr != outStr)
1597 outPtr--; /* trim back off extra newline */
1598 *outPtr = '\0';
1600 /* replace the text between start and end with the newly created string */
1601 delete(buf, start, end);
1602 insert(buf, start, outStr);
1603 *replaceLen = outPtr - outStr;
1604 *endPos = start + (outPtr - outStr) - len + endOffset;
1605 XtFree(outStr);
1609 ** Overlay a rectangular area of text without calling the modify callbacks.
1610 ** "nDeleted" and "nInserted" return the number of characters deleted and
1611 ** inserted beginning at the start of the line containing "startPos".
1612 ** "endPos" returns buffer position of the lower left edge of the inserted
1613 ** column (as a hint for routines which need to set a cursor position).
1615 static void overlayRect(textBuffer *buf, int startPos, int rectStart,
1616 int rectEnd, const char *insText,
1617 int *nDeleted, int *nInserted, int *endPos)
1619 int nLines, start, end, lineStart, lineEnd;
1620 int expInsLen, len, endOffset;
1621 char *c, *outStr, *outPtr, *line, *expText, *insLine;
1622 const char *insPtr;
1624 /* Allocate a buffer for the replacement string large enough to hold
1625 possibly expanded tabs in the inserted text, as well as per line: 1)
1626 an additional 2*MAX_EXP_CHAR_LEN characters for padding where tabs
1627 and control characters cross the column of the selection, 2) up to
1628 "column" additional spaces per line for padding out to the position
1629 of "column", 3) padding up to the width of the inserted text if that
1630 must be padded to align the text beyond the inserted column. (Space
1631 for additional newlines if the inserted text extends beyond the end
1632 of the buffer is counted with the length of insText) */
1633 start = BufStartOfLine(buf, startPos);
1634 nLines = countLines(insText) + 1;
1635 end = BufEndOfLine(buf, BufCountForwardNLines(buf, start, nLines-1));
1636 expText = expandTabs(insText, 0, buf->tabDist, buf->nullSubsChar,
1637 &expInsLen);
1638 XtFree(expText);
1639 outStr = XtMalloc(end-start + expInsLen +
1640 nLines * (rectEnd + MAX_EXP_CHAR_LEN) + 1);
1642 /* Loop over all lines in the buffer between start and end overlaying the
1643 text between rectStart and rectEnd and padding appropriately. Trim
1644 trailing space from line (whitespace at the ends of lines otherwise
1645 tends to multiply, since additional padding is added to maintain it */
1646 outPtr = outStr;
1647 lineStart = start;
1648 insPtr = insText;
1649 while (True) {
1650 lineEnd = BufEndOfLine(buf, lineStart);
1651 line = BufGetRange(buf, lineStart, lineEnd);
1652 insLine = copyLine(insPtr, &len);
1653 insPtr += len;
1654 overlayRectInLine(line, insLine, rectStart, rectEnd, buf->tabDist,
1655 buf->useTabs, buf->nullSubsChar, outPtr, &len, &endOffset);
1656 XtFree(line);
1657 XtFree(insLine);
1658 for (c=outPtr+len-1; c>outPtr && (*c == ' ' || *c == '\t'); c--)
1659 len--;
1660 outPtr += len;
1661 *outPtr++ = '\n';
1662 lineStart = lineEnd < buf->length ? lineEnd + 1 : buf->length;
1663 if (*insPtr == '\0')
1664 break;
1665 insPtr++;
1667 if (outPtr != outStr)
1668 outPtr--; /* trim back off extra newline */
1669 *outPtr = '\0';
1671 /* replace the text between start and end with the new stuff */
1672 delete(buf, start, end);
1673 insert(buf, start, outStr);
1674 *nInserted = outPtr - outStr;
1675 *nDeleted = end - start;
1676 *endPos = start + (outPtr - outStr) - len + endOffset;
1677 XtFree(outStr);
1681 ** Insert characters from single-line string "insLine" in single-line string
1682 ** "line" at "column", leaving "insWidth" space before continuing line.
1683 ** "outLen" returns the number of characters written to "outStr", "endOffset"
1684 ** returns the number of characters from the beginning of the string to
1685 ** the right edge of the inserted text (as a hint for routines which need
1686 ** to position the cursor).
1688 static void insertColInLine(const char *line, const char *insLine,
1689 int column, int insWidth, int tabDist, int useTabs, char nullSubsChar,
1690 char *outStr, int *outLen, int *endOffset)
1692 char *c, *outPtr, *retabbedStr;
1693 const char *linePtr;
1694 int indent, toIndent, len, postColIndent;
1696 /* copy the line up to "column" */
1697 outPtr = outStr;
1698 indent = 0;
1699 for (linePtr=line; *linePtr!='\0'; linePtr++) {
1700 len = BufCharWidth(*linePtr, indent, tabDist, nullSubsChar);
1701 if (indent + len > column)
1702 break;
1703 indent += len;
1704 *outPtr++ = *linePtr;
1707 /* If "column" falls in the middle of a character, and the character is a
1708 tab, leave it off and leave the indent short and it will get padded
1709 later. If it's a control character, insert it and adjust indent
1710 accordingly. */
1711 if (indent < column && *linePtr != '\0') {
1712 postColIndent = indent + len;
1713 if (*linePtr == '\t')
1714 linePtr++;
1715 else {
1716 *outPtr++ = *linePtr++;
1717 indent += len;
1719 } else
1720 postColIndent = indent;
1722 /* If there's no text after the column and no text to insert, that's all */
1723 if (*insLine == '\0' && *linePtr == '\0') {
1724 *outLen = *endOffset = outPtr - outStr;
1725 return;
1728 /* pad out to column if text is too short */
1729 if (indent < column) {
1730 addPadding(outPtr, indent, column, tabDist, useTabs, nullSubsChar,&len);
1731 outPtr += len;
1732 indent = column;
1735 /* Copy the text from "insLine" (if any), recalculating the tabs as if
1736 the inserted string began at column 0 to its new column destination */
1737 if (*insLine != '\0') {
1738 retabbedStr = realignTabs(insLine, 0, indent, tabDist, useTabs,
1739 nullSubsChar, &len);
1740 for (c=retabbedStr; *c!='\0'; c++) {
1741 *outPtr++ = *c;
1742 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
1743 indent += len;
1745 XtFree(retabbedStr);
1748 /* If the original line did not extend past "column", that's all */
1749 if (*linePtr == '\0') {
1750 *outLen = *endOffset = outPtr - outStr;
1751 return;
1754 /* Pad out to column + width of inserted text + (additional original
1755 offset due to non-breaking character at column) */
1756 toIndent = column + insWidth + postColIndent-column;
1757 addPadding(outPtr, indent, toIndent, tabDist, useTabs, nullSubsChar, &len);
1758 outPtr += len;
1759 indent = toIndent;
1761 /* realign tabs for text beyond "column" and write it out */
1762 retabbedStr = realignTabs(linePtr, postColIndent, indent, tabDist,
1763 useTabs, nullSubsChar, &len);
1764 strcpy(outPtr, retabbedStr);
1765 XtFree(retabbedStr);
1766 *endOffset = outPtr - outStr;
1767 *outLen = (outPtr - outStr) + len;
1771 ** Remove characters in single-line string "line" between displayed positions
1772 ** "rectStart" and "rectEnd", and write the result to "outStr", which is
1773 ** assumed to be large enough to hold the returned string. Note that in
1774 ** certain cases, it is possible for the string to get longer due to
1775 ** expansion of tabs. "endOffset" returns the number of characters from
1776 ** the beginning of the string to the point where the characters were
1777 ** deleted (as a hint for routines which need to position the cursor).
1779 static void deleteRectFromLine(const char *line, int rectStart, int rectEnd,
1780 int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
1781 int *endOffset)
1783 int indent, preRectIndent, postRectIndent, len;
1784 const char *c;
1785 char *outPtr;
1786 char *retabbedStr;
1788 /* copy the line up to rectStart */
1789 outPtr = outStr;
1790 indent = 0;
1791 for (c=line; *c!='\0'; c++) {
1792 if (indent > rectStart)
1793 break;
1794 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
1795 if (indent + len > rectStart && (indent == rectStart || *c == '\t'))
1796 break;
1797 indent += len;
1798 *outPtr++ = *c;
1800 preRectIndent = indent;
1802 /* skip the characters between rectStart and rectEnd */
1803 for(; *c!='\0' && indent<rectEnd; c++)
1804 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
1805 postRectIndent = indent;
1807 /* If the line ended before rectEnd, there's nothing more to do */
1808 if (*c == '\0') {
1809 *outPtr = '\0';
1810 *outLen = *endOffset = outPtr - outStr;
1811 return;
1814 /* fill in any space left by removed tabs or control characters
1815 which straddled the boundaries */
1816 indent = max(rectStart + postRectIndent-rectEnd, preRectIndent);
1817 addPadding(outPtr, preRectIndent, indent, tabDist, useTabs, nullSubsChar,
1818 &len);
1819 outPtr += len;
1821 /* Copy the rest of the line. If the indentation has changed, preserve
1822 the position of non-whitespace characters by converting tabs to
1823 spaces, then back to tabs with the correct offset */
1824 retabbedStr = realignTabs(c, postRectIndent, indent, tabDist, useTabs,
1825 nullSubsChar, &len);
1826 strcpy(outPtr, retabbedStr);
1827 XtFree(retabbedStr);
1828 *endOffset = outPtr - outStr;
1829 *outLen = (outPtr - outStr) + len;
1833 ** Overlay characters from single-line string "insLine" on single-line string
1834 ** "line" between displayed character offsets "rectStart" and "rectEnd".
1835 ** "outLen" returns the number of characters written to "outStr", "endOffset"
1836 ** returns the number of characters from the beginning of the string to
1837 ** the right edge of the inserted text (as a hint for routines which need
1838 ** to position the cursor).
1840 ** This code does not handle control characters very well, but oh well.
1842 static void overlayRectInLine(const char *line, const char *insLine,
1843 int rectStart, int rectEnd, int tabDist, int useTabs,
1844 char nullSubsChar, char *outStr, int *outLen, int *endOffset)
1846 char *c, *outPtr, *retabbedStr;
1847 const char *linePtr;
1848 int inIndent, outIndent, len, postRectIndent;
1850 /* copy the line up to "rectStart" or just before the char that
1851 contains it*/
1852 outPtr = outStr;
1853 inIndent = outIndent = 0;
1854 for (linePtr=line; *linePtr!='\0'; linePtr++) {
1855 len = BufCharWidth(*linePtr, inIndent, tabDist, nullSubsChar);
1856 if (inIndent + len > rectStart)
1857 break;
1858 inIndent += len;
1859 outIndent += len;
1860 *outPtr++ = *linePtr;
1863 /* If "rectStart" falls in the middle of a character, and the character
1864 is a tab, leave it off and leave the outIndent short and it will get
1865 padded later. If it's a control character, insert it and adjust
1866 outIndent accordingly. */
1867 if (inIndent < rectStart && *linePtr != '\0') {
1868 if (*linePtr == '\t') {
1869 /* Skip past the tab */
1870 linePtr++;
1871 inIndent += len;
1872 } else {
1873 *outPtr++ = *linePtr++;
1874 outIndent += len;
1875 inIndent += len;
1879 /* skip the characters between rectStart and rectEnd */
1880 for(; *linePtr!='\0' && inIndent < rectEnd; linePtr++)
1881 inIndent += BufCharWidth(*linePtr, inIndent, tabDist, nullSubsChar);
1882 postRectIndent = inIndent;
1884 /* After this inIndent is dead and linePtr is supposed to point at the
1885 character just past the last character that will be altered by
1886 the overlay, whether that's a \t or otherwise. postRectIndent is
1887 the position at which that character is supposed to appear */
1889 /* If there's no text after rectStart and no text to insert, that's all */
1890 if (*insLine == '\0' && *linePtr == '\0') {
1891 *outLen = *endOffset = outPtr - outStr;
1892 return;
1895 /* pad out to rectStart if text is too short */
1896 if (outIndent < rectStart) {
1897 addPadding(outPtr, outIndent, rectStart, tabDist, useTabs, nullSubsChar,
1898 &len);
1899 outPtr += len;
1901 outIndent = rectStart;
1903 /* Copy the text from "insLine" (if any), recalculating the tabs as if
1904 the inserted string began at column 0 to its new column destination */
1905 if (*insLine != '\0') {
1906 retabbedStr = realignTabs(insLine, 0, rectStart, tabDist, useTabs,
1907 nullSubsChar, &len);
1908 for (c=retabbedStr; *c!='\0'; c++) {
1909 *outPtr++ = *c;
1910 len = BufCharWidth(*c, outIndent, tabDist, nullSubsChar);
1911 outIndent += len;
1913 XtFree(retabbedStr);
1916 /* If the original line did not extend past "rectStart", that's all */
1917 if (*linePtr == '\0') {
1918 *outLen = *endOffset = outPtr - outStr;
1919 return;
1922 /* Pad out to rectEnd + (additional original offset
1923 due to non-breaking character at right boundary) */
1924 addPadding(outPtr, outIndent, postRectIndent, tabDist, useTabs,
1925 nullSubsChar, &len);
1926 outPtr += len;
1927 outIndent = postRectIndent;
1929 /* copy the text beyond "rectEnd" */
1930 strcpy(outPtr, linePtr);
1931 *endOffset = outPtr - outStr;
1932 *outLen = (outPtr - outStr) + strlen(linePtr);
1935 static void setSelection(selection *sel, int start, int end)
1937 sel->selected = start != end;
1938 sel->zeroWidth = (start == end) ? 1 : 0;
1939 sel->rectangular = False;
1940 sel->start = min(start, end);
1941 sel->end = max(start, end);
1944 static void setRectSelect(selection *sel, int start, int end,
1945 int rectStart, int rectEnd)
1947 sel->selected = rectStart < rectEnd;
1948 sel->zeroWidth = (rectStart == rectEnd) ? 1 : 0;
1949 sel->rectangular = True;
1950 sel->start = start;
1951 sel->end = end;
1952 sel->rectStart = rectStart;
1953 sel->rectEnd = rectEnd;
1956 static int getSelectionPos(selection *sel, int *start, int *end,
1957 int *isRect, int *rectStart, int *rectEnd)
1959 /* Always fill in the parameters (zero-width can be requested too). */
1960 *isRect = sel->rectangular;
1961 *start = sel->start;
1962 *end = sel->end;
1963 if (sel->rectangular) {
1964 *rectStart = sel->rectStart;
1965 *rectEnd = sel->rectEnd;
1967 return sel->selected;
1970 static char *getSelectionText(textBuffer *buf, selection *sel)
1972 int start, end, isRect, rectStart, rectEnd;
1973 char *text;
1975 /* If there's no selection, return an allocated empty string */
1976 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd)) {
1977 text = XtMalloc(1);
1978 *text = '\0';
1979 return text;
1982 /* If the selection is not rectangular, return the selected range */
1983 if (isRect)
1984 return BufGetTextInRect(buf, start, end, rectStart, rectEnd);
1985 else
1986 return BufGetRange(buf, start, end);
1989 static void removeSelected(textBuffer *buf, selection *sel)
1991 int start, end;
1992 int isRect, rectStart, rectEnd;
1994 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd))
1995 return;
1996 if (isRect)
1997 BufRemoveRect(buf, start, end, rectStart, rectEnd);
1998 else
1999 BufRemove(buf, start, end);
2002 static void replaceSelected(textBuffer *buf, selection *sel, const char *text)
2004 int start, end, isRect, rectStart, rectEnd;
2005 selection oldSelection = *sel;
2007 /* If there's no selection, return */
2008 if (!getSelectionPos(sel, &start, &end, &isRect, &rectStart, &rectEnd))
2009 return;
2011 /* Do the appropriate type of replace */
2012 if (isRect)
2013 BufReplaceRect(buf, start, end, rectStart, rectEnd, text);
2014 else
2015 BufReplace(buf, start, end, text);
2017 /* Unselect (happens automatically in BufReplace, but BufReplaceRect
2018 can't detect when the contents of a selection goes away) */
2019 sel->selected = False;
2020 redisplaySelection(buf, &oldSelection, sel);
2023 static void addPadding(char *string, int startIndent, int toIndent,
2024 int tabDist, int useTabs, char nullSubsChar, int *charsAdded)
2026 char *outPtr;
2027 int len, indent;
2029 indent = startIndent;
2030 outPtr = string;
2031 if (useTabs) {
2032 while (indent < toIndent) {
2033 len = BufCharWidth('\t', indent, tabDist, nullSubsChar);
2034 if (len > 1 && indent + len <= toIndent) {
2035 *outPtr++ = '\t';
2036 indent += len;
2037 } else {
2038 *outPtr++ = ' ';
2039 indent++;
2042 } else {
2043 while (indent < toIndent) {
2044 *outPtr++ = ' ';
2045 indent++;
2048 *charsAdded = outPtr - string;
2052 ** Call the stored modify callback procedure(s) for this buffer to update the
2053 ** changed area(s) on the screen and any other listeners.
2055 static void callModifyCBs(textBuffer *buf, int pos, int nDeleted,
2056 int nInserted, int nRestyled, char *deletedText)
2058 int i;
2060 for (i=0; i<buf->nModifyProcs; i++)
2061 (*buf->modifyProcs[i])(pos, nInserted, nDeleted, nRestyled,
2062 deletedText, buf->cbArgs[i]);
2066 ** Call the stored pre-delete callback procedure(s) for this buffer to update
2067 ** the changed area(s) on the screen and any other listeners.
2069 static void callPreDeleteCBs(textBuffer *buf, int pos, int nDeleted)
2071 int i;
2073 for (i=0; i<buf->nPreDeleteProcs; i++)
2074 (*buf->preDeleteProcs[i])(pos, nDeleted, buf->preDeleteCbArgs[i]);
2078 ** Call the stored redisplay procedure(s) for this buffer to update the
2079 ** screen for a change in a selection.
2081 static void redisplaySelection(textBuffer *buf, selection *oldSelection,
2082 selection *newSelection)
2084 int oldStart, oldEnd, newStart, newEnd, ch1Start, ch1End, ch2Start, ch2End;
2086 /* If either selection is rectangular, add an additional character to
2087 the end of the selection to request the redraw routines to wipe out
2088 the parts of the selection beyond the end of the line */
2089 oldStart = oldSelection->start;
2090 newStart = newSelection->start;
2091 oldEnd = oldSelection->end;
2092 newEnd = newSelection->end;
2093 if (oldSelection->rectangular)
2094 oldEnd++;
2095 if (newSelection->rectangular)
2096 newEnd++;
2098 /* If the old or new selection is unselected, just redisplay the
2099 single area that is (was) selected and return */
2100 if (!oldSelection->selected && !newSelection->selected)
2101 return;
2102 if (!oldSelection->selected) {
2103 callModifyCBs(buf, newStart, 0, 0, newEnd-newStart, NULL);
2104 return;
2106 if (!newSelection->selected) {
2107 callModifyCBs(buf, oldStart, 0, 0, oldEnd-oldStart, NULL);
2108 return;
2111 /* If the selection changed from normal to rectangular or visa versa, or
2112 if a rectangular selection changed boundaries, redisplay everything */
2113 if ((oldSelection->rectangular && !newSelection->rectangular) ||
2114 (!oldSelection->rectangular && newSelection->rectangular) ||
2115 (oldSelection->rectangular && (
2116 (oldSelection->rectStart != newSelection->rectStart) ||
2117 (oldSelection->rectEnd != newSelection->rectEnd)))) {
2118 callModifyCBs(buf, min(oldStart, newStart), 0, 0,
2119 max(oldEnd, newEnd) - min(oldStart, newStart), NULL);
2120 return;
2123 /* If the selections are non-contiguous, do two separate updates
2124 and return */
2125 if (oldEnd < newStart || newEnd < oldStart) {
2126 callModifyCBs(buf, oldStart, 0, 0, oldEnd-oldStart, NULL);
2127 callModifyCBs(buf, newStart, 0, 0, newEnd-newStart, NULL);
2128 return;
2131 /* Otherwise, separate into 3 separate regions: ch1, and ch2 (the two
2132 changed areas), and the unchanged area of their intersection,
2133 and update only the changed area(s) */
2134 ch1Start = min(oldStart, newStart);
2135 ch2End = max(oldEnd, newEnd);
2136 ch1End = max(oldStart, newStart);
2137 ch2Start = min(oldEnd, newEnd);
2138 if (ch1Start != ch1End)
2139 callModifyCBs(buf, ch1Start, 0, 0, ch1End-ch1Start, NULL);
2140 if (ch2Start != ch2End)
2141 callModifyCBs(buf, ch2Start, 0, 0, ch2End-ch2Start, NULL);
2144 static void moveGap(textBuffer *buf, int pos)
2146 int gapLen = buf->gapEnd - buf->gapStart;
2148 if (pos > buf->gapStart)
2149 memmove(&buf->buf[buf->gapStart], &buf->buf[buf->gapEnd],
2150 pos - buf->gapStart);
2151 else
2152 memmove(&buf->buf[pos + gapLen], &buf->buf[pos], buf->gapStart - pos);
2153 buf->gapEnd += pos - buf->gapStart;
2154 buf->gapStart += pos - buf->gapStart;
2158 ** reallocate the text storage in "buf" to have a gap starting at "newGapStart"
2159 ** and a gap size of "newGapLen", preserving the buffer's current contents.
2161 static void reallocateBuf(textBuffer *buf, int newGapStart, int newGapLen)
2163 char *newBuf;
2164 int newGapEnd;
2166 newBuf = XtMalloc(buf->length + newGapLen);
2167 newGapEnd = newGapStart + newGapLen;
2168 if (newGapStart <= buf->gapStart) {
2169 memcpy(newBuf, buf->buf, newGapStart);
2170 memcpy(&newBuf[newGapEnd], &buf->buf[newGapStart],
2171 buf->gapStart - newGapStart);
2172 memcpy(&newBuf[newGapEnd + buf->gapStart - newGapStart],
2173 &buf->buf[buf->gapEnd], buf->length - buf->gapStart);
2174 } else { /* newGapStart > buf->gapStart */
2175 memcpy(newBuf, buf->buf, buf->gapStart);
2176 memcpy(&newBuf[buf->gapStart], &buf->buf[buf->gapEnd],
2177 newGapStart - buf->gapStart);
2178 memcpy(&newBuf[newGapEnd],
2179 &buf->buf[buf->gapEnd + newGapStart - buf->gapStart],
2180 buf->length - newGapStart);
2182 XtFree(buf->buf);
2183 buf->buf = newBuf;
2184 buf->gapStart = newGapStart;
2185 buf->gapEnd = newGapEnd;
2186 #ifdef PURIFY
2187 {int i; for (i=buf->gapStart; i<buf->gapEnd; i++) buf->buf[i] = '.';}
2188 #endif
2192 ** Update all of the selections in "buf" for changes in the buffer's text
2194 static void updateSelections(textBuffer *buf, int pos, int nDeleted,
2195 int nInserted)
2197 updateSelection(&buf->primary, pos, nDeleted, nInserted);
2198 updateSelection(&buf->secondary, pos, nDeleted, nInserted);
2199 updateSelection(&buf->highlight, pos, nDeleted, nInserted);
2203 ** Update an individual selection for changes in the corresponding text
2205 static void updateSelection(selection *sel, int pos, int nDeleted,
2206 int nInserted)
2208 if ((!sel->selected && !sel->zeroWidth) || pos > sel->end)
2209 return;
2210 if (pos+nDeleted <= sel->start) {
2211 sel->start += nInserted - nDeleted;
2212 sel->end += nInserted - nDeleted;
2213 } else if (pos <= sel->start && pos+nDeleted >= sel->end) {
2214 sel->start = pos;
2215 sel->end = pos;
2216 sel->selected = False;
2217 sel->zeroWidth = False;
2218 } else if (pos <= sel->start && pos+nDeleted < sel->end) {
2219 sel->start = pos;
2220 sel->end = nInserted + sel->end - nDeleted;
2221 } else if (pos < sel->end) {
2222 sel->end += nInserted - nDeleted;
2223 if (sel->end <= sel->start)
2224 sel->selected = False;
2229 ** Search forwards in buffer "buf" for character "searchChar", starting
2230 ** with the character "startPos", and returning the result in "foundPos"
2231 ** returns True if found, False if not. (The difference between this and
2232 ** BufSearchForward is that it's optimized for single characters. The
2233 ** overall performance of the text widget is dependent on its ability to
2234 ** count lines quickly, hence searching for a single character: newline)
2236 static int searchForward(textBuffer *buf, int startPos, char searchChar,
2237 int *foundPos)
2239 int pos, gapLen = buf->gapEnd - buf->gapStart;
2241 pos = startPos;
2242 while (pos < buf->gapStart) {
2243 if (buf->buf[pos] == searchChar) {
2244 *foundPos = pos;
2245 return True;
2247 pos++;
2249 while (pos < buf->length) {
2250 if (buf->buf[pos + gapLen] == searchChar) {
2251 *foundPos = pos;
2252 return True;
2254 pos++;
2256 *foundPos = buf->length;
2257 return False;
2261 ** Search backwards in buffer "buf" for character "searchChar", starting
2262 ** with the character BEFORE "startPos", returning the result in "foundPos"
2263 ** returns True if found, False if not. (The difference between this and
2264 ** BufSearchBackward is that it's optimized for single characters. The
2265 ** overall performance of the text widget is dependent on its ability to
2266 ** count lines quickly, hence searching for a single character: newline)
2268 static int searchBackward(textBuffer *buf, int startPos, char searchChar,
2269 int *foundPos)
2271 int pos, gapLen = buf->gapEnd - buf->gapStart;
2273 if (startPos == 0) {
2274 *foundPos = 0;
2275 return False;
2277 pos = startPos == 0 ? 0 : startPos - 1;
2278 while (pos >= buf->gapStart) {
2279 if (buf->buf[pos + gapLen] == searchChar) {
2280 *foundPos = pos;
2281 return True;
2283 pos--;
2285 while (pos >= 0) {
2286 if (buf->buf[pos] == searchChar) {
2287 *foundPos = pos;
2288 return True;
2290 pos--;
2292 *foundPos = 0;
2293 return False;
2297 ** Copy from "text" to end up to but not including newline (or end of "text")
2298 ** and return the copy as the function value, and the length of the line in
2299 ** "lineLen"
2301 static char *copyLine(const char *text, int *lineLen)
2303 int len = 0;
2304 const char *c;
2305 char *outStr;
2307 for (c=text; *c!='\0' && *c!='\n'; c++)
2308 len++;
2309 outStr = XtMalloc(len + 1);
2310 strncpy(outStr, text, len);
2311 outStr[len] = '\0';
2312 *lineLen = len;
2313 return outStr;
2317 ** Count the number of newlines in a null-terminated text string;
2319 static int countLines(const char *string)
2321 const char *c;
2322 int lineCount = 0;
2324 for (c=string; *c!='\0'; c++)
2325 if (*c == '\n') lineCount++;
2326 return lineCount;
2330 ** Measure the width in displayed characters of string "text"
2332 static int textWidth(const char *text, int tabDist, char nullSubsChar)
2334 int width = 0, maxWidth = 0;
2335 const char *c;
2337 for (c=text; *c!='\0'; c++) {
2338 if (*c == '\n') {
2339 if (width > maxWidth)
2340 maxWidth = width;
2341 width = 0;
2342 } else
2343 width += BufCharWidth(*c, width, tabDist, nullSubsChar);
2345 if (width > maxWidth)
2346 return width;
2347 return maxWidth;
2351 ** Find the first and last character position in a line withing a rectangular
2352 ** selection (for copying). Includes tabs which cross rectStart, but not
2353 ** control characters which do so. Leaves off tabs which cross rectEnd.
2355 ** Technically, the calling routine should convert tab characters which
2356 ** cross the right boundary of the selection to spaces which line up with
2357 ** the edge of the selection. Unfortunately, the additional memory
2358 ** management required in the parent routine to allow for the changes
2359 ** in string size is not worth all the extra work just for a couple of
2360 ** shifted characters, so if a tab protrudes, just lop it off and hope
2361 ** that there are other characters in the selection to establish the right
2362 ** margin for subsequent columnar pastes of this data.
2364 static void findRectSelBoundariesForCopy(textBuffer *buf, int lineStartPos,
2365 int rectStart, int rectEnd, int *selStart, int *selEnd)
2367 int pos, width, indent = 0;
2368 char c;
2370 /* find the start of the selection */
2371 for (pos=lineStartPos; pos<buf->length; pos++) {
2372 c = BufGetCharacter(buf, pos);
2373 if (c == '\n')
2374 break;
2375 width = BufCharWidth(c, indent, buf->tabDist, buf->nullSubsChar);
2376 if (indent + width > rectStart) {
2377 if (indent != rectStart && c != '\t') {
2378 pos++;
2379 indent += width;
2381 break;
2383 indent += width;
2385 *selStart = pos;
2387 /* find the end */
2388 for (; pos<buf->length; pos++) {
2389 c = BufGetCharacter(buf, pos);
2390 if (c == '\n')
2391 break;
2392 width = BufCharWidth(c, indent, buf->tabDist, buf->nullSubsChar);
2393 indent += width;
2394 if (indent > rectEnd) {
2395 if (indent-width != rectEnd && c != '\t')
2396 pos++;
2397 break;
2400 *selEnd = pos;
2404 ** Adjust the space and tab characters from string "text" so that non-white
2405 ** characters remain stationary when the text is shifted from starting at
2406 ** "origIndent" to starting at "newIndent". Returns an allocated string
2407 ** which must be freed by the caller with XtFree.
2409 static char *realignTabs(const char *text, int origIndent, int newIndent,
2410 int tabDist, int useTabs, char nullSubsChar, int *newLength)
2412 char *expStr, *outStr;
2413 int len;
2415 /* If the tabs settings are the same, retain original tabs */
2416 if (origIndent % tabDist == newIndent %tabDist) {
2417 len = strlen(text);
2418 outStr = XtMalloc(len + 1);
2419 strcpy(outStr, text);
2420 *newLength = len;
2421 return outStr;
2424 /* If the tab settings are not the same, brutally convert tabs to
2425 spaces, then back to tabs in the new position */
2426 expStr = expandTabs(text, origIndent, tabDist, nullSubsChar, &len);
2427 if (!useTabs) {
2428 *newLength = len;
2429 return expStr;
2431 outStr = unexpandTabs(expStr, newIndent, tabDist, nullSubsChar, newLength);
2432 XtFree(expStr);
2433 return outStr;
2437 ** Expand tabs to spaces for a block of text. The additional parameter
2438 ** "startIndent" if nonzero, indicates that the text is a rectangular selection
2439 ** beginning at column "startIndent"
2441 static char *expandTabs(const char *text, int startIndent, int tabDist,
2442 char nullSubsChar, int *newLen)
2444 char *outStr, *outPtr;
2445 const char *c;
2446 int indent, len, outLen = 0;
2448 /* rehearse the expansion to figure out length for output string */
2449 indent = startIndent;
2450 for (c=text; *c!='\0'; c++) {
2451 if (*c == '\t') {
2452 len = BufCharWidth(*c, indent, tabDist, nullSubsChar);
2453 outLen += len;
2454 indent += len;
2455 } else if (*c == '\n') {
2456 indent = startIndent;
2457 outLen++;
2458 } else {
2459 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
2460 outLen++;
2464 /* do the expansion */
2465 outStr = XtMalloc(outLen+1);
2466 outPtr = outStr;
2467 indent = startIndent;
2468 for (c=text; *c!= '\0'; c++) {
2469 if (*c == '\t') {
2470 len = BufExpandCharacter(*c, indent, outPtr, tabDist, nullSubsChar);
2471 outPtr += len;
2472 indent += len;
2473 } else if (*c == '\n') {
2474 indent = startIndent;
2475 *outPtr++ = *c;
2476 } else {
2477 indent += BufCharWidth(*c, indent, tabDist, nullSubsChar);
2478 *outPtr++ = *c;
2481 outStr[outLen] = '\0';
2482 *newLen = outLen;
2483 return outStr;
2487 ** Convert sequences of spaces into tabs. The threshold for conversion is
2488 ** when 3 or more spaces can be converted into a single tab, this avoids
2489 ** converting double spaces after a period withing a block of text.
2491 static char *unexpandTabs(const char *text, int startIndent, int tabDist,
2492 char nullSubsChar, int *newLen)
2494 char *outStr, *outPtr, expandedChar[MAX_EXP_CHAR_LEN];
2495 const char *c;
2496 int indent, len;
2498 outStr = XtMalloc(strlen(text)+1);
2499 outPtr = outStr;
2500 indent = startIndent;
2501 for (c=text; *c!='\0';) {
2502 if (*c == ' ') {
2503 len = BufExpandCharacter('\t', indent, expandedChar, tabDist,
2504 nullSubsChar);
2505 if (len >= 3 && !strncmp(c, expandedChar, len)) {
2506 c += len;
2507 *outPtr++ = '\t';
2508 indent += len;
2509 } else {
2510 *outPtr++ = *c++;
2511 indent++;
2513 } else if (*c == '\n') {
2514 indent = startIndent;
2515 *outPtr++ = *c++;
2516 } else {
2517 *outPtr++ = *c++;
2518 indent++;
2521 *outPtr = '\0';
2522 *newLen = outPtr - outStr;
2523 return outStr;
2526 static int max(int i1, int i2)
2528 return i1 >= i2 ? i1 : i2;
2531 static int min(int i1, int i2)
2533 return i1 <= i2 ? i1 : i2;