1 static const char CVSID
[] = "$Id: shift.c,v 1.13 2002/07/11 21:18:10 slobasso 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"
38 #include "../util/DialogF.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 text
= BufGetAll(tempBuf
);
173 BufReplace(buf
, selStart
, selEnd
, text
);
175 BufRectSelect(buf
, selStart
, selStart
+ tempBuf
->length
,
176 rectStart
+offset
, rectEnd
+offset
);
180 void UpcaseSelection(WindowInfo
*window
)
182 changeCase(window
, True
);
185 void DowncaseSelection(WindowInfo
*window
)
187 changeCase(window
, False
);
191 ** Capitalize or lowercase the contents of the selection (or of the character
192 ** before the cursor if there is no selection). If "makeUpper" is true,
193 ** change to upper case, otherwise, change to lower case.
195 static void changeCase(WindowInfo
*window
, int makeUpper
)
197 textBuffer
*buf
= window
->buffer
;
199 int cursorPos
, start
, end
, isRect
, rectStart
, rectEnd
;
201 /* Get the selection. Use character before cursor if no selection */
202 if (!BufGetSelectionPos(buf
, &start
, &end
, &isRect
, &rectStart
, &rectEnd
)) {
203 char bufChar
[2] = " ";
204 cursorPos
= TextGetCursorPos(window
->lastFocus
);
205 if (cursorPos
== 0) {
206 XBell(TheDisplay
, 0);
209 *bufChar
= BufGetCharacter(buf
, cursorPos
-1);
210 *bufChar
= makeUpper
? toupper((unsigned char)*bufChar
) :
211 tolower((unsigned char)*bufChar
);
212 BufReplace(buf
, cursorPos
-1, cursorPos
, bufChar
);
214 text
= BufGetSelectionText(buf
);
215 for (c
=text
; *c
!='\0'; c
++)
216 *c
= makeUpper
? toupper((unsigned char)*c
) :
217 tolower((unsigned char)*c
);
218 BufReplaceSelected(buf
, text
);
221 BufRectSelect(buf
, start
, end
, rectStart
, rectEnd
);
223 BufSelect(buf
, start
, end
);
227 void FillSelection(WindowInfo
*window
)
229 textBuffer
*buf
= window
->buffer
;
230 char *text
, *filledText
;
231 int left
, right
, nCols
, len
, isRect
, rectStart
, rectEnd
;
232 int rightMargin
, wrapMargin
;
233 int insertPos
= TextGetCursorPos(window
->lastFocus
);
234 int hasSelection
= window
->buffer
->primary
.selected
;
236 /* Find the range of characters and get the text to fill. If there is a
237 selection, use it but extend non-rectangular selections to encompass
238 whole lines. If there is no selection, find the paragraph containing
239 the insertion cursor */
240 if (!BufGetSelectionPos(buf
, &left
, &right
, &isRect
, &rectStart
, &rectEnd
)) {
241 left
= findParagraphStart(buf
, insertPos
);
242 right
= findParagraphEnd(buf
, insertPos
);
244 XBell(TheDisplay
, 0);
247 text
= BufGetRange(buf
, left
, right
);
249 left
= BufStartOfLine(buf
, left
);
250 right
= BufEndOfLine(buf
, right
);
251 text
= BufGetTextInRect(buf
, left
, right
, rectStart
, INT_MAX
);
253 left
= BufStartOfLine(buf
, left
);
254 if (right
!= 0 && BufGetCharacter(buf
, right
-1) != '\n') {
255 right
= BufEndOfLine(buf
, right
);
256 if (right
< buf
->length
)
259 BufSelect(buf
, left
, right
);
260 text
= BufGetRange(buf
, left
, right
);
263 /* Find right margin either as specified in the rectangular selection, or
264 by measuring the text and querying the window's wrap margin (or width) */
265 if (hasSelection
&& isRect
) {
266 rightMargin
= rectEnd
- rectStart
;
268 XtVaGetValues(window
->textArea
, textNcolumns
, &nCols
,
269 textNwrapMargin
, &wrapMargin
, NULL
);
270 rightMargin
= (wrapMargin
== 0 ? nCols
: wrapMargin
) - 1;
274 filledText
= fillParagraphs(text
, rightMargin
, buf
->tabDist
, buf
->useTabs
,
275 buf
->nullSubsChar
, &len
, False
);
278 /* Replace the text in the window */
279 if (hasSelection
&& isRect
) {
280 BufReplaceRect(buf
, left
, right
, rectStart
, INT_MAX
, filledText
);
281 BufRectSelect(buf
, left
,
282 BufEndOfLine(buf
, BufCountForwardNLines(buf
, left
,
283 countLines(filledText
)-1)), rectStart
, rectEnd
);
285 BufReplace(buf
, left
, right
, filledText
);
287 BufSelect(buf
, left
, left
+ len
);
291 /* Find a reasonable cursor position. Usually insertPos is best, but
292 if the text was indented, positions can shift */
293 if (hasSelection
&& isRect
)
294 TextSetCursorPos(window
->lastFocus
, buf
->cursorPosHint
);
296 TextSetCursorPos(window
->lastFocus
, insertPos
< left
? left
:
297 (insertPos
> left
+ len
? left
+ len
: insertPos
));
301 ** shift lines left and right in a multi-line text string. Returns the
302 ** shifted text in memory that must be freed by the caller with XtFree.
304 char *ShiftText(char *text
, int direction
, int tabsAllowed
, int tabDist
,
305 int nChars
, int *newLen
)
307 char *shiftedText
, *shiftedLine
;
308 char *textPtr
, *lineStartPtr
, *shiftedPtr
;
312 ** Allocate memory for shifted string. Shift left adds a maximum of
313 ** tabDist-2 characters per line (remove one tab, add tabDist-1 spaces).
314 ** Shift right adds a maximum of nChars character per line.
316 if (direction
== SHIFT_RIGHT
)
317 bufLen
= strlen(text
) + countLines(text
) * nChars
;
319 bufLen
= strlen(text
) + countLines(text
) * tabDist
;
320 shiftedText
= (char *)XtMalloc(bufLen
+ 1);
323 ** break into lines and call shiftLine(Left/Right) on each
327 shiftedPtr
= shiftedText
;
329 if (*textPtr
=='\n' || *textPtr
=='\0') {
330 shiftedLine
= (direction
== SHIFT_RIGHT
) ?
331 shiftLineRight(lineStartPtr
, textPtr
-lineStartPtr
,
332 tabsAllowed
, tabDist
, nChars
) :
333 shiftLineLeft(lineStartPtr
, textPtr
-lineStartPtr
, tabDist
,
335 strcpy(shiftedPtr
, shiftedLine
);
336 shiftedPtr
+= strlen(shiftedLine
);
338 if (*textPtr
== '\0') {
339 /* terminate string & exit loop at end of text */
343 /* move the newline from text to shifted text */
344 *shiftedPtr
++ = *textPtr
++;
346 /* start line over */
347 lineStartPtr
= textPtr
;
351 *newLen
= shiftedPtr
- shiftedText
;
355 static char *shiftLineRight(char *line
, int lineLen
, int tabsAllowed
,
356 int tabDist
, int nChars
)
359 char *lineInPtr
, *lineOutPtr
;
363 lineOut
= XtMalloc(lineLen
+ nChars
+ 1);
364 lineOutPtr
= lineOut
;
367 if (*lineInPtr
== '\0' || (lineInPtr
- line
) >= lineLen
) {
368 /* nothing on line, wipe it out */
371 } else if (*lineInPtr
== ' ') {
372 /* white space continues with tab, advance to next tab stop */
374 *lineOutPtr
++ = *lineInPtr
++;
375 } else if (*lineInPtr
== '\t') {
376 /* white space continues with tab, advance to next tab stop */
377 whiteWidth
= nextTab(whiteWidth
, tabDist
);
378 *lineOutPtr
++ = *lineInPtr
++;
380 /* end of white space, add nChars of space */
381 for (i
=0; i
<nChars
; i
++) {
384 /* if we're now at a tab stop, change last 8 spaces to a tab */
385 if (tabsAllowed
&& atTabStop(whiteWidth
, tabDist
)) {
386 lineOutPtr
-= tabDist
;
387 *lineOutPtr
++ = '\t';
390 /* move remainder of line */
391 while (*lineInPtr
!='\0' && (lineInPtr
- line
) < lineLen
)
392 *lineOutPtr
++ = *lineInPtr
++;
399 static char *shiftLineLeft(char *line
, int lineLen
, int tabDist
, int nChars
)
402 int i
, whiteWidth
, lastWhiteWidth
, whiteGoal
;
403 char *lineInPtr
, *lineOutPtr
;
406 lineOut
= XtMalloc(lineLen
+ tabDist
+ 1);
407 lineOutPtr
= lineOut
;
411 if (*lineInPtr
== '\0' || (lineInPtr
- line
) >= lineLen
) {
412 /* nothing on line, wipe it out */
415 } else if (*lineInPtr
== ' ') {
416 /* white space continues with space, advance one character */
418 *lineOutPtr
++ = *lineInPtr
++;
419 } else if (*lineInPtr
== '\t') {
420 /* white space continues with tab, advance to next tab stop */
421 /* save the position, though, in case we need to remove the tab */
422 lastWhiteWidth
= whiteWidth
;
423 whiteWidth
= nextTab(whiteWidth
, tabDist
);
424 *lineOutPtr
++ = *lineInPtr
++;
426 /* end of white space, remove nChars characters */
427 for (i
=1; i
<=nChars
; i
++) {
428 if (lineOutPtr
> lineOut
) {
429 if (*(lineOutPtr
-1) == ' ') {
430 /* end of white space is a space, just remove it */
433 /* end of white space is a tab, remove it and add
436 whiteGoal
= whiteWidth
- i
;
437 whiteWidth
= lastWhiteWidth
;
438 while (whiteWidth
< whiteGoal
) {
445 /* move remainder of line */
446 while (*lineInPtr
!='\0' && (lineInPtr
- line
) < lineLen
)
447 *lineOutPtr
++ = *lineInPtr
++;
455 static int atTabStop(int pos
, int tabDist
)
457 return (pos
%tabDist
== 0);
460 static int nextTab(int pos
, int tabDist
)
462 return (pos
/tabDist
)*tabDist
+ tabDist
;
465 static int countLines(const char *text
)
469 while(*text
!= '\0') {
470 if (*text
++ == '\n') {
478 ** Find the implied left margin of a text string (the number of columns to the
479 ** first non-whitespace character on any line) up to either the terminating
480 ** null character at the end of the string, or "length" characters, whever
483 static int findLeftMargin(char *text
, int length
, int tabDist
)
486 int col
= 0, leftMargin
= INT_MAX
;
489 for (c
=text
; *c
!='\0' && c
-text
<length
; c
++) {
491 col
+= BufCharWidth('\t', col
, tabDist
, '\0');
492 } else if (*c
== ' ') {
494 } else if (*c
== '\n') {
499 if (col
< leftMargin
&& inMargin
)
505 /* if no non-white text is found, the leftMargin will never be set */
506 if (leftMargin
== INT_MAX
)
513 ** Fill multiple paragraphs between rightMargin and an implied left margin
514 ** and first line indent determined by analyzing the text. alignWithFirst
515 ** aligns subsequent paragraphs with the margins of the first paragraph (a
516 ** capability not currently used in NEdit, but carried over from code for
517 ** previous versions which did all paragraphs together).
519 static char *fillParagraphs(char *text
, int rightMargin
, int tabDist
,
520 int useTabs
, char nullSubsChar
, int *filledLen
, int alignWithFirst
)
522 int paraStart
, paraEnd
, fillEnd
;
523 char *c
, ch
, *secondLineStart
, *paraText
, *filledText
;
524 int firstLineLen
, firstLineIndent
, leftMargin
, len
;
527 /* Create a buffer to accumulate the filled paragraphs */
529 BufSetAll(buf
, text
);
532 ** Loop over paragraphs, filling each one, and accumulating the results
538 /* Skip over white space */
539 while (paraStart
< buf
->length
) {
540 ch
= BufGetCharacter(buf
, paraStart
);
541 if (ch
!= ' ' && ch
!= '\t' && ch
!= '\n')
545 if (paraStart
>= buf
->length
)
547 paraStart
= BufStartOfLine(buf
, paraStart
);
549 /* Find the end of the paragraph */
550 paraEnd
= findParagraphEnd(buf
, paraStart
);
552 /* Operate on either the one paragraph, or to make them all identical,
553 do all of them together (fill paragraph can format all the paragraphs
554 it finds with identical specs if it gets passed more than one) */
555 fillEnd
= alignWithFirst
? buf
->length
: paraEnd
;
557 /* Get the paragraph in a text string (or all of the paragraphs if
558 we're making them all the same) */
559 paraText
= BufGetRange(buf
, paraStart
, fillEnd
);
561 /* Find separate left margins for the first and for the first line of
562 the paragraph, and for rest of the remainder of the paragraph */
563 for (c
=paraText
; *c
!='\0' && *c
!='\n'; c
++);
564 firstLineLen
= c
- paraText
;
565 secondLineStart
= *c
== '\0' ? paraText
: c
+ 1;
566 firstLineIndent
= findLeftMargin(paraText
, firstLineLen
, tabDist
);
567 leftMargin
= findLeftMargin(secondLineStart
, paraEnd
- paraStart
-
568 (secondLineStart
- paraText
), tabDist
);
570 /* Fill the paragraph */
571 filledText
= fillParagraph(paraText
, leftMargin
, firstLineIndent
,
572 rightMargin
, tabDist
, useTabs
, nullSubsChar
, &len
);
575 /* Replace it in the buffer */
576 BufReplace(buf
, paraStart
, fillEnd
, filledText
);
579 /* move on to the next paragraph */
583 /* Free the buffer and return its contents */
584 filledText
= BufGetAll(buf
);
585 *filledLen
= buf
->length
;
591 ** Trim leading space, and arrange text to fill between leftMargin and
592 ** rightMargin (except for the first line which fills from firstLineIndent),
593 ** re-creating whitespace to the left of the text using tabs (if allowTabs is
594 ** True) calculated using tabDist, and spaces. Returns a newly allocated
595 ** string as the function result, and the length of the new string in filledLen.
597 static char *fillParagraph(char *text
, int leftMargin
, int firstLineIndent
,
598 int rightMargin
, int tabDist
, int allowTabs
, char nullSubsChar
,
601 char *cleanedText
, *outText
, *indentString
, *leadIndentStr
, *outPtr
, *c
, *b
;
602 int col
, cleanedLen
, indentLen
, leadIndentLen
, nLines
= 1;
603 int inWhitespace
, inMargin
;
605 /* remove leading spaces, convert newlines to spaces */
606 cleanedText
= XtMalloc(strlen(text
)+1);
607 outPtr
= cleanedText
;
609 for (c
=text
; *c
!='\0'; c
++) {
610 if (*c
== '\t' || *c
== ' ') {
613 } else if (*c
== '\n') {
615 /* a newline before any text separates paragraphs, so leave
616 it in, back up, and convert the previous space back to \n */
617 if (outPtr
> cleanedText
&& *(outPtr
-1) == ' ')
629 cleanedLen
= outPtr
- cleanedText
;
632 /* Put back newlines breaking text at word boundaries within the margins.
633 Algorithm: scan through characters, counting columns, and when the
634 margin width is exceeded, search backward for beginning of the word
635 and convert the last whitespace character into a newline */
636 col
= firstLineIndent
;
637 for (c
=cleanedText
; *c
!='\0'; c
++) {
641 col
+= BufCharWidth(*c
, col
, tabDist
, nullSubsChar
);
642 if (col
-1 > rightMargin
) {
644 for (b
=c
; b
>=cleanedText
&& *b
!='\n'; b
--) {
645 if (*b
== '\t' || *b
== ' ') {
654 inWhitespace
= False
;
660 /* produce a string to prepend to lines to indent them to the left margin */
661 leadIndentStr
= makeIndentString(firstLineIndent
, tabDist
,
662 allowTabs
, &leadIndentLen
);
663 indentString
= makeIndentString(leftMargin
, tabDist
, allowTabs
, &indentLen
);
665 /* allocate memory for the finished string */
666 outText
= XtMalloc(sizeof(char) * (cleanedLen
+ leadIndentLen
+
667 indentLen
* (nLines
-1) + 1));
670 /* prepend the indent string to each line of the filled text */
671 strncpy(outPtr
, leadIndentStr
, leadIndentLen
);
672 outPtr
+= leadIndentLen
;
673 for (c
=cleanedText
; *c
!='\0'; c
++) {
676 strncpy(outPtr
, indentString
, indentLen
);
681 /* convert any trailing space to newline. Add terminating null */
682 if (*(outPtr
-1) == ' ')
686 /* clean up, return result */
688 XtFree(leadIndentStr
);
689 XtFree(indentString
);
690 *filledLen
= outPtr
- outText
;
694 static char *makeIndentString(int indent
, int tabDist
, int allowTabs
, int *nChars
)
696 char *indentString
, *outPtr
;
699 outPtr
= indentString
= XtMalloc(sizeof(char) * indent
+ 1);
701 for (i
=0; i
<indent
/tabDist
; i
++)
703 for (i
=0; i
<indent
%tabDist
; i
++)
706 for (i
=0; i
<indent
; i
++)
710 *nChars
= outPtr
- indentString
;
715 ** Find the boundaries of the paragraph containing pos
717 static int findParagraphEnd(textBuffer
*buf
, int startPos
)
721 static char whiteChars
[] = " \t";
723 pos
= BufEndOfLine(buf
, startPos
)+1;
724 while (pos
< buf
->length
) {
725 c
= BufGetCharacter(buf
, pos
);
728 if (strchr(whiteChars
, c
) != NULL
)
731 pos
= BufEndOfLine(buf
, pos
)+1;
733 return pos
< buf
->length
? pos
: buf
->length
;
735 static int findParagraphStart(textBuffer
*buf
, int startPos
)
739 static char whiteChars
[] = " \t";
743 parStart
= BufStartOfLine(buf
, startPos
);
746 c
= BufGetCharacter(buf
, pos
);
749 if (strchr(whiteChars
, c
) != NULL
)
752 parStart
= BufStartOfLine(buf
, pos
);
756 return parStart
> 0 ? parStart
: 0;