1: /************************************************************************* 2: * This program is copyright (C) 1985, 1986 by Jonathan Payne. It is * 3: * provided to you without charge for use only on a licensed Unix * 4: * system. You may copy JOVE provided that this notice is included with * 5: * the copy. You may not sell copies of this program or versions * 6: * modified for use on microcomputer systems, unless the copies are * 7: * included with a Unix system distribution and the source is provided. * 8: *************************************************************************/ 9: 10: #include "jove.h" 11: 12: /* Thanks to Brian Harvey for this paragraph boundery finding algorithm. 13: It's really quite hairy figuring it out. This deals with paragraphs that 14: are seperated by blank lines, lines beginning with a Period (assumed to 15: be an nroff command), lines beginning with BackSlash (assumed to be Tex 16: commands). Also handles paragraphs that are separated by lines of 17: different indent; and it deals with outdented paragraphs, too. It's 18: really quite nice. Here's Brian's algorithm. 19: 20: Definitions: 21: 22: THIS means the line containing the cursor. 23: PREV means the line above THIS. 24: NEXT means the line below THIS. 25: 26: BLANK means empty, empty except for spaces and tabs, starts with a period 27: or a backslash, or nonexistent (because the edge of the buffer is 28: reached). ((BH 12/24/85 A line starting with backslash is blank only if 29: the following line also starts with backslash. This is so that \noindent 30: is part of a paragraph, but long strings of TeX commands don't get 31: rearranged. It still isn't perfect but it's better.)) 32: 33: BSBLANK means BLANK or starts with a backslash. (BH 12/24/85) 34: 35: HEAD means the first (nonblank) line of the paragraph containing THIS. 36: BODY means all other (nonblank) lines of the paragraph. 37: TAIL means the last (nb) line of the paragraph. (TAIL is part of BODY.) 38: 39: HEAD INDENT means the indentation of HEAD. M-J should preserve this. 40: BODY INDENT means the indentation of BODY. Ditto. 41: 42: Subprocedures: 43: 44: TAILRULE(BODYLINE) 45: If BODYLINE is BLANK, the paragraph has only one line, and there is no 46: BODY and therefore no TAIL. Return. Otherwise, starting from BODYLINE, 47: move down until you find a line that either is BSBLANK or has a different 48: indentation from BODYLINE. The line above that different line is TAIL. 49: Return. 50: 51: Rules: 52: 53: 1. If THIS is BLANK, which command are you doing? If M-J or M-[, then go 54: up to the first non-BLANK line and start over. (If there is no non-BLANK 55: line before THIS, ring the bell.) If M-], then the first non-BLANK line 56: below THIS is HEAD, and the second consecutive non-BSBLANK line (if any) is 57: the beginning of BODY. (If there is no non-BLANK line after THIS, ring 58: the bell.) Do TAILRULE(beginning-of-BODY). Go to rule A. 59: 60: 2. If PREV is BLANK or THIS is BSBLANK, then THIS is HEAD, and NEXT (if 61: not BSBLANK) is in BODY. Do TAILRULE(NEXT). Go to rule A. 62: 63: 3. If NEXT is BSBLANK, then THIS is TAIL, therefore part of BODY. Go to 64: rule 5 to find HEAD. 65: 66: 4. If either NEXT or PREV has the same indentation as THIS, then THIS is 67: part of BODY. Do TAILRULE(THIS). Go to rule 5 to find HEAD. Otherwise, 68: go to rule 6. 69: 70: 5. Go up until you find a line that is either BSBLANK or has a different 71: indentation from THIS. If that line is BLANK, the line below it is HEAD. 72: If that line is non-BLANK, then call that new line THIS for what follows. 73: If (the new) PREV has the same indent as THIS, then (the new) NEXT is 74: HEAD. If PREV has a different indent from THIS, then THIS is HEAD. Go to 75: rule A. 76: 77: 6. If you got here, then both NEXT and PREV are nonblank and are 78: differently indented from THIS. This is a tricky case and there is no 79: guarantee that you're going to win. The most straightforward thing to do 80: is assume that we are not using hanging indentation. In that case: 81: whichever of PREV and THIS is indented further is HEAD. Do 82: TAILRULE(HEAD+1). Go to rule A. 83: 84: 6+. A more complicated variant would be this: if THIS is indented further 85: than PREV, we are using regular indentation and rule 6 applies. If PREV 86: is indented further than THIS, look at both NEXT and the line after NEXT. 87: If those two lines are indented equally, and more than THIS, then we are 88: using hanging indent, THIS is HEAD, and NEXT is the first line of BODY. 89: Do TAILRULE(NEXT). Otherwise, rule 6 applies. 90: 91: A. You now know where HEAD and TAIL are. The indentation of HEAD is HEAD 92: INDENT; the indentation of TAIL is BODY INDENT. 93: 94: B. If you are trying to M-J, you are now ready to do it. 95: 96: C. If you are trying to M-], leave point after the newline that ends 97: TAIL. In other words, leave the cursor at the beginning of the line 98: after TAIL. It is not possible for this to leave point where it started 99: unless it was already at the end of the buffer. 100: 101: D. If you are trying to M-[, if the line before HEAD is not BLANK, then 102: leave point just before HEAD. That is, leave the cursor at the beginning 103: of HEAD. If the line before HEAD is BLANK, then leave the cursor at the 104: beginning of that line. If the cursor didn't move, go up to the first 105: earlier non-BLANK line and start over. 106: 107: 108: End of Algorithm. I implemented rule 6+ because it seemed nicer. */ 109: 110: int RMargin = 78, 111: LMargin = 0; 112: Line *para_head, 113: *para_tail; 114: int head_indent, 115: body_indent; 116: static int use_lmargin; 117: 118: /* some defines for paragraph boundery checking */ 119: #define I_EMPTY -1 /* line "looks" empty (spaces and tabs) */ 120: #define I_PERIOD -2 /* line begins with "." or "\" */ 121: #define I_BUFEDGE -3 /* line is nonexistent (edge of buffer) */ 122: 123: static int bslash; /* Nonzero if get_indent finds line starting 124: with backslash */ 125: 126: i_bsblank(lp) 127: Line *lp; 128: { 129: if (i_blank(lp)) 130: return 1; 131: return bslash; 132: } 133: 134: i_blank(lp) 135: Line *lp; 136: { 137: return (get_indent(lp) < 0); 138: } 139: 140: static 141: get_indent(lp) 142: register Line *lp; 143: { 144: Bufpos save; 145: register int indent; 146: 147: bslash = 0; 148: if (lp == 0) 149: return I_BUFEDGE; 150: DOTsave(&save); 151: SetLine(lp); 152: if (blnkp(linebuf)) 153: indent = I_EMPTY; 154: else if (linebuf[0] == '.') 155: indent = I_PERIOD; 156: else if (linebuf[0] == '\\') { 157: /* BH 12/24/85. Backslash is BLANK only if next line 158: also starts with Backslash. */ 159: bslash++; 160: SetLine(lp->l_next); 161: if (linebuf[0] == '\\') 162: indent = I_PERIOD; 163: else 164: indent = 0; 165: } else { 166: ToIndent(); 167: indent = calc_pos(linebuf, curchar); 168: } 169: SetDot(&save); 170: 171: return indent; 172: } 173: 174: static Line * 175: tailrule(lp) 176: register Line *lp; 177: { 178: int i; 179: 180: i = get_indent(lp); 181: if (i < 0) 182: return lp; /* one line paragraph */ 183: do { 184: if ((get_indent(lp->l_next) != i) || bslash) 185: /* BH line with backslash is head of next para */ 186: break; 187: } while ((lp = lp->l_next) != 0); 188: if (lp == 0) 189: complain((char *) 0); 190: return lp; 191: } 192: 193: /* Finds the beginning, end and indent of the current paragraph, and sets 194: the above global variables. HOW says how to behave when we're between 195: paragraphs. That is, it's either FORWARD or BACKWARD depending on which 196: way we're favoring. */ 197: 198: find_para(how) 199: { 200: Line *this, 201: *prev, 202: *next, 203: *head = 0, 204: *body = 0, 205: *tail = 0; 206: int this_indent; 207: Bufpos orig; /* remember where we were when we started */ 208: 209: exp = 1; 210: DOTsave(&orig); 211: strt: 212: this = curline; 213: prev = curline->l_prev; 214: next = curline->l_next; 215: this_indent = get_indent(this); 216: 217: if (i_blank(this)) { /* rule 1 */ 218: if (how == BACKWARD) { 219: while (i_blank(curline)) 220: if (firstp(curline)) 221: complain((char *) 0); 222: else 223: line_move(BACKWARD, NO); 224: goto strt; 225: } else { 226: while (i_blank(curline)) 227: if (lastp(curline)) 228: complain((char *) 0); 229: else 230: line_move(FORWARD, NO); 231: head = curline; 232: next = curline->l_next; 233: if (!i_bsblank(next)) 234: body = next; 235: else 236: body = head; 237: } 238: } else if (i_bsblank(this) || i_blank(prev)) { /* rule 2 */ 239: head = this; 240: if (!i_bsblank(next)) 241: body = next; 242: } else if (i_bsblank(next)) { /* rule 3 */ 243: tail = this; 244: body = this; 245: } else if ((get_indent(next) == this_indent) || /* rule 4 */ 246: (get_indent(prev) == this_indent)) 247: body = this; 248: else { /* rule 6+ */ 249: if (get_indent(prev) > this_indent) { 250: /* hanging indent maybe? */ 251: if ((next != 0) && 252: (get_indent(next) == get_indent(next->l_next))) { 253: head = this; 254: body = next; 255: } 256: } 257: /* Now we handle hanging indent else and the other 258: case of this_indent > get_indent(prev). That is, 259: if we didn't resolve HEAD in the above if, then 260: we are not a hanging indent. */ 261: if (head == 0) { /* still don't know */ 262: if (this_indent > get_indent(prev)) 263: head = this; 264: else 265: head = prev; 266: body = head->l_next; 267: } 268: } 269: /* rule 5 -- find the missing parts */ 270: if (head == 0) { /* haven't found head of paragraph so do so now */ 271: Line *lp; 272: int i; 273: 274: lp = this; 275: do { 276: i = get_indent(lp->l_prev); 277: if (i < 0) /* is blank */ 278: head = lp; 279: else if (i != this_indent || bslash) { 280: Line *this = lp->l_prev; 281: 282: if (get_indent(this->l_prev) == i) 283: head = this->l_next; 284: else 285: head = this; 286: } 287: } while (head == 0 && (lp = lp->l_prev) != 0); 288: if (lp == 0) 289: complain((char *) 0); 290: } 291: if (body == 0) /* this must be a one line paragraph */ 292: body = head; 293: if (tail == 0) 294: tail = tailrule(body); 295: if (tail == 0 || head == 0 || body == 0) 296: complain("BUG! tail(%d),head(%d),body(%d)!", tail, head, body); 297: para_head = head; 298: para_tail = tail; 299: head_indent = get_indent(head); 300: body_indent = get_indent(body); 301: 302: SetDot(&orig); 303: } 304: 305: Justify() 306: { 307: use_lmargin = (exp_p != 0); 308: find_para(BACKWARD); 309: DoJustify(para_head, 0, para_tail, length(para_tail), NO, 310: use_lmargin ? LMargin : body_indent); 311: } 312: 313: Line * 314: max_line(l1, l2) 315: Line *l1, 316: *l2; 317: { 318: if (inorder(l1, 0, l2, 0)) 319: return l2; 320: return l1; 321: } 322: 323: Line * 324: min_line(l1, l2) 325: Line *l1, 326: *l2; 327: { 328: if (inorder(l1, 0, l2, 0)) 329: return l1; 330: return l2; 331: } 332: 333: RegJustify() 334: { 335: Mark *mp = CurMark(), 336: *tailmark; 337: Line *l1 = curline, 338: *l2 = mp->m_line; 339: int c1 = curchar, 340: c2 = mp->m_char; 341: Line *rl1, 342: *rl2; 343: 344: use_lmargin = (exp_p != 0); 345: (void) fixorder(&l1, &c1, &l2, &c2); 346: do { 347: DotTo(l1, c1); 348: find_para(FORWARD); 349: rl1 = max_line(l1, para_head); 350: rl2 = min_line(l2, para_tail); 351: tailmark = MakeMark(para_tail, 0, FLOATER); 352: DoJustify(rl1, (rl1 == l1) ? c1 : 0, rl2, 353: (rl2 == l2) ? c2 : length(rl2), 354: NO, use_lmargin ? LMargin : body_indent); 355: l1 = tailmark->m_line->l_next; 356: DelMark(tailmark); 357: c1 = 0; 358: } while (l1 != 0 && l2 != rl2); 359: } 360: 361: do_rfill() 362: { 363: Mark *mp = CurMark(); 364: Line *l1 = curline, 365: *l2 = mp->m_line; 366: int c1 = curchar, 367: c2 = mp->m_char; 368: 369: use_lmargin = (exp_p != 0); 370: (void) fixorder(&l1, &c1, &l2, &c2); 371: DoJustify(l1, c1, l2, c2, NO, use_lmargin ? LMargin : 0); 372: } 373: 374: do_space() 375: { 376: int c1 = curchar, 377: c2 = c1, 378: diff, 379: nspace; 380: char ch; 381: 382: while (c1 > 0 && ((ch = linebuf[c1 - 1]) == ' ' || ch == '\t')) 383: c1--; 384: while ((ch = linebuf[c2]) == ' ' || ch == '\t') 385: c2++; 386: diff = (c2 - c1); 387: curchar = c2; 388: 389: if (diff == 0) 390: return; 391: if (c1 > 0) { 392: int topunct = c1 - 1; 393: 394: nspace = 1; 395: if (diff >= 2) { 396: while (index("\")]", linebuf[topunct])) { 397: if (topunct == 0) 398: break; 399: topunct--; 400: } 401: if (index("?!.:", linebuf[topunct])) 402: nspace = 2; 403: } 404: } else 405: nspace = 0; 406: 407: if (diff > nspace) 408: DoTimes(DelPChar(), (diff - nspace)); 409: else if (diff < nspace) 410: DoTimes(Insert(' '), (nspace - diff)); 411: } 412: 413: DoJustify(l1, c1, l2, c2, scrunch, indent) 414: Line *l1, 415: *l2; 416: { 417: int okay_char = -1; 418: char *cp; 419: Mark *savedot = MakeMark(curline, curchar, FLOATER), 420: *endmark; 421: 422: exp = 1; 423: (void) fixorder(&l1, &c1, &l2, &c2); /* l1/c1 will be before l2/c2 */ 424: DotTo(l1, c1); 425: if (get_indent(l1) >= c1) { 426: if (use_lmargin) { 427: DelWtSpace(); 428: n_indent(indent + (head_indent - body_indent)); 429: use_lmargin = 0; /* turn this off now */ 430: } 431: ToIndent(); 432: } 433: endmark = MakeMark(l2, c2, FLOATER); 434: 435: for (;;) { 436: while (calc_pos(linebuf, curchar) < RMargin) { 437: if (curline == endmark->m_line && curchar >= endmark->m_char) 438: goto outahere; 439: okay_char = curchar; 440: if (eolp()) { 441: DelNChar(); /* Delete line separator. */ 442: ins_str(" ", NO); 443: } else { 444: cp = StrIndex(1, linebuf, curchar + 1, ' '); 445: if (cp == 0) 446: Eol(); 447: else 448: curchar = (cp - linebuf); 449: } 450: do_space(); 451: } 452: if (okay_char > 0) 453: curchar = okay_char; 454: if (curline == endmark->m_line && curchar >= endmark->m_char) 455: goto outahere; 456: 457: /* Can't fit in small margin, so we do the best we can. */ 458: if (eolp()) { 459: line_move(FORWARD, NO); 460: DelWtSpace(); 461: n_indent(indent); 462: } else { 463: DelWtSpace(); 464: LineInsert(); 465: if (scrunch && TwoBlank()) { 466: Eol(); 467: DelNChar(); 468: } 469: n_indent(indent); 470: } 471: } 472: outahere: 473: ToMark(savedot); /* Back to where we were */ 474: DelMark(endmark); /* Free up marks */ 475: DelMark(savedot); 476: this_cmd = last_cmd = 0; /* So everything is under control */ 477: f_mess(""); 478: } 479: 480: extern Line *para_head, 481: *para_tail; 482: 483: DoPara(dir) 484: { 485: register int num = exp, 486: first_time = TRUE; 487: 488: while (--num >= 0) { 489: tryagain: find_para(dir); /* find paragraph bounderies */ 490: if ((dir == BACKWARD) && 491: ((!first_time) || ((para_head == curline) && bolp()))) { 492: if (bobp()) 493: complain((char *) 0); 494: BackChar(); 495: first_time = !first_time; 496: goto tryagain; 497: } 498: SetLine((dir == BACKWARD) ? para_head : para_tail); 499: if (dir == BACKWARD && !firstp(curline) && 500: i_blank(curline->l_prev)) 501: line_move(BACKWARD, NO); 502: else if (dir == FORWARD) { 503: if (lastp(curline)) { 504: Eol(); 505: break; 506: } 507: /* otherwise */ 508: line_move(FORWARD, NO); 509: } 510: } 511: } 512: 513: BackPara() 514: { 515: DoPara(BACKWARD); 516: } 517: 518: ForPara() 519: { 520: DoPara(FORWARD); 521: }