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