1 static const char CVSID
[] = "$Id: shift.c,v 1.14 2003/04/07 22:51:41 yooden Exp $";
2 /*******************************************************************************
4 * shift.c -- Nirvana Editor built-in filter commands *
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 *******************************************************************************/
30 #include "../config.h"
43 #include "../util/VMSparam.h"
46 #include <sys/param.h>
57 static void shiftRect(WindowInfo
*window
, int direction
, int byTab
,
58 int selStart
, int selEnd
, int rectStart
, int rectEnd
);
59 static void changeCase(WindowInfo
*window
, int makeUpper
);
60 static char *shiftLineRight(char *line
, int lineLen
, int tabsAllowed
,
61 int tabDist
, int nChars
);
62 static char *shiftLineLeft(char *line
, int lineLen
, int tabDist
, int nChars
);
63 static int findLeftMargin(char *text
, int length
, int tabDist
);
64 static char *fillParagraphs(char *text
, int rightMargin
, int tabDist
,
65 int useTabs
, char nullSubsChar
, int *filledLen
, int alignWithFirst
);
66 static char *fillParagraph(char *text
, int leftMargin
, int firstLineIndent
,
67 int rightMargin
, int tabDist
, int allowTabs
, char nullSubsChar
,
69 static char *makeIndentString(int indent
, int tabDist
, int allowTabs
, int *nChars
);
70 static int atTabStop(int pos
, int tabDist
);
71 static int nextTab(int pos
, int tabDist
);
72 static int countLines(const char *text
);
73 static int findParagraphStart(textBuffer
*buf
, int startPos
);
74 static int findParagraphEnd(textBuffer
*buf
, int startPos
);
77 ** Shift the selection left or right by a single character, or by one tab stop
78 ** if "byTab" is true. (The length of a tab stop is the size of an emulated
79 ** tab if emulated tabs are turned on, or a hardware tab if not).
81 void ShiftSelection(WindowInfo
*window
, int direction
, int byTab
)
83 int selStart
, selEnd
, isRect
, rectStart
, rectEnd
;
84 int shiftedLen
, newEndPos
, cursorPos
, origLength
, emTabDist
, shiftDist
;
85 char *text
, *shiftedText
;
86 textBuffer
*buf
= window
->buffer
;
88 /* get selection, if no text selected, use current insert position */
89 if (!BufGetSelectionPos(buf
, &selStart
, &selEnd
, &isRect
,
90 &rectStart
, &rectEnd
)) {
91 cursorPos
= TextGetCursorPos(window
->lastFocus
);
92 selStart
= BufStartOfLine(buf
, cursorPos
);
93 selEnd
= BufEndOfLine(buf
, cursorPos
);
94 if (selEnd
< buf
->length
)
96 BufSelect(buf
, selStart
, selEnd
);
98 text
= BufGetRange(buf
, selStart
, selEnd
);
100 cursorPos
= TextGetCursorPos(window
->lastFocus
);
101 origLength
= buf
->length
;
102 shiftRect(window
, direction
, byTab
, selStart
, selEnd
, rectStart
,
104 TextSetCursorPos(window
->lastFocus
, (cursorPos
< (selEnd
+selStart
)/2) ?
105 selStart
: cursorPos
+ (buf
->length
- origLength
));
108 selStart
= BufStartOfLine(buf
, selStart
);
109 if (selEnd
!= 0 && BufGetCharacter(buf
, selEnd
-1) != '\n') {
110 selEnd
= BufEndOfLine(buf
, selEnd
);
111 if (selEnd
< buf
->length
)
114 BufSelect(buf
, selStart
, selEnd
);
115 text
= BufGetRange(buf
, selStart
, selEnd
);
118 /* shift the text by the appropriate distance */
120 XtVaGetValues(window
->textArea
, textNemulateTabs
, &emTabDist
, NULL
);
121 shiftDist
= emTabDist
== 0 ? buf
->tabDist
: emTabDist
;
124 shiftedText
= ShiftText(text
, direction
, buf
->useTabs
, buf
->tabDist
,
125 shiftDist
, &shiftedLen
);
127 BufReplaceSelected(buf
, shiftedText
);
130 newEndPos
= selStart
+ shiftedLen
;
131 BufSelect(buf
, selStart
, newEndPos
);
134 static void shiftRect(WindowInfo
*window
, int direction
, int byTab
,
135 int selStart
, int selEnd
, int rectStart
, int rectEnd
)
137 int offset
, emTabDist
;
138 textBuffer
*tempBuf
, *buf
= window
->buffer
;
141 /* Make sure selStart and SelEnd refer to whole lines */
142 selStart
= BufStartOfLine(buf
, selStart
);
143 selEnd
= BufEndOfLine(buf
, selEnd
);
145 /* Calculate the the left/right offset for the new rectangle */
147 XtVaGetValues(window
->textArea
, textNemulateTabs
, &emTabDist
, NULL
);
148 offset
= emTabDist
== 0 ? buf
->tabDist
: emTabDist
;
151 offset
*= direction
== SHIFT_LEFT
? -1 : 1;
152 if (rectStart
+ offset
< 0)
155 /* Create a temporary buffer for the lines containing the selection, to
156 hide the intermediate steps from the display update routines */
157 tempBuf
= BufCreate();
158 tempBuf
->tabDist
= buf
->tabDist
;
159 tempBuf
->useTabs
= buf
->useTabs
;
160 text
= BufGetRange(buf
, selStart
, selEnd
);
161 BufSetAll(tempBuf
, text
);
164 /* Do the shift in the temporary buffer */
165 text
= BufGetTextInRect(buf
, selStart
, selEnd
, rectStart
, rectEnd
);
166 BufRemoveRect(tempBuf
, 0, selEnd
-selStart
, rectStart
, rectEnd
);
167 BufInsertCol(tempBuf
, rectStart
+offset
, 0, text
, NULL
, NULL
);
170 /* Make the change in the real buffer */
171 text
= BufGetAll(tempBuf
);
172 BufReplace(buf
, selStart
, selEnd
, text
);
174 BufRectSelect(buf
, selStart
, selStart
+ tempBuf
->length
,
175 rectStart
+offset
, rectEnd
+offset
);
179 void UpcaseSelection(WindowInfo
*window
)
181 changeCase(window
, True
);
184 void DowncaseSelection(WindowInfo
*window
)
186 changeCase(window
, False
);
190 ** Capitalize or lowercase the contents of the selection (or of the character
191 ** before the cursor if there is no selection). If "makeUpper" is true,
192 ** change to upper case, otherwise, change to lower case.
194 static void changeCase(WindowInfo
*window
, int makeUpper
)
196 textBuffer
*buf
= window
->buffer
;
198 int cursorPos
, start
, end
, isRect
, rectStart
, rectEnd
;
200 /* Get the selection. Use character before cursor if no selection */
201 if (!BufGetSelectionPos(buf
, &start
, &end
, &isRect
, &rectStart
, &rectEnd
)) {
202 char bufChar
[2] = " ";
203 cursorPos
= TextGetCursorPos(window
->lastFocus
);
204 if (cursorPos
== 0) {
205 XBell(TheDisplay
, 0);
208 *bufChar
= BufGetCharacter(buf
, cursorPos
-1);
209 *bufChar
= makeUpper
? toupper((unsigned char)*bufChar
) :
210 tolower((unsigned char)*bufChar
);
211 BufReplace(buf
, cursorPos
-1, cursorPos
, bufChar
);
213 text
= BufGetSelectionText(buf
);
214 for (c
=text
; *c
!='\0'; c
++)
215 *c
= makeUpper
? toupper((unsigned char)*c
) :
216 tolower((unsigned char)*c
);
217 BufReplaceSelected(buf
, text
);
220 BufRectSelect(buf
, start
, end
, rectStart
, rectEnd
);
222 BufSelect(buf
, start
, end
);
226 void FillSelection(WindowInfo
*window
)
228 textBuffer
*buf
= window
->buffer
;
229 char *text
, *filledText
;
230 int left
, right
, nCols
, len
, isRect
, rectStart
, rectEnd
;
231 int rightMargin
, wrapMargin
;
232 int insertPos
= TextGetCursorPos(window
->lastFocus
);
233 int hasSelection
= window
->buffer
->primary
.selected
;
235 /* Find the range of characters and get the text to fill. If there is a
236 selection, use it but extend non-rectangular selections to encompass
237 whole lines. If there is no selection, find the paragraph containing
238 the insertion cursor */
239 if (!BufGetSelectionPos(buf
, &left
, &right
, &isRect
, &rectStart
, &rectEnd
)) {
240 left
= findParagraphStart(buf
, insertPos
);
241 right
= findParagraphEnd(buf
, insertPos
);
243 XBell(TheDisplay
, 0);
246 text
= BufGetRange(buf
, left
, right
);
248 left
= BufStartOfLine(buf
, left
);
249 right
= BufEndOfLine(buf
, right
);
250 text
= BufGetTextInRect(buf
, left
, right
, rectStart
, INT_MAX
);
252 left
= BufStartOfLine(buf
, left
);
253 if (right
!= 0 && BufGetCharacter(buf
, right
-1) != '\n') {
254 right
= BufEndOfLine(buf
, right
);
255 if (right
< buf
->length
)
258 BufSelect(buf
, left
, right
);
259 text
= BufGetRange(buf
, left
, right
);
262 /* Find right margin either as specified in the rectangular selection, or
263 by measuring the text and querying the window's wrap margin (or width) */
264 if (hasSelection
&& isRect
) {
265 rightMargin
= rectEnd
- rectStart
;
267 XtVaGetValues(window
->textArea
, textNcolumns
, &nCols
,
268 textNwrapMargin
, &wrapMargin
, NULL
);
269 rightMargin
= (wrapMargin
== 0 ? nCols
: wrapMargin
) - 1;
273 filledText
= fillParagraphs(text
, rightMargin
, buf
->tabDist
, buf
->useTabs
,
274 buf
->nullSubsChar
, &len
, False
);
277 /* Replace the text in the window */
278 if (hasSelection
&& isRect
) {
279 BufReplaceRect(buf
, left
, right
, rectStart
, INT_MAX
, filledText
);
280 BufRectSelect(buf
, left
,
281 BufEndOfLine(buf
, BufCountForwardNLines(buf
, left
,
282 countLines(filledText
)-1)), rectStart
, rectEnd
);
284 BufReplace(buf
, left
, right
, filledText
);
286 BufSelect(buf
, left
, left
+ len
);
290 /* Find a reasonable cursor position. Usually insertPos is best, but
291 if the text was indented, positions can shift */
292 if (hasSelection
&& isRect
)
293 TextSetCursorPos(window
->lastFocus
, buf
->cursorPosHint
);
295 TextSetCursorPos(window
->lastFocus
, insertPos
< left
? left
:
296 (insertPos
> left
+ len
? left
+ len
: insertPos
));
300 ** shift lines left and right in a multi-line text string. Returns the
301 ** shifted text in memory that must be freed by the caller with XtFree.
303 char *ShiftText(char *text
, int direction
, int tabsAllowed
, int tabDist
,
304 int nChars
, int *newLen
)
306 char *shiftedText
, *shiftedLine
;
307 char *textPtr
, *lineStartPtr
, *shiftedPtr
;
311 ** Allocate memory for shifted string. Shift left adds a maximum of
312 ** tabDist-2 characters per line (remove one tab, add tabDist-1 spaces).
313 ** Shift right adds a maximum of nChars character per line.
315 if (direction
== SHIFT_RIGHT
)
316 bufLen
= strlen(text
) + countLines(text
) * nChars
;
318 bufLen
= strlen(text
) + countLines(text
) * tabDist
;
319 shiftedText
= (char *)XtMalloc(bufLen
+ 1);
322 ** break into lines and call shiftLine(Left/Right) on each
326 shiftedPtr
= shiftedText
;
328 if (*textPtr
=='\n' || *textPtr
=='\0') {
329 shiftedLine
= (direction
== SHIFT_RIGHT
) ?
330 shiftLineRight(lineStartPtr
, textPtr
-lineStartPtr
,
331 tabsAllowed
, tabDist
, nChars
) :
332 shiftLineLeft(lineStartPtr
, textPtr
-lineStartPtr
, tabDist
,
334 strcpy(shiftedPtr
, shiftedLine
);
335 shiftedPtr
+= strlen(shiftedLine
);
337 if (*textPtr
== '\0') {
338 /* terminate string & exit loop at end of text */
342 /* move the newline from text to shifted text */
343 *shiftedPtr
++ = *textPtr
++;
345 /* start line over */
346 lineStartPtr
= textPtr
;
350 *newLen
= shiftedPtr
- shiftedText
;
354 static char *shiftLineRight(char *line
, int lineLen
, int tabsAllowed
,
355 int tabDist
, int nChars
)
358 char *lineInPtr
, *lineOutPtr
;
362 lineOut
= XtMalloc(lineLen
+ nChars
+ 1);
363 lineOutPtr
= lineOut
;
366 if (*lineInPtr
== '\0' || (lineInPtr
- line
) >= lineLen
) {
367 /* nothing on line, wipe it out */
370 } else if (*lineInPtr
== ' ') {
371 /* white space continues with tab, advance to next tab stop */
373 *lineOutPtr
++ = *lineInPtr
++;
374 } else if (*lineInPtr
== '\t') {
375 /* white space continues with tab, advance to next tab stop */
376 whiteWidth
= nextTab(whiteWidth
, tabDist
);
377 *lineOutPtr
++ = *lineInPtr
++;
379 /* end of white space, add nChars of space */
380 for (i
=0; i
<nChars
; i
++) {
383 /* if we're now at a tab stop, change last 8 spaces to a tab */
384 if (tabsAllowed
&& atTabStop(whiteWidth
, tabDist
)) {
385 lineOutPtr
-= tabDist
;
386 *lineOutPtr
++ = '\t';
389 /* move remainder of line */
390 while (*lineInPtr
!='\0' && (lineInPtr
- line
) < lineLen
)
391 *lineOutPtr
++ = *lineInPtr
++;
398 static char *shiftLineLeft(char *line
, int lineLen
, int tabDist
, int nChars
)
401 int i
, whiteWidth
, lastWhiteWidth
, whiteGoal
;
402 char *lineInPtr
, *lineOutPtr
;
405 lineOut
= XtMalloc(lineLen
+ tabDist
+ 1);
406 lineOutPtr
= lineOut
;
410 if (*lineInPtr
== '\0' || (lineInPtr
- line
) >= lineLen
) {
411 /* nothing on line, wipe it out */
414 } else if (*lineInPtr
== ' ') {
415 /* white space continues with space, advance one character */
417 *lineOutPtr
++ = *lineInPtr
++;
418 } else if (*lineInPtr
== '\t') {
419 /* white space continues with tab, advance to next tab stop */
420 /* save the position, though, in case we need to remove the tab */
421 lastWhiteWidth
= whiteWidth
;
422 whiteWidth
= nextTab(whiteWidth
, tabDist
);
423 *lineOutPtr
++ = *lineInPtr
++;
425 /* end of white space, remove nChars characters */
426 for (i
=1; i
<=nChars
; i
++) {
427 if (lineOutPtr
> lineOut
) {
428 if (*(lineOutPtr
-1) == ' ') {
429 /* end of white space is a space, just remove it */
432 /* end of white space is a tab, remove it and add
435 whiteGoal
= whiteWidth
- i
;
436 whiteWidth
= lastWhiteWidth
;
437 while (whiteWidth
< whiteGoal
) {
444 /* move remainder of line */
445 while (*lineInPtr
!='\0' && (lineInPtr
- line
) < lineLen
)
446 *lineOutPtr
++ = *lineInPtr
++;
454 static int atTabStop(int pos
, int tabDist
)
456 return (pos
%tabDist
== 0);
459 static int nextTab(int pos
, int tabDist
)
461 return (pos
/tabDist
)*tabDist
+ tabDist
;
464 static int countLines(const char *text
)
468 while(*text
!= '\0') {
469 if (*text
++ == '\n') {
477 ** Find the implied left margin of a text string (the number of columns to the
478 ** first non-whitespace character on any line) up to either the terminating
479 ** null character at the end of the string, or "length" characters, whever
482 static int findLeftMargin(char *text
, int length
, int tabDist
)
485 int col
= 0, leftMargin
= INT_MAX
;
488 for (c
=text
; *c
!='\0' && c
-text
<length
; c
++) {
490 col
+= BufCharWidth('\t', col
, tabDist
, '\0');
491 } else if (*c
== ' ') {
493 } else if (*c
== '\n') {
498 if (col
< leftMargin
&& inMargin
)
504 /* if no non-white text is found, the leftMargin will never be set */
505 if (leftMargin
== INT_MAX
)
512 ** Fill multiple paragraphs between rightMargin and an implied left margin
513 ** and first line indent determined by analyzing the text. alignWithFirst
514 ** aligns subsequent paragraphs with the margins of the first paragraph (a
515 ** capability not currently used in NEdit, but carried over from code for
516 ** previous versions which did all paragraphs together).
518 static char *fillParagraphs(char *text
, int rightMargin
, int tabDist
,
519 int useTabs
, char nullSubsChar
, int *filledLen
, int alignWithFirst
)
521 int paraStart
, paraEnd
, fillEnd
;
522 char *c
, ch
, *secondLineStart
, *paraText
, *filledText
;
523 int firstLineLen
, firstLineIndent
, leftMargin
, len
;
526 /* Create a buffer to accumulate the filled paragraphs */
528 BufSetAll(buf
, text
);
531 ** Loop over paragraphs, filling each one, and accumulating the results
537 /* Skip over white space */
538 while (paraStart
< buf
->length
) {
539 ch
= BufGetCharacter(buf
, paraStart
);
540 if (ch
!= ' ' && ch
!= '\t' && ch
!= '\n')
544 if (paraStart
>= buf
->length
)
546 paraStart
= BufStartOfLine(buf
, paraStart
);
548 /* Find the end of the paragraph */
549 paraEnd
= findParagraphEnd(buf
, paraStart
);
551 /* Operate on either the one paragraph, or to make them all identical,
552 do all of them together (fill paragraph can format all the paragraphs
553 it finds with identical specs if it gets passed more than one) */
554 fillEnd
= alignWithFirst
? buf
->length
: paraEnd
;
556 /* Get the paragraph in a text string (or all of the paragraphs if
557 we're making them all the same) */
558 paraText
= BufGetRange(buf
, paraStart
, fillEnd
);
560 /* Find separate left margins for the first and for the first line of
561 the paragraph, and for rest of the remainder of the paragraph */
562 for (c
=paraText
; *c
!='\0' && *c
!='\n'; c
++);
563 firstLineLen
= c
- paraText
;
564 secondLineStart
= *c
== '\0' ? paraText
: c
+ 1;
565 firstLineIndent
= findLeftMargin(paraText
, firstLineLen
, tabDist
);
566 leftMargin
= findLeftMargin(secondLineStart
, paraEnd
- paraStart
-
567 (secondLineStart
- paraText
), tabDist
);
569 /* Fill the paragraph */
570 filledText
= fillParagraph(paraText
, leftMargin
, firstLineIndent
,
571 rightMargin
, tabDist
, useTabs
, nullSubsChar
, &len
);
574 /* Replace it in the buffer */
575 BufReplace(buf
, paraStart
, fillEnd
, filledText
);
578 /* move on to the next paragraph */
582 /* Free the buffer and return its contents */
583 filledText
= BufGetAll(buf
);
584 *filledLen
= buf
->length
;
590 ** Trim leading space, and arrange text to fill between leftMargin and
591 ** rightMargin (except for the first line which fills from firstLineIndent),
592 ** re-creating whitespace to the left of the text using tabs (if allowTabs is
593 ** True) calculated using tabDist, and spaces. Returns a newly allocated
594 ** string as the function result, and the length of the new string in filledLen.
596 static char *fillParagraph(char *text
, int leftMargin
, int firstLineIndent
,
597 int rightMargin
, int tabDist
, int allowTabs
, char nullSubsChar
,
600 char *cleanedText
, *outText
, *indentString
, *leadIndentStr
, *outPtr
, *c
, *b
;
601 int col
, cleanedLen
, indentLen
, leadIndentLen
, nLines
= 1;
602 int inWhitespace
, inMargin
;
604 /* remove leading spaces, convert newlines to spaces */
605 cleanedText
= XtMalloc(strlen(text
)+1);
606 outPtr
= cleanedText
;
608 for (c
=text
; *c
!='\0'; c
++) {
609 if (*c
== '\t' || *c
== ' ') {
612 } else if (*c
== '\n') {
614 /* a newline before any text separates paragraphs, so leave
615 it in, back up, and convert the previous space back to \n */
616 if (outPtr
> cleanedText
&& *(outPtr
-1) == ' ')
628 cleanedLen
= outPtr
- cleanedText
;
631 /* Put back newlines breaking text at word boundaries within the margins.
632 Algorithm: scan through characters, counting columns, and when the
633 margin width is exceeded, search backward for beginning of the word
634 and convert the last whitespace character into a newline */
635 col
= firstLineIndent
;
636 for (c
=cleanedText
; *c
!='\0'; c
++) {
640 col
+= BufCharWidth(*c
, col
, tabDist
, nullSubsChar
);
641 if (col
-1 > rightMargin
) {
643 for (b
=c
; b
>=cleanedText
&& *b
!='\n'; b
--) {
644 if (*b
== '\t' || *b
== ' ') {
653 inWhitespace
= False
;
659 /* produce a string to prepend to lines to indent them to the left margin */
660 leadIndentStr
= makeIndentString(firstLineIndent
, tabDist
,
661 allowTabs
, &leadIndentLen
);
662 indentString
= makeIndentString(leftMargin
, tabDist
, allowTabs
, &indentLen
);
664 /* allocate memory for the finished string */
665 outText
= XtMalloc(sizeof(char) * (cleanedLen
+ leadIndentLen
+
666 indentLen
* (nLines
-1) + 1));
669 /* prepend the indent string to each line of the filled text */
670 strncpy(outPtr
, leadIndentStr
, leadIndentLen
);
671 outPtr
+= leadIndentLen
;
672 for (c
=cleanedText
; *c
!='\0'; c
++) {
675 strncpy(outPtr
, indentString
, indentLen
);
680 /* convert any trailing space to newline. Add terminating null */
681 if (*(outPtr
-1) == ' ')
685 /* clean up, return result */
687 XtFree(leadIndentStr
);
688 XtFree(indentString
);
689 *filledLen
= outPtr
- outText
;
693 static char *makeIndentString(int indent
, int tabDist
, int allowTabs
, int *nChars
)
695 char *indentString
, *outPtr
;
698 outPtr
= indentString
= XtMalloc(sizeof(char) * indent
+ 1);
700 for (i
=0; i
<indent
/tabDist
; i
++)
702 for (i
=0; i
<indent
%tabDist
; i
++)
705 for (i
=0; i
<indent
; i
++)
709 *nChars
= outPtr
- indentString
;
714 ** Find the boundaries of the paragraph containing pos
716 static int findParagraphEnd(textBuffer
*buf
, int startPos
)
720 static char whiteChars
[] = " \t";
722 pos
= BufEndOfLine(buf
, startPos
)+1;
723 while (pos
< buf
->length
) {
724 c
= BufGetCharacter(buf
, pos
);
727 if (strchr(whiteChars
, c
) != NULL
)
730 pos
= BufEndOfLine(buf
, pos
)+1;
732 return pos
< buf
->length
? pos
: buf
->length
;
734 static int findParagraphStart(textBuffer
*buf
, int startPos
)
738 static char whiteChars
[] = " \t";
742 parStart
= BufStartOfLine(buf
, startPos
);
745 c
= BufGetCharacter(buf
, pos
);
748 if (strchr(whiteChars
, c
) != NULL
)
751 parStart
= BufStartOfLine(buf
, pos
);
755 return parStart
> 0 ? parStart
: 0;