1 /*
2 * User text formatting functions
3 * Copyright
4 * (C) 1992 Joseph H. Allen
5 *
6 * This file is part of JOE (Joe's Own Editor)
7 */
8 #include "types.h"
9
10 /* Center line cursor is on and move cursor to beginning of next line */
11
ucenter(W * w,int k)12 int ucenter(W *w, int k)
13 {
14 BW *bw;
15 P *p, *q;
16 off_t endcol, begcol, x;
17 int c;
18
19 WIND_BW(bw, w);
20 p = bw->cursor;
21
22 p_goto_eol(p);
23 while (joe_isblank(bw->b->o.charmap, (c = prgetc(p))))
24 /* do nothing */;
25 if (c == '\n') {
26 pgetc(p);
27 goto done;
28 }
29 if (c == NO_MORE_DATA)
30 goto done;
31 pgetc(p);
32 endcol = piscol(p);
33
34 p_goto_bol(p);
35 while (joe_isblank(bw->b->o.charmap, (c = pgetc(p))))
36 /* do nothing */;
37 if (c == '\n') {
38 prgetc(p);
39 goto done;
40 }
41 if (c == NO_MORE_DATA)
42 goto done;
43 prgetc(p);
44 begcol = piscol(p);
45
46 if (endcol - begcol > bw->o.rmargin + bw->o.lmargin)
47 goto done;
48
49 q = pdup(p, "ucenter");
50 p_goto_bol(q);
51 bdel(q, p);
52 prm(q);
53
54 for (x = 0; x != (bw->o.lmargin + bw->o.rmargin) / 2 - (endcol - begcol) / 2; ++x)
55 binsc(p, ' ');
56
57 done:
58 if (!pnextl(p)) {
59 binsc(p, '\n');
60 pgetc(p);
61 return -1;
62 } else
63 return 0;
64 }
65
66 /* Return true if c is a character which can indent a paragraph */
67
68 /* > is for mail/news
69 * * is for C comments
70 * / is for C++ comments
71 * # is for shell script comments
72 * % is for TeX comments
73 */
74
cpara(BW * bw,int c)75 static int cpara(BW *bw, int c)
76 {
77 if (c == ' ' || c == '\t')
78 return 1;
79 if (bw->o.cpara) {
80 const char *s = bw->o.cpara;
81 while (*s) {
82 if (utf8_decode_fwrd(&s, NULL) == c)
83 return 1;
84 }
85 }
86 return 0;
87 #ifdef junk
88 if (c == ' ' || c == '\t' || c == '\\' ||
89 c == '>' || c == '|' || c == ':' || c == '*' || c == '/' ||
90 c == ',' || c == '.' || c == '?' || c == ';' || c == ']' ||
91 c == '}' || c == '=' || c == '+' || c == '-' || c == '_' ||
92 c == ')' || c == '&' || c == '^' || c == '%' || c == '$' ||
93 c == '#' || c == '@' || c == '!' || c == '~')
94 return 1;
95 else
96 return 0;
97 #endif
98 }
99
100 /* Return true if this first non-whitespace character means
101 we are not a paragraph for sure (for example, '.' in nroff) */
102
cnotpara(BW * bw,int c)103 static int cnotpara(BW *bw, int c)
104 {
105 const char *s;
106 if (bw->o.cnotpara) {
107 s = bw->o.cnotpara;
108 while (*s) {
109 if (c == utf8_decode_fwrd(&s, NULL))
110 return 1;
111 }
112 }
113 return 0;
114 }
115
116 /* Return true if line is definitely not a paragraph line.
117 * Lines which aren't paragraph lines:
118 * 1) Blank lines
119 * 2) Lines which begin with '.'
120 */
121
pisnpara(BW * bw,P * p)122 static int pisnpara(BW *bw, P *p)
123 {
124 P *q;
125 int c;
126
127 q = pdup(p, "pisnpara");
128 p_goto_bol(q);
129 while (cpara(bw, c = pgetc(q)))
130 /* do nothing */;
131 prm(q);
132 if (cnotpara(bw, c) || c == '\r' || c == '\n')
133 return 1;
134 else
135 return 0;
136 }
137
138 /* Determine amount of indentation on current line. Set first
139 to include '-' and '*' bullets. */
140
nindent(BW * bw,P * p,int first)141 static off_t nindent(BW *bw, P *p, int first)
142 {
143 P *q = pdup(p, "nindent");
144 off_t col;
145 int c;
146
147 p_goto_bol(q);
148 do {
149 col = q->col;
150 } while (cpara(bw, (c = pgetc(q))));
151 if (first && (c == '-' || c == '*')) {
152 c = pgetc(q);
153 if (c == ' ') {
154 col = q->col;
155 }
156 }
157 prm(q);
158 return col;
159 }
160
161 /* Get indentation prefix column */
162
prefix(BW * bw,P * p,int up)163 static off_t prefix(BW *bw, P *p,int up)
164 {
165 off_t len;
166 P *q = pdup(p, "prefix");
167
168 p_goto_bol(q);
169 while (cpara(bw, brch(q)))
170 pgetc(q);
171 while (!pisbol(q)) {
172 /* int c; */
173 if (!joe_isblank(p->b->o.charmap, ( /* c = */ prgetc(q)))) {
174 /*
175 if (up && (c == '*' || c == '-')) {
176 if (!pisbol(q)) {
177 c = prgetc(q);
178 pgetc(q);
179 if (c == ' ' || c == '\t')
180 goto skip;
181 } else
182 goto skip;
183 }
184 pgetc(q);
185 */
186 break;
187 /* skip:; */
188 }
189 }
190 len = piscol(q);
191 prm(q);
192 return len;
193 }
194
195 /* Move pointer to beginning of paragraph
196 *
197 * This function simply moves backwards until it sees:
198 * 0) The beginning of the file
199 * 1) A blank line
200 * 2) A line with a different indentation prefix
201 * 3) A line with indentation greater than that of the line we started with
202 * 4) A line with indentation less than that of the starting line, but with
203 * a blank line (or beginning of file) preceding it.
204 */
205
206 int within = 0;
207
pbop(BW * bw,P * p)208 P *pbop(BW *bw, P *p)
209 {
210 off_t indent;
211 off_t prelen;
212 P *last;
213
214 p_goto_bol(p);
215 indent = nindent(bw, p, 0);
216 prelen = prefix(bw, p, 0);
217 last = pdup(p, "pbop");
218 while (!pisbof(p) && (!within || !markb || p->byte > markb->byte)) {
219 off_t ind;
220 off_t len;
221
222 pprevl(p);
223 p_goto_bol(p);
224 ind = nindent(bw, p, 0);
225 len = prefix(bw, p, 0);
226 if (pisnpara(bw, p) || len != prelen) {
227 pset(p, last);
228 break;
229 }
230 if (ind > indent) {
231 /*
232 int ok = 1;
233 P *q = pdup(p, "pbop");
234 if (pprevl(q)) {
235 p_goto_bol(q);
236 if (nindent(bw, q, 0) == ind)
237 ok = 0;
238 }
239 prm(q);
240 if (!ok)
241 pnextl(p);
242 */
243 break;
244 }
245 if (ind < indent) {
246 pset(p, last);
247 break;
248 /* if (pisbof(p)) {
249 break;
250 }
251 pprevl(p);
252 p_goto_bol(p);
253 if (pisnpara(bw, p)) {
254 pnextl(p);
255 break;
256 } else {
257 pnextl(p);
258 pnextl(p);
259 break;
260 } */
261 }
262 pset(last, p);
263 }
264 prm(last);
265 return p;
266 }
267
268 /* Move pointer to end of paragraph. Pointer must already be on first
269 * line of paragraph for this to work correctly.
270 *
271 * This function moves forwards until it sees:
272 * 0) The end of the file.
273 * 1) A blank line
274 * 2) A line with indentation different from the second line of the paragraph
275 * 3) A line with prefix column different from first line
276 */
277
peop(BW * bw,P * p)278 P *peop(BW *bw, P *p)
279 {
280 off_t indent;
281 off_t prelen;
282
283 if (!pnextl(p) || pisnpara(bw, p) || (within && markk && p->byte >= markk->byte))
284 return p;
285 indent = nindent(bw, p, 0);
286 prelen = prefix(bw, p, 0);
287 while (pnextl(p) && (!within || !markk || p->byte < markk->byte)) {
288 off_t ind = nindent(bw, p, 0);
289 off_t len = prefix(bw, p, 0);
290
291 if (ind != indent || len != prelen || pisnpara(bw, p))
292 break;
293 }
294 return p;
295 }
296
297 /* Motion commands */
298
ubop(W * w,int k)299 int ubop(W *w, int k)
300 {
301 BW *bw;
302 P *q;
303 WIND_BW(bw, w);
304 q = pdup(bw->cursor, "ubop");
305
306 up:
307 while (pisnpara(bw, q) && !pisbof(q) && (!within || !markb || q->byte > markb->byte))
308 pprevl(q);
309 pbop(bw, q);
310 if (q->byte != bw->cursor->byte) {
311 pset(bw->cursor, q);
312 prm(q);
313 return 0;
314 } else if (!pisbof(q)) {
315 prgetc(q);
316 goto up;
317 } else {
318 prm(q);
319 return -1;
320 }
321 }
322
ueop(W * w,int k)323 int ueop(W *w, int k)
324 {
325 P *q;
326 BW *bw;
327 WIND_BW(bw, w);
328 q = pdup(bw->cursor, "ueop");
329
330 up:
331 while (pisnpara(bw, q) && !piseof(q))
332 pnextl(q);
333 pbop(bw, q);
334 peop(bw, q);
335 if (q->byte != bw->cursor->byte) {
336 pset(bw->cursor, q);
337 prm(q);
338 return 0;
339 } else if (!piseof(q)) {
340 pnextl(q);
341 goto up;
342 } else {
343 prm(q);
344 return -1;
345 }
346 }
347
348 /* Wrap word. If 'french' is set, only one space will be placed
349 * after . ? or !
350 */
351
wrapword(BW * bw,P * p,off_t indent,int french,int no_over,char * indents)352 void wrapword(BW *bw, P *p, off_t indent, int french, int no_over, char *indents)
353 {
354 P *q;
355 P *r;
356 P *s;
357 int rmf = 0;
358 int c;
359 off_t to = p->byte;
360 int my_indents = 0;
361
362 /* autoindent when called by utype */
363 if (!indents) {
364 /* Get indentation prefix from beginning of line */
365 s = pdup(p, "wrapword");
366 p_goto_bol(s);
367 pbop(bw, s);
368 /* Record indentation of second line of paragraph, of first
369 * line if there is only one line */
370 q = pdup(s, "wrapword");
371 pnextl(q);
372 if (q->line < p->line) {
373 /* Second line */
374 P *tr = pdup(q, "wrapword");
375
376 indent = nindent(bw, q, 0);
377 pcol(tr, indent);
378 indents = brs(q, tr->byte - q->byte); /* Risky */
379 prm(tr);
380 } else {
381 /* First line */
382 P *tr = pdup(s, "uformat");
383 ptrdiff_t x, y;
384
385 indent = nindent(bw, s, 1);
386 pcol(tr, indent);
387 indents = brs(s, tr->byte - s->byte); /* Risky */
388 prm(tr);
389 if (!bw->o.autoindent) {
390 /* Don't indent second line of single-line paragraphs if autoindent is off */
391 ptrdiff_t tx = zlen(indents);
392 ptrdiff_t orgx = tx;
393 while (tx && (indents[tx - 1] == ' ' || indents[tx - 1] == '\t'))
394 indents[--tx] = 0;
395 if (tx && orgx != tx) {
396 indents[tx++] = ' ';
397 indents[tx] = 0;
398 }
399 indent = txtwidth1(bw->o.charmap, bw->o.tab, indents, tx);
400 }
401 for (x = 0; indents[x] && (indents[x] == ' ' || indents[x] == '\t'); ++x);
402 y = zlen(indents);
403 while (y && (indents[y - 1] == ' ' || indents[y - 1] == '\t'))
404 --y;
405 /* Don't duplicate bullet */
406 if (y && ((indents[y - 1] == '*' && !cpara(bw, '*')) || (indents[y - 1] == '-' && !cpara(bw, '-'))) &&
407 (y == 1 || indents[y - 2] == ' ' || indents[y - 2] == '\t'))
408 indents[y - 1] = ' ';
409 /* Fix C comment */
410 if (indents[x] == '/' && indents[x + 1] == '*')
411 indents[x] = ' ';
412 }
413 if (bw->o.lmargin > indent) {
414 int x;
415 for (x = 0; indents[x] == ' ' || indents[x] == '\t'; ++x);
416 if (!indents[x]) {
417 joe_free(indents);
418 indent = bw->o.lmargin;
419 indents = (char *)joe_malloc(indent+1); /* Risky */
420 for (x = 0; x != indent; ++x)
421 indents[x] = ' ';
422 indents[x] = 0;
423 }
424 }
425 my_indents = 1;
426 prm(q);
427 prm(s);
428 }
429
430
431 /*
432 if(!indents) {
433 int f = 0;
434 P *r = pdup(p);
435
436 p_goto_bol(r);
437 q = pdup(r);
438 while(cpara(c = brc(q))) {
439 if(!joe_isblank(c))
440 f = 1;
441 pgetc(q);
442 }
443 if(f) {
444 indents = brs(r, q->byte-r->byte);
445 rmf = 1;
446 if(indents[0] == '/' && indents[1] == '*')
447 indents[0] = ' ';
448 }
449 prm(r);
450 prm(q);
451 }
452 */
453
454 /* Get to beginning of word */
455 while (!pisbol(p) && piscol(p) > indent && !joe_isblank(p->b->o.charmap, prgetc(p)))
456 /* do nothing */;
457
458 /* If we found the beginning of a word... */
459 if (!pisbol(p) && piscol(p) > indent) {
460 /* Move q to two (or one if 'french' is set) spaces after end of previous
461 word */
462 q = pdup(p, "wrapword");
463 while (!pisbol(q))
464 if (!joe_isblank(p->b->o.charmap, (c = prgetc(q)))) {
465 pgetc(q);
466 if ((c == '.' || c == '?' || c == '!')
467 && q->byte != p->byte && !french)
468 pgetc(q);
469 break;
470 }
471 pgetc(p);
472
473 /* Delete space between start of word and end of previous word */
474 to -= p->byte - q->byte;
475 bdel(q, p);
476 prm(q);
477
478 if (bw->o.flowed) {
479 binsc(p, ' ');
480 pgetc(p);
481 ++to;
482 }
483
484 /* Move word to beginning of next line */
485 binsc(p, '\n');
486
487 /* When overtype is on, do not insert lines */
488 if (!no_over && p->b->o.overtype){
489 /* delete the next line break which is unnecessary */
490 r = pdup(p, "wrapword");
491 /* p_goto_eol(r); */
492 pgetc(r);
493 p_goto_eol(r);
494 s = pdup(r, "wrapword");
495 pgetc(r);
496 bdel(s,r);
497 binsc(r, ' ');
498
499 /* Now we got to take care that all subsequent lines are not longer than the right margin */
500 /* Move cursor to right margin */
501 pfwrd(r, r->b->o.rmargin - r->col);
502
503 /* Make a copy of the cursor and move the copied cursor to the end of the line */
504 prm(s);
505 s = pdup(r, "wrapword");
506 p_goto_eol(s);
507
508 /* If s is located behind r then the line goes beyond the right margin and we need to call wordwrap() for that line. */
509 /*
510 if (r->byte < s->byte){
511 wrapword(bw, r, indent, french, 1, indents);
512 }
513 */
514 prm(r);
515 prm(s);
516 }
517
518 ++to;
519 if (p->b->o.crlf)
520 ++to;
521 pgetc(p);
522
523 /* Indent to left margin */
524 if (indents) {
525 binss(p, indents);
526 to += zlen(indents);
527 } else
528 while (indent--) {
529 binsc(p, ' ');
530 ++to;
531 }
532
533 if (rmf)
534 joe_free(indents);
535 }
536
537 /* Move cursor back to original position */
538 pfwrd(p, to - p->byte);
539 if (my_indents)
540 joe_free(indents);
541 }
542
543 /* Reformat paragraph */
544
uformat(W * w,int k)545 int uformat(W *w, int k)
546 {
547 off_t indent;
548 char *indents;
549 B *buf;
550 P *b;
551 off_t curoff;
552 int c;
553 P *p, *q;
554 BW *bw;
555 int flag;
556 WIND_BW(bw, w);
557
558 p = pdup(bw->cursor, "uformat");
559 p_goto_bol(p);
560
561 /* Do nothing if we're not on a paragraph line */
562 if (pisnpara(bw, p)) {
563 prm(p);
564 return 0;
565 }
566
567 /* Move p to beginning of paragraph, bw->cursor to end of paragraph and
568 * set curoff to original cursor offset within the paragraph */
569 pbop(bw, p);
570 curoff = bw->cursor->byte - p->byte;
571 pset(bw->cursor, p);
572 peop(bw, bw->cursor);
573
574 /* Ensure that paragraph ends on a beginning of a line */
575 if (!pisbol(bw->cursor))
576 binsc(bw->cursor, '\n'), pgetc(bw->cursor);
577
578 /* Record indentation of second line of paragraph, of first line if there
579 * is only one line */
580 q = pdup(p, "uformat");
581 pnextl(q);
582 if (q->line != bw->cursor->line) {
583 P *r = pdup(q, "uformat");
584
585 indent = nindent(bw, q, 0);
586 pcol(r, indent);
587 indents = brs(q, r->byte - q->byte); /* Risky */
588 prm(r);
589 } else {
590 P *r = pdup(p, "uformat");
591 ptrdiff_t x, y;
592 indent = nindent(bw, p, 1); /* allowing * and - here */
593 pcol(r, indent);
594 indents = brs(p, r->byte - p->byte); /* Risky */
595 prm(r);
596 if (!bw->o.autoindent) {
597 /* Don't indent second line of single-line paragraphs if autoindent is off */
598 ptrdiff_t tx = zlen(indents);
599 while (tx && (indents[tx - 1] == ' ' || indents[tx - 1] == '\t'))
600 indents[--tx] = 0;
601 if (tx) {
602 indents[tx++] = ' ';
603 indents[tx] = 0;
604 }
605 indent = txtwidth1(bw->o.charmap, bw->o.tab, indents, tx);
606 }
607 for (x = 0; indents[x] && (indents[x] == ' ' || indents[x] == '\t'); ++x);
608 y = zlen(indents);
609 while (y && (indents[y - 1] == ' ' || indents[y - 1] == '\t'))
610 --y;
611 /* Don't duplicate if it looks like a bullet */
612 if (y && ((indents[y - 1] == '*' && !cpara(bw, '*')) || (indents[y - 1] == '-' && !cpara(bw, '-'))) &&
613 (y == 1 || indents[y - 2] == ' ' || indents[y - 2] == '\t'))
614 indents[y - 1] = ' ';
615 /* Fix C comment */
616 if (indents[x] == '/' && indents[x + 1] == '*')
617 indents[x] = ' ';
618 }
619 prm(q);
620
621 /* But if the left margin is greater, we use that instead */
622 if (bw->o.lmargin > indent) {
623 int x;
624 for (x = 0; indents[x] == ' ' || indents[x] == '\t'; ++x);
625 if (!indents[x]) {
626 joe_free(indents);
627 indent = bw->o.lmargin;
628 indents = (char *)joe_malloc(indent+1); /* Risky, indent could be very large in theory */
629 for (x = 0; x != indent; ++x)
630 indents[x] = ' ';
631 indents[x] = 0;
632 }
633 }
634
635 /* Cut paragraph into new buffer */
636
637 /* New buffer needs to inherit UTF-8 and CR-LF options */
638 buf = bcpy(p, bw->cursor);
639 buf->o.crlf = p->b->o.crlf;
640 buf->o.charmap = p->b->o.charmap;
641 bdel(p, bw->cursor);
642
643 /* text is in buffer. insert it at cursor */
644
645 b = pdup(buf->bof, "uformat");
646
647 /* First line: preserve whitespace within this line
648 (but still apply french spacing after periods) */
649 flag = 0;
650 while (!piseof(b)) {
651 c = brch(b);
652 if (joe_isblank(b->b->o.charmap,c) || c == '\n') {
653 int f = 0;
654
655 /* First space after end of word */
656 if (flag && piscol(p) > bw->o.rmargin)
657 wrapword(bw, p, indent, bw->o.french, 1, indents);
658
659 flag = 0;
660
661 /* Stop if we're at end of line */
662 if (c == '\n' || piseolblank(b))
663 break;
664
665 /* Set f if previous character was '.', '?' or '!' */
666 if (!pisbof(b)) {
667 P *d = pdup(b, "uformat");
668 int g = prgetc(d);
669 if (g=='.' || g=='?' || g=='!') {
670 f = 1;
671 }
672 prm(d);
673 }
674
675 if (f) {
676 /* Skip past the whitespace. */
677 while (joe_isblank(b->b->o.charmap, brc(b))) {
678 if(b->byte == curoff)
679 pset(bw->cursor, p);
680 pgetc(b);
681 }
682
683 /* Insert proper amount of whitespace */
684 if (!piseof(b)) {
685 if (!bw->o.french)
686 binsc(p, ' '), pgetc(p);
687 binsc(p, ' ');
688 pgetc(p);
689 }
690 } else {
691 /* Insert whitespace character, advance pointer */
692 binsc(p, pgetc(b));
693 pgetc(p);
694 }
695 } else {
696 /* Insert characters of word and wrap if necessary */
697 if (b->byte == curoff)
698 pset(bw->cursor, p);
699
700 binsc(p, pgetc(b));
701 pgetc(p);
702 flag = 1;
703 }
704 }
705
706 /* Remaining lines: collapse whitespace */
707
708 flag = 0;
709 while (!piseof(b)) {
710 c = brc(b);
711 if (joe_isblank(b->b->o.charmap,c) || c == '\n') {
712 int f = 0;
713 P *d;
714 int g;
715
716 /* First space at end of word, wrap it */
717 if (flag && piscol(p) > bw->o.rmargin)
718 wrapword(bw, p, indent, bw->o.french, 1, indents);
719
720 flag = 0;
721
722 /* Detect end of sentence */
723 d=pdup(b, "uformat");
724 g=prgetc(d);
725 if (g=='.' || g=='?' || g=='!') {
726 f = 1;
727 }
728 prm(d);
729
730 /* Skip past the whitespace. Skip over indentations */
731 loop:
732
733 c = brc(b);
734 if (c == '\n') {
735 if (b->byte == curoff)
736 pset(bw->cursor, p);
737
738 pgetc(b);
739 while (cpara(bw, (c=brch(b)))) {
740 if (b->byte == curoff)
741 pset(bw->cursor, p);
742 pgetc(b);
743 }
744 }
745
746 if (joe_isblank(b->b->o.charmap,c)) {
747 if(b->byte == curoff)
748 pset(bw->cursor, p);
749 pgetc(b);
750 goto loop;
751 }
752
753 /* Insert proper amount of whitespace */
754 if (!piseof(b)) {
755 if (f && !bw->o.french)
756 binsc(p, ' '), pgetc(p);
757 binsc(p, ' ');
758 pgetc(p);
759 }
760 } else {
761 /* Insert characters of word and wrap if necessary */
762 if (b->byte == curoff)
763 pset(bw->cursor, p);
764
765 binsc(p, pgetc(b));
766 pgetc(p);
767 flag = 1;
768 }
769 }
770
771 if (flag && piscol(p) > bw->o.rmargin)
772 wrapword(bw, p, indent, bw->o.french, 1, indents);
773
774 binsc(p, '\n');
775 prm(p);
776 brm(buf);
777 joe_free(indents);
778 return 0;
779 }
780
781 /* Format entire block */
782
ufmtblk(W * w,int k)783 int ufmtblk(W *w, int k)
784 {
785 BW *bw;
786 WIND_BW(bw, w);
787 if (markv(1) && bw->cursor->byte >= markb->byte && bw->cursor->byte <= markk->byte) {
788 markk->end = 1;
789 utomarkk(w, 0);
790 within = 1;
791 do {
792 ubop(w, 0), uformat(w, 0);
793 } while (bw->cursor->byte > markb->byte);
794 within = 0;
795 markk->end = 0;
796 if (lightoff)
797 unmark(w, 0);
798 return 0;
799 } else
800 return uformat(w, 0);
801 }
802