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