BSD 4_3 development
[unix-history] / usr / contrib / jove / paragraph.c
CommitLineData
fb51ca13
C
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
110int RMargin = 78,
111 LMargin = 0;
112Line *para_head,
113 *para_tail;
114int head_indent,
115 body_indent;
116static 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
123static int bslash; /* Nonzero if get_indent finds line starting
124 with backslash */
125
126i_bsblank(lp)
127Line *lp;
128{
129 if (i_blank(lp))
130 return 1;
131 return bslash;
132}
133
134i_blank(lp)
135Line *lp;
136{
137 return (get_indent(lp) < 0);
138}
139
140static
141get_indent(lp)
142register 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
174static Line *
175tailrule(lp)
176register 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
198find_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);
211strt:
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
305Justify()
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
313Line *
314max_line(l1, l2)
315Line *l1,
316 *l2;
317{
318 if (inorder(l1, 0, l2, 0))
319 return l2;
320 return l1;
321}
322
323Line *
324min_line(l1, l2)
325Line *l1,
326 *l2;
327{
328 if (inorder(l1, 0, l2, 0))
329 return l1;
330 return l2;
331}
332
333RegJustify()
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
361do_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
374do_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
413DoJustify(l1, c1, l2, c2, scrunch, indent)
414Line *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 }
472outahere:
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
480extern Line *para_head,
481 *para_tail;
482
483DoPara(dir)
484{
485 register int num = exp,
486 first_time = TRUE;
487
488 while (--num >= 0) {
489tryagain: 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
513BackPara()
514{
515 DoPara(BACKWARD);
516}
517
518ForPara()
519{
520 DoPara(FORWARD);
521}