UNIXworkcode

1 /******************************************************************************* 2 * * 3 * shift.c -- Nirvana Editor built-in filter commands * 4 * * 5 * Copyright (C) 1999 Mark Edel * 6 * * 7 * This is free software; you can redistribute it and/or modify it under the * 8 * terms of the GNU General Public License as published by the Free Software * 9 * Foundation; either version 2 of the License, or (at your option) any later * 10 * version. In addition, you may distribute version of this program linked to * 11 * Motif or Open Motif. See README for details. * 12 * * 13 * This software is distributed in the hope that it will be useful, but WITHOUT * 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * 16 * for more details. * 17 * * 18 * You should have received a copy of the GNU General Public License along with * 19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple * 20 * Place, Suite 330, Boston, MA 02111-1307 USA * 21 * * 22 * Nirvana Text Editor * 23 * June 18, 1991 * 24 * * 25 * Written by Mark Edel * 26 * * 27 *******************************************************************************/ 28 29 #ifdef HAVE_CONFIG_H 30 #include "../config.h" 31 #endif 32 33 #include "shift.h" 34 #include "textBuf.h" 35 #include "text.h" 36 #include "nedit.h" 37 #include "window.h" 38 #include "../util/nedit_malloc.h" 39 40 #include <string.h> 41 #include <limits.h> 42 #include <ctype.h> 43 #include <locale.h> 44 #include <wctype.h> 45 #include <wchar.h> 46 #include <sys/param.h> 47 48 #include <Xm/Xm.h> 49 50 #ifdef HAVE_DEBUG_H 51 #include "../debug.h" 52 #endif 53 54 55 static void shiftRect(WindowInfo *window, int direction, int byTab, 56 int selStart, int selEnd, int rectStart, int rectEnd); 57 static void changeCase(WindowInfo *window, int makeUpper); 58 static char *shiftLineRight(char *line, int lineLen, int tabsAllowed, 59 int tabDist, int nChars); 60 static char *shiftLineLeft(char *line, int lineLen, int tabDist, int nChars); 61 static int findLeftMargin(char *text, int length, int tabDist); 62 static char *fillParagraphs(char *text, int rightMargin, int tabDist, 63 int useTabs, char nullSubsChar, int *filledLen, int alignWithFirst); 64 static char *fillParagraph(char *text, int leftMargin, int firstLineIndent, 65 int rightMargin, int tabDist, int allowTabs, char nullSubsChar, 66 int *filledLen); 67 static char *makeIndentString(int indent, int tabDist, int allowTabs, int *nChars); 68 static int atTabStop(int pos, int tabDist); 69 static int nextTab(int pos, int tabDist); 70 static int countLines(const char *text); 71 static int findParagraphStart(textBuffer *buf, int startPos); 72 static int findParagraphEnd(textBuffer *buf, int startPos); 73 74 /* 75 ** Shift the selection left or right by a single character, or by one tab stop 76 ** if "byTab" is true. (The length of a tab stop is the size of an emulated 77 ** tab if emulated tabs are turned on, or a hardware tab if not). 78 */ 79 void ShiftSelection(WindowInfo *window, int direction, int byTab) 80 { 81 int selStart, selEnd, isRect, rectStart, rectEnd; 82 int shiftedLen, newEndPos, cursorPos, origLength, emTabDist, shiftDist; 83 char *text, *shiftedText; 84 textBuffer *buf = window->buffer; 85 86 /* get selection, if no text selected, use current insert position */ 87 if (!BufGetSelectionPos(buf, &selStart, &selEnd, &isRect, 88 &rectStart, &rectEnd)) { 89 cursorPos = TextGetCursorPos(window->lastFocus); 90 selStart = BufStartOfLine(buf, cursorPos); 91 selEnd = BufEndOfLine(buf, cursorPos); 92 if (selEnd < buf->length) 93 selEnd++; 94 BufSelect(buf, selStart, selEnd); 95 isRect = False; 96 text = BufGetRange(buf, selStart, selEnd); 97 } else if (isRect) { 98 cursorPos = TextGetCursorPos(window->lastFocus); 99 origLength = buf->length; 100 shiftRect(window, direction, byTab, selStart, selEnd, rectStart, 101 rectEnd); 102 TextSetCursorPos(window->lastFocus, (cursorPos < (selEnd+selStart)/2) ? 103 selStart : cursorPos + (buf->length - origLength)); 104 return; 105 } else { 106 selStart = BufStartOfLine(buf, selStart); 107 if (selEnd != 0 && BufGetCharacter(buf, selEnd-1) != '\n') { 108 selEnd = BufEndOfLine(buf, selEnd); 109 if (selEnd < buf->length) 110 selEnd++; 111 } 112 BufSelect(buf, selStart, selEnd); 113 text = BufGetRange(buf, selStart, selEnd); 114 } 115 116 /* shift the text by the appropriate distance */ 117 if (byTab) { 118 XtVaGetValues(window->textArea, textNemulateTabs, &emTabDist, NULL); 119 shiftDist = emTabDist == 0 ? buf->tabDist : emTabDist; 120 } else 121 shiftDist = 1; 122 shiftedText = ShiftText(text, direction, buf->useTabs, buf->tabDist, 123 shiftDist, &shiftedLen); 124 NEditFree(text); 125 BufReplaceSelected(buf, shiftedText); 126 NEditFree(shiftedText); 127 128 newEndPos = selStart + shiftedLen; 129 BufSelect(buf, selStart, newEndPos); 130 } 131 132 static void shiftRect(WindowInfo *window, int direction, int byTab, 133 int selStart, int selEnd, int rectStart, int rectEnd) 134 { 135 int offset, emTabDist; 136 textBuffer *tempBuf, *buf = window->buffer; 137 char *text; 138 139 /* Make sure selStart and SelEnd refer to whole lines */ 140 selStart = BufStartOfLine(buf, selStart); 141 selEnd = BufEndOfLine(buf, selEnd); 142 143 /* Calculate the the left/right offset for the new rectangle */ 144 if (byTab) { 145 XtVaGetValues(window->textArea, textNemulateTabs, &emTabDist, NULL); 146 offset = emTabDist == 0 ? buf->tabDist : emTabDist; 147 } else 148 offset = 1; 149 offset *= direction == SHIFT_LEFT ? -1 : 1; 150 if (rectStart + offset < 0) 151 offset = -rectStart; 152 153 /* Create a temporary buffer for the lines containing the selection, to 154 hide the intermediate steps from the display update routines */ 155 tempBuf = BufCreate(); 156 tempBuf->tabDist = buf->tabDist; 157 tempBuf->useTabs = buf->useTabs; 158 text = BufGetRange(buf, selStart, selEnd); 159 BufSetAll(tempBuf, text); 160 NEditFree(text); 161 162 /* Do the shift in the temporary buffer */ 163 text = BufGetTextInRect(buf, selStart, selEnd, rectStart, rectEnd); 164 BufRemoveRect(tempBuf, 0, selEnd-selStart, rectStart, rectEnd); 165 BufInsertCol(tempBuf, rectStart+offset, 0, text, NULL, NULL); 166 NEditFree(text); 167 168 /* Make the change in the real buffer */ 169 BufReplace(buf, selStart, selEnd, BufAsString(tempBuf)); 170 BufRectSelect(buf, selStart, selStart + tempBuf->length, 171 rectStart+offset, rectEnd+offset); 172 BufFree(tempBuf); 173 } 174 175 void UpcaseSelection(WindowInfo *window) 176 { 177 changeCase(window, True); 178 } 179 180 void DowncaseSelection(WindowInfo *window) 181 { 182 changeCase(window, False); 183 } 184 185 /* 186 ** Capitalize or lowercase the contents of the selection (or of the character 187 ** before the cursor if there is no selection). If "makeUpper" is true, 188 ** change to upper case, otherwise, change to lower case. 189 */ 190 static void changeCase(WindowInfo *window, int makeUpper) 191 { 192 textBuffer *buf = window->buffer; 193 char *text, *c; 194 int cursorPos, start, end, isRect, rectStart, rectEnd; 195 196 char *bak_locale = NULL; 197 if(!XNEditDefaultCharsetIsUTF8()) { 198 bak_locale = setlocale(LC_CTYPE, NULL); 199 setlocale(LC_CTYPE, "C.UTF-8"); 200 } 201 202 /* Get the selection. Use character before cursor if no selection */ 203 if (!BufGetSelectionPos(buf, &start, &end, &isRect, &rectStart, &rectEnd)) { 204 cursorPos = TextGetCursorPos(window->lastFocus); 205 if (cursorPos == 0) { 206 XBell(TheDisplay, 0); 207 if(bak_locale) setlocale(LC_CTYPE, bak_locale); 208 return; 209 } 210 211 int leftPos = BufLeftPos(buf, cursorPos); 212 213 wchar_t w = BufGetCharacterW(buf, leftPos); 214 wchar_t wc = makeUpper ? towupper(w) : towlower(w); 215 if(wc == 0) wc = w; 216 217 char bufChar[8]; 218 int clen = wctomb(bufChar, wc); 219 bufChar[clen] = 0; 220 221 BufReplace(buf, leftPos, cursorPos, bufChar); 222 } else { 223 Boolean modified = False; 224 225 text = BufGetSelectionText(buf); 226 size_t textlen = strlen(text); 227 228 // reserve some extra space for terminator and to prevent realloc 229 size_t conv_alloc = textlen + 8; 230 size_t conv_len = 0; 231 char *converted = NEditMalloc(conv_alloc); 232 233 mbstate_t state; 234 memset(&state, 0, sizeof(mbstate_t)); 235 236 int inc = 1; // text increment 237 size_t i = 0; // text position 238 size_t cpos = 0; // converted position 239 char utf8[8]; // buffer for converting wchar_t to multibyte 240 int append_len = 0; // number of bytes that should be appended to dst 241 for(c = text; *c != '\0'; c += inc) { 242 inc = Utf8CharLen((unsigned char*)c); 243 if(i + inc > textlen) { 244 inc = textlen - i; 245 } 246 247 // convert utf8 to wchar_t 248 // if it works, convert to upper/lower 249 // if not, just copy from text to converted 250 wchar_t w; 251 size_t nconverted = mbrtowc(&w, c, inc, &state); 252 if(nconverted > 0) { 253 wchar_t wc = makeUpper ? towupper(w) : towlower(w); 254 append_len = wctomb(utf8, wc); 255 if(wc != w) { 256 modified = True; 257 } 258 inc = nconverted; 259 } else { 260 append_len = inc; 261 } 262 263 if(conv_alloc - cpos - 1 < append_len) { 264 conv_alloc += 32; 265 converted = NEditRealloc(converted, conv_alloc); 266 } 267 memcpy(converted + cpos, utf8, append_len); 268 conv_len += append_len; 269 270 cpos += append_len; 271 i += inc; 272 } 273 274 converted[conv_len] = '\0'; 275 276 if (modified) { 277 BufReplaceSelected(buf, converted); 278 } 279 280 NEditFree(converted); 281 NEditFree(text); 282 if (isRect) 283 BufRectSelect(buf, start, end, rectStart, rectEnd); 284 else 285 BufSelect(buf, start, end); 286 } 287 288 if(bak_locale) setlocale(LC_CTYPE, bak_locale); 289 } 290 291 void FillSelection(WindowInfo *window) 292 { 293 textBuffer *buf = window->buffer; 294 char *text, *filledText; 295 int left, right, nCols, len, isRect, rectStart, rectEnd; 296 int rightMargin, wrapMargin; 297 int insertPos = TextGetCursorPos(window->lastFocus); 298 int hasSelection = window->buffer->primary.selected; 299 300 /* Find the range of characters and get the text to fill. If there is a 301 selection, use it but extend non-rectangular selections to encompass 302 whole lines. If there is no selection, find the paragraph containing 303 the insertion cursor */ 304 if (!BufGetSelectionPos(buf, &left, &right, &isRect, &rectStart, &rectEnd)) { 305 left = findParagraphStart(buf, insertPos); 306 right = findParagraphEnd(buf, insertPos); 307 if (left == right) { 308 XBell(TheDisplay, 0); 309 return; 310 } 311 text = BufGetRange(buf, left, right); 312 } else if (isRect) { 313 left = BufStartOfLine(buf, left); 314 right = BufEndOfLine(buf, right); 315 text = BufGetTextInRect(buf, left, right, rectStart, INT_MAX); 316 } else { 317 left = BufStartOfLine(buf, left); 318 if (right != 0 && BufGetCharacter(buf, right-1) != '\n') { 319 right = BufEndOfLine(buf, right); 320 if (right < buf->length) 321 right++; 322 } 323 BufSelect(buf, left, right); 324 text = BufGetRange(buf, left, right); 325 } 326 327 /* Find right margin either as specified in the rectangular selection, or 328 by measuring the text and querying the window's wrap margin (or width) */ 329 if (hasSelection && isRect) { 330 rightMargin = rectEnd - rectStart; 331 } else 332 { 333 XtVaGetValues(window->textArea, 334 textNcolumns, &nCols, 335 textNwrapMargin, &wrapMargin, 336 NULL); 337 rightMargin = (wrapMargin == 0 ? nCols : wrapMargin); 338 } 339 340 /* Fill the text */ 341 filledText = fillParagraphs(text, rightMargin, buf->tabDist, buf->useTabs, 342 buf->nullSubsChar, &len, False); 343 NEditFree(text); 344 345 /* Replace the text in the window */ 346 if (hasSelection && isRect) { 347 BufReplaceRect(buf, left, right, rectStart, INT_MAX, filledText); 348 BufRectSelect(buf, left, 349 BufEndOfLine(buf, BufCountForwardNLines(buf, left, 350 countLines(filledText)-1)), rectStart, rectEnd); 351 } else { 352 BufReplace(buf, left, right, filledText); 353 if (hasSelection) 354 BufSelect(buf, left, left + len); 355 } 356 NEditFree(filledText); 357 358 /* Find a reasonable cursor position. Usually insertPos is best, but 359 if the text was indented, positions can shift */ 360 if (hasSelection && isRect) 361 TextSetCursorPos(window->lastFocus, buf->cursorPosHint); 362 else 363 TextSetCursorPos(window->lastFocus, insertPos < left ? left : 364 (insertPos > left + len ? left + len : insertPos)); 365 } 366 367 /* 368 ** shift lines left and right in a multi-line text string. Returns the 369 ** shifted text in memory that must be freed by the caller with NEditFree. 370 */ 371 char *ShiftText(char *text, int direction, int tabsAllowed, int tabDist, 372 int nChars, int *newLen) 373 { 374 char *shiftedText, *shiftedLine; 375 char *textPtr, *lineStartPtr, *shiftedPtr; 376 int bufLen; 377 378 /* 379 ** Allocate memory for shifted string. Shift left adds a maximum of 380 ** tabDist-2 characters per line (remove one tab, add tabDist-1 spaces). 381 ** Shift right adds a maximum of nChars character per line. 382 */ 383 if (direction == SHIFT_RIGHT) 384 bufLen = strlen(text) + countLines(text) * nChars; 385 else 386 bufLen = strlen(text) + countLines(text) * tabDist; 387 shiftedText = (char*)NEditMalloc(bufLen + 1); 388 389 /* 390 ** break into lines and call shiftLine(Left/Right) on each 391 */ 392 lineStartPtr = text; 393 textPtr = text; 394 shiftedPtr = shiftedText; 395 while (TRUE) { 396 if (*textPtr=='\n' || *textPtr=='\0') { 397 shiftedLine = (direction == SHIFT_RIGHT) ? 398 shiftLineRight(lineStartPtr, textPtr-lineStartPtr, 399 tabsAllowed, tabDist, nChars) : 400 shiftLineLeft(lineStartPtr, textPtr-lineStartPtr, tabDist, 401 nChars); 402 strcpy(shiftedPtr, shiftedLine); 403 shiftedPtr += strlen(shiftedLine); 404 NEditFree(shiftedLine); 405 if (*textPtr == '\0') { 406 /* terminate string & exit loop at end of text */ 407 *shiftedPtr = '\0'; 408 break; 409 } else { 410 /* move the newline from text to shifted text */ 411 *shiftedPtr++ = *textPtr++; 412 } 413 /* start line over */ 414 lineStartPtr = textPtr; 415 } else 416 textPtr++; 417 } 418 *newLen = shiftedPtr - shiftedText; 419 return shiftedText; 420 } 421 422 static char *shiftLineRight(char *line, int lineLen, int tabsAllowed, 423 int tabDist, int nChars) 424 { 425 char *lineOut; 426 char *lineInPtr, *lineOutPtr; 427 int whiteWidth, i; 428 429 lineInPtr = line; 430 lineOut = (char*)NEditMalloc(lineLen + nChars + 1); 431 lineOutPtr = lineOut; 432 whiteWidth = 0; 433 while (TRUE) { 434 if (*lineInPtr == '\0' || (lineInPtr - line) >= lineLen) { 435 /* nothing on line, wipe it out */ 436 *lineOut = '\0'; 437 return lineOut; 438 } else if (*lineInPtr == ' ') { 439 /* white space continues with tab, advance to next tab stop */ 440 whiteWidth++; 441 *lineOutPtr++ = *lineInPtr++; 442 } else if (*lineInPtr == '\t') { 443 /* white space continues with tab, advance to next tab stop */ 444 whiteWidth = nextTab(whiteWidth, tabDist); 445 *lineOutPtr++ = *lineInPtr++; 446 } else { 447 /* end of white space, add nChars of space */ 448 for (i=0; i<nChars; i++) { 449 *lineOutPtr++ = ' '; 450 whiteWidth++; 451 /* if we're now at a tab stop, change last 8 spaces to a tab */ 452 if (tabsAllowed && atTabStop(whiteWidth, tabDist)) { 453 lineOutPtr -= tabDist; 454 *lineOutPtr++ = '\t'; 455 } 456 } 457 /* move remainder of line */ 458 while (*lineInPtr!='\0' && (lineInPtr - line) < lineLen) 459 *lineOutPtr++ = *lineInPtr++; 460 *lineOutPtr = '\0'; 461 return lineOut; 462 } 463 } 464 } 465 466 static char *shiftLineLeft(char *line, int lineLen, int tabDist, int nChars) 467 { 468 char *lineOut; 469 int i, whiteWidth, lastWhiteWidth, whiteGoal; 470 char *lineInPtr, *lineOutPtr; 471 472 lineInPtr = line; 473 lineOut = (char*)NEditMalloc(lineLen + tabDist + 1); 474 lineOutPtr = lineOut; 475 whiteWidth = 0; 476 lastWhiteWidth = 0; 477 while (TRUE) { 478 if (*lineInPtr == '\0' || (lineInPtr - line) >= lineLen) { 479 /* nothing on line, wipe it out */ 480 *lineOut = '\0'; 481 return lineOut; 482 } else if (*lineInPtr == ' ') { 483 /* white space continues with space, advance one character */ 484 whiteWidth++; 485 *lineOutPtr++ = *lineInPtr++; 486 } else if (*lineInPtr == '\t') { 487 /* white space continues with tab, advance to next tab stop */ 488 /* save the position, though, in case we need to remove the tab */ 489 lastWhiteWidth = whiteWidth; 490 whiteWidth = nextTab(whiteWidth, tabDist); 491 *lineOutPtr++ = *lineInPtr++; 492 } else { 493 /* end of white space, remove nChars characters */ 494 for (i=1; i<=nChars; i++) { 495 if (lineOutPtr > lineOut) { 496 if (*(lineOutPtr-1) == ' ') { 497 /* end of white space is a space, just remove it */ 498 lineOutPtr--; 499 } else { 500 /* end of white space is a tab, remove it and add 501 back spaces */ 502 lineOutPtr--; 503 whiteGoal = whiteWidth - i; 504 whiteWidth = lastWhiteWidth; 505 while (whiteWidth < whiteGoal) { 506 *lineOutPtr++ = ' '; 507 whiteWidth++; 508 } 509 } 510 } 511 } 512 /* move remainder of line */ 513 while (*lineInPtr!='\0' && (lineInPtr - line) < lineLen) 514 *lineOutPtr++ = *lineInPtr++; 515 /* add a null */ 516 *lineOutPtr = '\0'; 517 return lineOut; 518 } 519 } 520 } 521 522 static int atTabStop(int pos, int tabDist) 523 { 524 return (pos%tabDist == 0); 525 } 526 527 static int nextTab(int pos, int tabDist) 528 { 529 return (pos/tabDist)*tabDist + tabDist; 530 } 531 532 static int countLines(const char *text) 533 { 534 int count = 1; 535 536 while(*text != '\0') { 537 if (*text++ == '\n') { 538 count++; 539 } 540 } 541 return count; 542 } 543 544 /* 545 ** Find the implied left margin of a text string (the number of columns to the 546 ** first non-whitespace character on any line) up to either the terminating 547 ** null character at the end of the string, or "length" characters, whever 548 ** comes first. 549 */ 550 static int findLeftMargin(char *text, int length, int tabDist) 551 { 552 char *c; 553 int col = 0, leftMargin = INT_MAX; 554 int inMargin = True; 555 556 for (c=text; *c!='\0' && c-text<length; c++) { 557 if (*c == '\t') { 558 col += BufCharWidth('\t', col, tabDist, '\0'); 559 } else if (*c == ' ') { 560 col++; 561 } else if (*c == '\n') { 562 col = 0; 563 inMargin = True; 564 } else { 565 /* non-whitespace */ 566 if (col < leftMargin && inMargin) 567 leftMargin = col; 568 inMargin = False; 569 } 570 } 571 572 /* if no non-white text is found, the leftMargin will never be set */ 573 if (leftMargin == INT_MAX) 574 return 0; 575 576 return leftMargin; 577 } 578 579 /* 580 ** Fill multiple paragraphs between rightMargin and an implied left margin 581 ** and first line indent determined by analyzing the text. alignWithFirst 582 ** aligns subsequent paragraphs with the margins of the first paragraph (a 583 ** capability not currently used in NEdit, but carried over from code for 584 ** previous versions which did all paragraphs together). 585 */ 586 static char *fillParagraphs(char *text, int rightMargin, int tabDist, 587 int useTabs, char nullSubsChar, int *filledLen, int alignWithFirst) 588 { 589 int paraStart, paraEnd, fillEnd; 590 char *c, ch, *secondLineStart, *paraText, *filledText; 591 int firstLineLen, firstLineIndent, leftMargin, len; 592 textBuffer *buf; 593 594 /* Create a buffer to accumulate the filled paragraphs */ 595 buf = BufCreate(); 596 BufSetAll(buf, text); 597 598 /* 599 ** Loop over paragraphs, filling each one, and accumulating the results 600 ** in buf 601 */ 602 paraStart = 0; 603 for (;;) { 604 605 /* Skip over white space */ 606 while (paraStart < buf->length) { 607 ch = BufGetCharacter(buf, paraStart); 608 if (ch != ' ' && ch != '\t' && ch != '\n') 609 break; 610 paraStart++; 611 } 612 if (paraStart >= buf->length) 613 break; 614 paraStart = BufStartOfLine(buf, paraStart); 615 616 /* Find the end of the paragraph */ 617 paraEnd = findParagraphEnd(buf, paraStart); 618 619 /* Operate on either the one paragraph, or to make them all identical, 620 do all of them together (fill paragraph can format all the paragraphs 621 it finds with identical specs if it gets passed more than one) */ 622 fillEnd = alignWithFirst ? buf->length : paraEnd; 623 624 /* Get the paragraph in a text string (or all of the paragraphs if 625 we're making them all the same) */ 626 paraText = BufGetRange(buf, paraStart, fillEnd); 627 628 /* Find separate left margins for the first and for the first line of 629 the paragraph, and for rest of the remainder of the paragraph */ 630 for (c=paraText ; *c!='\0' && *c!='\n'; c++); 631 firstLineLen = c - paraText; 632 secondLineStart = *c == '\0' ? paraText : c + 1; 633 firstLineIndent = findLeftMargin(paraText, firstLineLen, tabDist); 634 leftMargin = findLeftMargin(secondLineStart, paraEnd - paraStart - 635 (secondLineStart - paraText), tabDist); 636 637 /* Fill the paragraph */ 638 filledText = fillParagraph(paraText, leftMargin, firstLineIndent, 639 rightMargin, tabDist, useTabs, nullSubsChar, &len); 640 NEditFree(paraText); 641 642 /* Replace it in the buffer */ 643 BufReplace(buf, paraStart, fillEnd, filledText); 644 NEditFree(filledText); 645 646 /* move on to the next paragraph */ 647 paraStart += len; 648 } 649 650 /* Free the buffer and return its contents */ 651 filledText = BufGetAll(buf); 652 *filledLen = buf->length; 653 BufFree(buf); 654 return filledText; 655 } 656 657 /* 658 ** Trim leading space, and arrange text to fill between leftMargin and 659 ** rightMargin (except for the first line which fills from firstLineIndent), 660 ** re-creating whitespace to the left of the text using tabs (if allowTabs is 661 ** True) calculated using tabDist, and spaces. Returns a newly allocated 662 ** string as the function result, and the length of the new string in filledLen. 663 */ 664 static char *fillParagraph(char *text, int leftMargin, int firstLineIndent, 665 int rightMargin, int tabDist, int allowTabs, char nullSubsChar, 666 int *filledLen) 667 { 668 char *cleanedText, *outText, *indentString, *leadIndentStr, *outPtr, *c, *b; 669 int col, cleanedLen, indentLen, leadIndentLen, nLines = 1; 670 int inWhitespace, inMargin; 671 672 /* remove leading spaces, convert newlines to spaces */ 673 cleanedText = (char*)NEditMalloc(strlen(text)+1); 674 outPtr = cleanedText; 675 inMargin = True; 676 for (c=text; *c!='\0'; c++) { 677 if (*c == '\t' || *c == ' ') { 678 if (!inMargin) 679 *outPtr++ = *c; 680 } else if (*c == '\n') { 681 if (inMargin) { 682 /* a newline before any text separates paragraphs, so leave 683 it in, back up, and convert the previous space back to \n */ 684 if (outPtr > cleanedText && *(outPtr-1) == ' ') 685 *(outPtr-1) = '\n'; 686 *outPtr++ = '\n'; 687 nLines +=2; 688 } else 689 *outPtr++ = ' '; 690 inMargin = True; 691 } else { 692 *outPtr++ = *c; 693 inMargin = False; 694 } 695 } 696 cleanedLen = outPtr - cleanedText; 697 *outPtr = '\0'; 698 699 /* Put back newlines breaking text at word boundaries within the margins. 700 Algorithm: scan through characters, counting columns, and when the 701 margin width is exceeded, search backward for beginning of the word 702 and convert the last whitespace character into a newline */ 703 col = firstLineIndent; 704 int inc = 1; 705 for (c=cleanedText; *c!='\0'; c+=inc) { 706 if (*c == '\n') { 707 col = leftMargin; 708 inc = 1; 709 } else { 710 col += BufCharWidth(*c, col, tabDist, nullSubsChar); 711 inc = Utf8CharLen((unsigned char*)c); 712 } 713 if (col-1 > rightMargin) { 714 inWhitespace = True; 715 for (b=c; b>=cleanedText && *b!='\n'; b--) { 716 if (*b == '\t' || *b == ' ') { 717 if (!inWhitespace) { 718 *b = '\n'; 719 c = b; 720 col = leftMargin; 721 nLines++; 722 break; 723 } 724 } else 725 inWhitespace = False; 726 } 727 } 728 } 729 nLines++; 730 731 /* produce a string to prepend to lines to indent them to the left margin */ 732 leadIndentStr = makeIndentString(firstLineIndent, tabDist, 733 allowTabs, &leadIndentLen); 734 indentString = makeIndentString(leftMargin, tabDist, allowTabs, &indentLen); 735 736 /* allocate memory for the finished string */ 737 outText = (char*)NEditMalloc(sizeof(char) * (cleanedLen + leadIndentLen + 738 indentLen * (nLines-1) + 1)); 739 outPtr = outText; 740 741 /* prepend the indent string to each line of the filled text */ 742 strncpy(outPtr, leadIndentStr, leadIndentLen); 743 outPtr += leadIndentLen; 744 for (c=cleanedText; *c!='\0'; c++) { 745 *outPtr++ = *c; 746 if (*c == '\n') { 747 strncpy(outPtr, indentString, indentLen); 748 outPtr += indentLen; 749 } 750 } 751 752 /* convert any trailing space to newline. Add terminating null */ 753 if (*(outPtr-1) == ' ') 754 *(outPtr-1) = '\n'; 755 *outPtr = '\0'; 756 757 /* clean up, return result */ 758 NEditFree(cleanedText); 759 NEditFree(leadIndentStr); 760 NEditFree(indentString); 761 *filledLen = outPtr - outText; 762 return outText; 763 } 764 765 static char *makeIndentString(int indent, int tabDist, int allowTabs, int *nChars) 766 { 767 char *indentString, *outPtr; 768 int i; 769 770 outPtr = indentString = (char*)NEditMalloc(sizeof(char) * indent + 1); 771 if (allowTabs) { 772 for (i=0; i<indent/tabDist; i++) 773 *outPtr++ = '\t'; 774 for (i=0; i<indent%tabDist; i++) 775 *outPtr++ = ' '; 776 } else { 777 for (i=0; i<indent; i++) 778 *outPtr++ = ' '; 779 } 780 *outPtr = '\0'; 781 *nChars = outPtr - indentString; 782 return indentString; 783 } 784 785 /* 786 ** Find the boundaries of the paragraph containing pos 787 */ 788 static int findParagraphEnd(textBuffer *buf, int startPos) 789 { 790 char c; 791 int pos; 792 static char whiteChars[] = " \t"; 793 794 pos = BufEndOfLine(buf, startPos)+1; 795 while (pos < buf->length) { 796 c = BufGetCharacter(buf, pos); 797 if (c == '\n') 798 break; 799 if (strchr(whiteChars, c) != NULL) 800 pos++; 801 else 802 pos = BufEndOfLine(buf, pos)+1; 803 } 804 return pos < buf->length ? pos : buf->length; 805 } 806 static int findParagraphStart(textBuffer *buf, int startPos) 807 { 808 char c; 809 int pos, parStart; 810 static char whiteChars[] = " \t"; 811 812 if (startPos == 0) 813 return 0; 814 parStart = BufStartOfLine(buf, startPos); 815 pos = parStart - 2; 816 while (pos > 0) { 817 c = BufGetCharacter(buf, pos); 818 if (c == '\n') 819 break; 820 if (strchr(whiteChars, c) != NULL) 821 pos--; 822 else { 823 parStart = BufStartOfLine(buf, pos); 824 pos = parStart - 2; 825 } 826 } 827 return parStart > 0 ? parStart : 0; 828 } 829