Commit | Line | Data |
---|---|---|
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 | ||
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 | } |