1 static const char CVSID
[] = "$Id: shift.c,v 1.18 2006/10/17 10:10:59 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 *
11 * version. In addition, you may distribute version of this program linked to *
12 * Motif or Open Motif. See README for details. *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
23 * Nirvana Text Editor *
26 * Written by Mark Edel *
28 *******************************************************************************/
31 #include "../config.h"
44 #include "../util/VMSparam.h"
47 #include <sys/param.h>
58 static void shiftRect(WindowInfo
*window
, int direction
, int byTab
,
59 int selStart
, int selEnd
, int rectStart
, int rectEnd
);
60 static void changeCase(WindowInfo
*window
, int makeUpper
);
61 static char *shiftLineRight(char *line
, int lineLen
, int tabsAllowed
,
62 int tabDist
, int nChars
);
63 static char *shiftLineLeft(char *line
, int lineLen
, int tabDist
, int nChars
);
64 static int findLeftMargin(char *text
, int length
, int tabDist
);
65 static char *fillParagraphs(char *text
, int rightMargin
, int tabDist
,
66 int useTabs
, char nullSubsChar
, int *filledLen
, int alignWithFirst
);
67 static char *fillParagraph(char *text
, int leftMargin
, int firstLineIndent
,
68 int rightMargin
, int tabDist
, int allowTabs
, char nullSubsChar
,
70 static char *makeIndentString(int indent
, int tabDist
, int allowTabs
, int *nChars
);
71 static int atTabStop(int pos
, int tabDist
);
72 static int nextTab(int pos
, int tabDist
);
73 static int countLines(const char *text
);
74 static int findParagraphStart(textBuffer
*buf
, int startPos
);
75 static int findParagraphEnd(textBuffer
*buf
, int startPos
);
78 ** Shift the selection left or right by a single character, or by one tab stop
79 ** if "byTab" is true. (The length of a tab stop is the size of an emulated
80 ** tab if emulated tabs are turned on, or a hardware tab if not).
82 void ShiftSelection(WindowInfo
*window
, int direction
, int byTab
)
84 int selStart
, selEnd
, isRect
, rectStart
, rectEnd
;
85 int shiftedLen
, newEndPos
, cursorPos
, origLength
, emTabDist
, shiftDist
;
86 char *text
, *shiftedText
;
87 textBuffer
*buf
= window
->buffer
;
89 /* get selection, if no text selected, use current insert position */
90 if (!BufGetSelectionPos(buf
, &selStart
, &selEnd
, &isRect
,
91 &rectStart
, &rectEnd
)) {
92 cursorPos
= TextGetCursorPos(window
->lastFocus
);
93 selStart
= BufStartOfLine(buf
, cursorPos
);
94 selEnd
= BufEndOfLine(buf
, cursorPos
);
95 if (selEnd
< buf
->length
)
97 BufSelect(buf
, selStart
, selEnd
);
99 text
= BufGetRange(buf
, selStart
, selEnd
);
101 cursorPos
= TextGetCursorPos(window
->lastFocus
);
102 origLength
= buf
->length
;
103 shiftRect(window
, direction
, byTab
, selStart
, selEnd
, rectStart
,
105 TextSetCursorPos(window
->lastFocus
, (cursorPos
< (selEnd
+selStart
)/2) ?
106 selStart
: cursorPos
+ (buf
->length
- origLength
));
109 selStart
= BufStartOfLine(buf
, selStart
);
110 if (selEnd
!= 0 && BufGetCharacter(buf
, selEnd
-1) != '\n') {
111 selEnd
= BufEndOfLine(buf
, selEnd
);
112 if (selEnd
< buf
->length
)
115 BufSelect(buf
, selStart
, selEnd
);
116 text
= BufGetRange(buf
, selStart
, selEnd
);
119 /* shift the text by the appropriate distance */
121 XtVaGetValues(window
->textArea
, textNemulateTabs
, &emTabDist
, NULL
);
122 shiftDist
= emTabDist
== 0 ? buf
->tabDist
: emTabDist
;
125 shiftedText
= ShiftText(text
, direction
, buf
->useTabs
, buf
->tabDist
,
126 shiftDist
, &shiftedLen
);
128 BufReplaceSelected(buf
, shiftedText
);
131 newEndPos
= selStart
+ shiftedLen
;
132 BufSelect(buf
, selStart
, newEndPos
);
135 static void shiftRect(WindowInfo
*window
, int direction
, int byTab
,
136 int selStart
, int selEnd
, int rectStart
, int rectEnd
)
138 int offset
, emTabDist
;
139 textBuffer
*tempBuf
, *buf
= window
->buffer
;
142 /* Make sure selStart and SelEnd refer to whole lines */
143 selStart
= BufStartOfLine(buf
, selStart
);
144 selEnd
= BufEndOfLine(buf
, selEnd
);
146 /* Calculate the the left/right offset for the new rectangle */
148 XtVaGetValues(window
->textArea
, textNemulateTabs
, &emTabDist
, NULL
);
149 offset
= emTabDist
== 0 ? buf
->tabDist
: emTabDist
;
152 offset
*= direction
== SHIFT_LEFT
? -1 : 1;
153 if (rectStart
+ offset
< 0)
156 /* Create a temporary buffer for the lines containing the selection, to
157 hide the intermediate steps from the display update routines */
158 tempBuf
= BufCreate();
159 tempBuf
->tabDist
= buf
->tabDist
;
160 tempBuf
->useTabs
= buf
->useTabs
;
161 text
= BufGetRange(buf
, selStart
, selEnd
);
162 BufSetAll(tempBuf
, text
);
165 /* Do the shift in the temporary buffer */
166 text
= BufGetTextInRect(buf
, selStart
, selEnd
, rectStart
, rectEnd
);
167 BufRemoveRect(tempBuf
, 0, selEnd
-selStart
, rectStart
, rectEnd
);
168 BufInsertCol(tempBuf
, rectStart
+offset
, 0, text
, NULL
, NULL
);
171 /* Make the change in the real buffer */
172 BufReplace(buf
, selStart
, selEnd
, BufAsString(tempBuf
));
173 BufRectSelect(buf
, selStart
, selStart
+ tempBuf
->length
,
174 rectStart
+offset
, rectEnd
+offset
);
178 void UpcaseSelection(WindowInfo
*window
)
180 changeCase(window
, True
);
183 void DowncaseSelection(WindowInfo
*window
)
185 changeCase(window
, False
);
189 ** Capitalize or lowercase the contents of the selection (or of the character
190 ** before the cursor if there is no selection). If "makeUpper" is true,
191 ** change to upper case, otherwise, change to lower case.
193 static void changeCase(WindowInfo
*window
, int makeUpper
)
195 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 Boolean modified
= False
;
215 text
= BufGetSelectionText(buf
);
216 for (c
= text
; *c
!= '\0'; c
++) {
218 *c
= makeUpper
? toupper((unsigned char)*c
) :
219 tolower((unsigned char)*c
);
226 BufReplaceSelected(buf
, text
);
231 BufRectSelect(buf
, start
, end
, rectStart
, rectEnd
);
233 BufSelect(buf
, start
, end
);
237 void FillSelection(WindowInfo
*window
)
239 textBuffer
*buf
= window
->buffer
;
240 char *text
, *filledText
;
241 int left
, right
, nCols
, len
, isRect
, rectStart
, rectEnd
;
242 int rightMargin
, wrapMargin
;
243 int insertPos
= TextGetCursorPos(window
->lastFocus
);
244 int hasSelection
= window
->buffer
->primary
.selected
;
246 /* Find the range of characters and get the text to fill. If there is a
247 selection, use it but extend non-rectangular selections to encompass
248 whole lines. If there is no selection, find the paragraph containing
249 the insertion cursor */
250 if (!BufGetSelectionPos(buf
, &left
, &right
, &isRect
, &rectStart
, &rectEnd
)) {
251 left
= findParagraphStart(buf
, insertPos
);
252 right
= findParagraphEnd(buf
, insertPos
);
254 XBell(TheDisplay
, 0);
257 text
= BufGetRange(buf
, left
, right
);
259 left
= BufStartOfLine(buf
, left
);
260 right
= BufEndOfLine(buf
, right
);
261 text
= BufGetTextInRect(buf
, left
, right
, rectStart
, INT_MAX
);
263 left
= BufStartOfLine(buf
, left
);
264 if (right
!= 0 && BufGetCharacter(buf
, right
-1) != '\n') {
265 right
= BufEndOfLine(buf
, right
);
266 if (right
< buf
->length
)
269 BufSelect(buf
, left
, right
);
270 text
= BufGetRange(buf
, left
, right
);
273 /* Find right margin either as specified in the rectangular selection, or
274 by measuring the text and querying the window's wrap margin (or width) */
275 if (hasSelection
&& isRect
) {
276 rightMargin
= rectEnd
- rectStart
;
279 XtVaGetValues(window
->textArea
,
280 textNcolumns
, &nCols
,
281 textNwrapMargin
, &wrapMargin
,
283 rightMargin
= (wrapMargin
== 0 ? nCols
: wrapMargin
);
287 filledText
= fillParagraphs(text
, rightMargin
, buf
->tabDist
, buf
->useTabs
,
288 buf
->nullSubsChar
, &len
, False
);
291 /* Replace the text in the window */
292 if (hasSelection
&& isRect
) {
293 BufReplaceRect(buf
, left
, right
, rectStart
, INT_MAX
, filledText
);
294 BufRectSelect(buf
, left
,
295 BufEndOfLine(buf
, BufCountForwardNLines(buf
, left
,
296 countLines(filledText
)-1)), rectStart
, rectEnd
);
298 BufReplace(buf
, left
, right
, filledText
);
300 BufSelect(buf
, left
, left
+ len
);
304 /* Find a reasonable cursor position. Usually insertPos is best, but
305 if the text was indented, positions can shift */
306 if (hasSelection
&& isRect
)
307 TextSetCursorPos(window
->lastFocus
, buf
->cursorPosHint
);
309 TextSetCursorPos(window
->lastFocus
, insertPos
< left
? left
:
310 (insertPos
> left
+ len
? left
+ len
: insertPos
));
314 ** shift lines left and right in a multi-line text string. Returns the
315 ** shifted text in memory that must be freed by the caller with XtFree.
317 char *ShiftText(char *text
, int direction
, int tabsAllowed
, int tabDist
,
318 int nChars
, int *newLen
)
320 char *shiftedText
, *shiftedLine
;
321 char *textPtr
, *lineStartPtr
, *shiftedPtr
;
325 ** Allocate memory for shifted string. Shift left adds a maximum of
326 ** tabDist-2 characters per line (remove one tab, add tabDist-1 spaces).
327 ** Shift right adds a maximum of nChars character per line.
329 if (direction
== SHIFT_RIGHT
)
330 bufLen
= strlen(text
) + countLines(text
) * nChars
;
332 bufLen
= strlen(text
) + countLines(text
) * tabDist
;
333 shiftedText
= (char *)XtMalloc(bufLen
+ 1);
336 ** break into lines and call shiftLine(Left/Right) on each
340 shiftedPtr
= shiftedText
;
342 if (*textPtr
=='\n' || *textPtr
=='\0') {
343 shiftedLine
= (direction
== SHIFT_RIGHT
) ?
344 shiftLineRight(lineStartPtr
, textPtr
-lineStartPtr
,
345 tabsAllowed
, tabDist
, nChars
) :
346 shiftLineLeft(lineStartPtr
, textPtr
-lineStartPtr
, tabDist
,
348 strcpy(shiftedPtr
, shiftedLine
);
349 shiftedPtr
+= strlen(shiftedLine
);
351 if (*textPtr
== '\0') {
352 /* terminate string & exit loop at end of text */
356 /* move the newline from text to shifted text */
357 *shiftedPtr
++ = *textPtr
++;
359 /* start line over */
360 lineStartPtr
= textPtr
;
364 *newLen
= shiftedPtr
- shiftedText
;
368 static char *shiftLineRight(char *line
, int lineLen
, int tabsAllowed
,
369 int tabDist
, int nChars
)
372 char *lineInPtr
, *lineOutPtr
;
376 lineOut
= XtMalloc(lineLen
+ nChars
+ 1);
377 lineOutPtr
= lineOut
;
380 if (*lineInPtr
== '\0' || (lineInPtr
- line
) >= lineLen
) {
381 /* nothing on line, wipe it out */
384 } else if (*lineInPtr
== ' ') {
385 /* white space continues with tab, advance to next tab stop */
387 *lineOutPtr
++ = *lineInPtr
++;
388 } else if (*lineInPtr
== '\t') {
389 /* white space continues with tab, advance to next tab stop */
390 whiteWidth
= nextTab(whiteWidth
, tabDist
);
391 *lineOutPtr
++ = *lineInPtr
++;
393 /* end of white space, add nChars of space */
394 for (i
=0; i
<nChars
; i
++) {
397 /* if we're now at a tab stop, change last 8 spaces to a tab */
398 if (tabsAllowed
&& atTabStop(whiteWidth
, tabDist
)) {
399 lineOutPtr
-= tabDist
;
400 *lineOutPtr
++ = '\t';
403 /* move remainder of line */
404 while (*lineInPtr
!='\0' && (lineInPtr
- line
) < lineLen
)
405 *lineOutPtr
++ = *lineInPtr
++;
412 static char *shiftLineLeft(char *line
, int lineLen
, int tabDist
, int nChars
)
415 int i
, whiteWidth
, lastWhiteWidth
, whiteGoal
;
416 char *lineInPtr
, *lineOutPtr
;
419 lineOut
= XtMalloc(lineLen
+ tabDist
+ 1);
420 lineOutPtr
= lineOut
;
424 if (*lineInPtr
== '\0' || (lineInPtr
- line
) >= lineLen
) {
425 /* nothing on line, wipe it out */
428 } else if (*lineInPtr
== ' ') {
429 /* white space continues with space, advance one character */
431 *lineOutPtr
++ = *lineInPtr
++;
432 } else if (*lineInPtr
== '\t') {
433 /* white space continues with tab, advance to next tab stop */
434 /* save the position, though, in case we need to remove the tab */
435 lastWhiteWidth
= whiteWidth
;
436 whiteWidth
= nextTab(whiteWidth
, tabDist
);
437 *lineOutPtr
++ = *lineInPtr
++;
439 /* end of white space, remove nChars characters */
440 for (i
=1; i
<=nChars
; i
++) {
441 if (lineOutPtr
> lineOut
) {
442 if (*(lineOutPtr
-1) == ' ') {
443 /* end of white space is a space, just remove it */
446 /* end of white space is a tab, remove it and add
449 whiteGoal
= whiteWidth
- i
;
450 whiteWidth
= lastWhiteWidth
;
451 while (whiteWidth
< whiteGoal
) {
458 /* move remainder of line */
459 while (*lineInPtr
!='\0' && (lineInPtr
- line
) < lineLen
)
460 *lineOutPtr
++ = *lineInPtr
++;
468 static int atTabStop(int pos
, int tabDist
)
470 return (pos
%tabDist
== 0);
473 static int nextTab(int pos
, int tabDist
)
475 return (pos
/tabDist
)*tabDist
+ tabDist
;
478 static int countLines(const char *text
)
482 while(*text
!= '\0') {
483 if (*text
++ == '\n') {
491 ** Find the implied left margin of a text string (the number of columns to the
492 ** first non-whitespace character on any line) up to either the terminating
493 ** null character at the end of the string, or "length" characters, whever
496 static int findLeftMargin(char *text
, int length
, int tabDist
)
499 int col
= 0, leftMargin
= INT_MAX
;
502 for (c
=text
; *c
!='\0' && c
-text
<length
; c
++) {
504 col
+= BufCharWidth('\t', col
, tabDist
, '\0');
505 } else if (*c
== ' ') {
507 } else if (*c
== '\n') {
512 if (col
< leftMargin
&& inMargin
)
518 /* if no non-white text is found, the leftMargin will never be set */
519 if (leftMargin
== INT_MAX
)
526 ** Fill multiple paragraphs between rightMargin and an implied left margin
527 ** and first line indent determined by analyzing the text. alignWithFirst
528 ** aligns subsequent paragraphs with the margins of the first paragraph (a
529 ** capability not currently used in NEdit, but carried over from code for
530 ** previous versions which did all paragraphs together).
532 static char *fillParagraphs(char *text
, int rightMargin
, int tabDist
,
533 int useTabs
, char nullSubsChar
, int *filledLen
, int alignWithFirst
)
535 int paraStart
, paraEnd
, fillEnd
;
536 char *c
, ch
, *secondLineStart
, *paraText
, *filledText
;
537 int firstLineLen
, firstLineIndent
, leftMargin
, len
;
540 /* Create a buffer to accumulate the filled paragraphs */
542 BufSetAll(buf
, text
);
545 ** Loop over paragraphs, filling each one, and accumulating the results
551 /* Skip over white space */
552 while (paraStart
< buf
->length
) {
553 ch
= BufGetCharacter(buf
, paraStart
);
554 if (ch
!= ' ' && ch
!= '\t' && ch
!= '\n')
558 if (paraStart
>= buf
->length
)
560 paraStart
= BufStartOfLine(buf
, paraStart
);
562 /* Find the end of the paragraph */
563 paraEnd
= findParagraphEnd(buf
, paraStart
);
565 /* Operate on either the one paragraph, or to make them all identical,
566 do all of them together (fill paragraph can format all the paragraphs
567 it finds with identical specs if it gets passed more than one) */
568 fillEnd
= alignWithFirst
? buf
->length
: paraEnd
;
570 /* Get the paragraph in a text string (or all of the paragraphs if
571 we're making them all the same) */
572 paraText
= BufGetRange(buf
, paraStart
, fillEnd
);
574 /* Find separate left margins for the first and for the first line of
575 the paragraph, and for rest of the remainder of the paragraph */
576 for (c
=paraText
; *c
!='\0' && *c
!='\n'; c
++);
577 firstLineLen
= c
- paraText
;
578 secondLineStart
= *c
== '\0' ? paraText
: c
+ 1;
579 firstLineIndent
= findLeftMargin(paraText
, firstLineLen
, tabDist
);
580 leftMargin
= findLeftMargin(secondLineStart
, paraEnd
- paraStart
-
581 (secondLineStart
- paraText
), tabDist
);
583 /* Fill the paragraph */
584 filledText
= fillParagraph(paraText
, leftMargin
, firstLineIndent
,
585 rightMargin
, tabDist
, useTabs
, nullSubsChar
, &len
);
588 /* Replace it in the buffer */
589 BufReplace(buf
, paraStart
, fillEnd
, filledText
);
592 /* move on to the next paragraph */
596 /* Free the buffer and return its contents */
597 filledText
= BufGetAll(buf
);
598 *filledLen
= buf
->length
;
604 ** Trim leading space, and arrange text to fill between leftMargin and
605 ** rightMargin (except for the first line which fills from firstLineIndent),
606 ** re-creating whitespace to the left of the text using tabs (if allowTabs is
607 ** True) calculated using tabDist, and spaces. Returns a newly allocated
608 ** string as the function result, and the length of the new string in filledLen.
610 static char *fillParagraph(char *text
, int leftMargin
, int firstLineIndent
,
611 int rightMargin
, int tabDist
, int allowTabs
, char nullSubsChar
,
614 char *cleanedText
, *outText
, *indentString
, *leadIndentStr
, *outPtr
, *c
, *b
;
615 int col
, cleanedLen
, indentLen
, leadIndentLen
, nLines
= 1;
616 int inWhitespace
, inMargin
;
618 /* remove leading spaces, convert newlines to spaces */
619 cleanedText
= XtMalloc(strlen(text
)+1);
620 outPtr
= cleanedText
;
622 for (c
=text
; *c
!='\0'; c
++) {
623 if (*c
== '\t' || *c
== ' ') {
626 } else if (*c
== '\n') {
628 /* a newline before any text separates paragraphs, so leave
629 it in, back up, and convert the previous space back to \n */
630 if (outPtr
> cleanedText
&& *(outPtr
-1) == ' ')
642 cleanedLen
= outPtr
- cleanedText
;
645 /* Put back newlines breaking text at word boundaries within the margins.
646 Algorithm: scan through characters, counting columns, and when the
647 margin width is exceeded, search backward for beginning of the word
648 and convert the last whitespace character into a newline */
649 col
= firstLineIndent
;
650 for (c
=cleanedText
; *c
!='\0'; c
++) {
654 col
+= BufCharWidth(*c
, col
, tabDist
, nullSubsChar
);
655 if (col
-1 > rightMargin
) {
657 for (b
=c
; b
>=cleanedText
&& *b
!='\n'; b
--) {
658 if (*b
== '\t' || *b
== ' ') {
667 inWhitespace
= False
;
673 /* produce a string to prepend to lines to indent them to the left margin */
674 leadIndentStr
= makeIndentString(firstLineIndent
, tabDist
,
675 allowTabs
, &leadIndentLen
);
676 indentString
= makeIndentString(leftMargin
, tabDist
, allowTabs
, &indentLen
);
678 /* allocate memory for the finished string */
679 outText
= XtMalloc(sizeof(char) * (cleanedLen
+ leadIndentLen
+
680 indentLen
* (nLines
-1) + 1));
683 /* prepend the indent string to each line of the filled text */
684 strncpy(outPtr
, leadIndentStr
, leadIndentLen
);
685 outPtr
+= leadIndentLen
;
686 for (c
=cleanedText
; *c
!='\0'; c
++) {
689 strncpy(outPtr
, indentString
, indentLen
);
694 /* convert any trailing space to newline. Add terminating null */
695 if (*(outPtr
-1) == ' ')
699 /* clean up, return result */
701 XtFree(leadIndentStr
);
702 XtFree(indentString
);
703 *filledLen
= outPtr
- outText
;
707 static char *makeIndentString(int indent
, int tabDist
, int allowTabs
, int *nChars
)
709 char *indentString
, *outPtr
;
712 outPtr
= indentString
= XtMalloc(sizeof(char) * indent
+ 1);
714 for (i
=0; i
<indent
/tabDist
; i
++)
716 for (i
=0; i
<indent
%tabDist
; i
++)
719 for (i
=0; i
<indent
; i
++)
723 *nChars
= outPtr
- indentString
;
728 ** Find the boundaries of the paragraph containing pos
730 static int findParagraphEnd(textBuffer
*buf
, int startPos
)
734 static char whiteChars
[] = " \t";
736 pos
= BufEndOfLine(buf
, startPos
)+1;
737 while (pos
< buf
->length
) {
738 c
= BufGetCharacter(buf
, pos
);
741 if (strchr(whiteChars
, c
) != NULL
)
744 pos
= BufEndOfLine(buf
, pos
)+1;
746 return pos
< buf
->length
? pos
: buf
->length
;
748 static int findParagraphStart(textBuffer
*buf
, int startPos
)
752 static char whiteChars
[] = " \t";
756 parStart
= BufStartOfLine(buf
, startPos
);
759 c
= BufGetCharacter(buf
, pos
);
762 if (strchr(whiteChars
, c
) != NULL
)
765 parStart
= BufStartOfLine(buf
, pos
);
769 return parStart
> 0 ? parStart
: 0;