1 /* wordproc.c - word-processor mode for the editor: does dynamic
2 		paragraph formatting.
3    Copyright (C) 1996-2017 Paul Sheer
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18    02111-1307, USA.
19  */
20 
21 #include <config.h>
22 #include "edit.h"
23 #if defined (HAVE_MAD) && ! defined (MIDNIGHT) && ! defined (GTK)
24 #include "mad.h"
25 #endif
26 
27 #ifdef MIDNIGHT
28 #define tab_width option_tab_spacing
29 #endif
30 
31 int line_is_blank (WEdit * edit, long line);
32 
33 #define NO_FORMAT_CHARS_START "-+*\\,.;:&>"
34 
line_start(WEdit * edit,long line)35 static long line_start (WEdit * edit, long line)
36 {
37     static long p = -1, l = 0;
38     int c;
39     if (p == -1 || abs (l - line) > abs (edit->curs_line - line)) {
40 	l = edit->curs_line;
41 	p = edit->curs1;
42     }
43     if (line < l)
44 	p = edit_move_backward (edit, p, l - line);
45     else if (line > l)
46 	p = edit_move_forward (edit, p, line - l, 0);
47     l = line;
48     p = edit_bol (edit, p);
49     while (strchr ("\t ", c = edit_get_byte (edit, p)))
50 	p++;
51     return p;
52 }
53 
bad_line_start(WEdit * edit,long p)54 static int bad_line_start (WEdit * edit, long p)
55 {
56     int c;
57     c = edit_get_byte (edit, p);
58     if (c == '.') {		/* `...' is acceptable */
59 	if (edit_get_byte (edit, p + 1) == '.')
60 	    if (edit_get_byte (edit, p + 2) == '.')
61 		return 0;
62 	return 1;
63     }
64     if (c == '-') {
65 	if (edit_get_byte (edit, p + 1) == '-')
66 	    if (edit_get_byte (edit, p + 2) == '-')
67 		return 0;	/* `---' is acceptable */
68 	return 1;
69     }
70     if (strchr (NO_FORMAT_CHARS_START, c))
71 	return 1;
72     return 0;
73 }
74 
begin_paragraph(WEdit * edit,long p,int force)75 static long begin_paragraph (WEdit * edit, long p, int force)
76 {
77     int i;
78     for (i = edit->curs_line - 1; i > 0; i--) {
79 	if (line_is_blank (edit, i)) {
80 	    i++;
81 	    break;
82 	}
83 	if (force) {
84 	    if (bad_line_start (edit, line_start (edit, i))) {
85 		i++;
86 		break;
87 	    }
88 	}
89     }
90     return edit_move_backward (edit, edit_bol (edit, edit->curs1), edit->curs_line - i);
91 }
92 
end_paragraph(WEdit * edit,long p,int force)93 static long end_paragraph (WEdit * edit, long p, int force)
94 {
95     int i;
96     for (i = edit->curs_line + 1; i < edit->total_lines; i++) {
97 	if (line_is_blank (edit, i)) {
98 	    i--;
99 	    break;
100 	}
101 	if (force)
102 	    if (bad_line_start (edit, line_start (edit, i))) {
103 		i--;
104 		break;
105 	    }
106     }
107     return edit_eol (edit, edit_move_forward (edit, edit_bol (edit, edit->curs1), i - edit->curs_line, 0));
108 }
109 
get_paragraph(WEdit * edit,long p,long q,int indent,int * size)110 static unsigned char *get_paragraph (WEdit * edit, long p, long q, int indent, int *size)
111 {
112     unsigned char *s, *t;
113 #if 0
114     t = malloc ((q - p) + 2 * (q - p) / option_word_wrap_line_length + 10);
115 #else
116     t = malloc (2 * (q - p) + 1024);
117 #endif
118     if (!t)
119 	return 0;
120     for (s = t; p < q; p++, s++) {
121 	if (indent)
122 	    if (edit_get_byte (edit, p - 1) == '\n')
123 		while (strchr ("\t ", edit_get_byte (edit, p)))
124 		    p++;
125 	*s = edit_get_byte (edit, p);
126     }
127     *size = (unsigned long) s - (unsigned long) t;
128     t[*size] = '\n';
129     return t;
130 }
131 
strip_newlines(unsigned char * t,int size)132 static void strip_newlines (unsigned char *t, int size)
133 {
134     unsigned char *p = t;
135     while (size--) {
136 	*p = *p == '\n' ? ' ' : *p;
137 	p++;
138     }
139 }
140 
141 #ifndef MIDNIGHT
142 int edit_width_of_long_printable (int c);
143 #endif
144 /*
145    This is a copy of the function
146    int calc_text_pos (WEdit * edit, long b, long *q, int l)
147    in propfont.c  :(
148    It calculates the number of chars in a line specified to length l in pixels
149  */
150 extern int tab_width;
next_tab_pos(int x)151 static inline int next_tab_pos (int x)
152 {
153     return x += tab_width - x % tab_width;
154 }
line_pixel_length(unsigned char * t,long b,int l)155 static int line_pixel_length (unsigned char *t, long b, int l)
156 {
157     int x = 0, c, xn = 0;
158     for (;;) {
159 	c = t[b];
160 	switch (c) {
161 	case '\n':
162 	    return b;
163 	case '\t':
164 	    xn = next_tab_pos (x);
165 	    break;
166 	default:
167 #ifdef MIDNIGHT
168 	    xn = x + 1;
169 #else
170 	    xn = x + edit_width_of_long_printable (c);
171 #endif
172 	    break;
173 	}
174 	if (xn > l)
175 	    break;
176 	x = xn;
177 	b++;
178     }
179     return b;
180 }
181 
182 /* find the start of a word */
next_word_start(unsigned char * t,int q,int size)183 static int next_word_start (unsigned char *t, int q, int size)
184 {
185     int i;
186     for (i = q;; i++) {
187 	switch (t[i]) {
188 	case '\n':
189 	    return -1;
190 	case '\t':
191 	case ' ':
192 	    for (;; i++) {
193 		if (t[i] == '\n')
194 		    return -1;
195 		if (t[i] != ' ' && t[i] != '\t')
196 		    return i;
197 	    }
198 	    break;
199 	}
200     }
201 }
202 
203 /* find the start of a word */
word_start(unsigned char * t,int q,int size)204 static int word_start (unsigned char *t, int q, int size)
205 {
206     int i = q;
207     if (t[q] == ' ' || t[q] == '\t')
208 	return next_word_start (t, q, size);
209     for (;;) {
210 	int c;
211 	if (!i)
212 	    return -1;
213 	c = t[i - 1];
214 	if (c == '\n')
215 	    return -1;
216 	if (c == ' ' || c == '\t')
217 	    return i;
218 	i--;
219     }
220 }
221 
222 /* replaces ' ' with '\n' to properly format a paragraph */
format_this(unsigned char * t,int size,int indent)223 static void format_this (unsigned char *t, int size, int indent)
224 {
225     int q = 0, ww;
226     strip_newlines (t, size);
227     ww = option_word_wrap_line_length * FONT_MEAN_WIDTH - indent;
228     if (ww < FONT_MEAN_WIDTH * 2)
229 	ww = FONT_MEAN_WIDTH * 2;
230     for (;;) {
231 	int p;
232 	q = line_pixel_length (t, q, ww);
233 	if (q > size)
234 	    break;
235 	if (t[q] == '\n')
236 	    break;
237 	p = word_start (t, q, size);
238 	if (p == -1)
239 	    q = next_word_start (t, q, size);	/* Return the end of the word if the beginning
240 						   of the word is at the beginning of a line
241 						   (i.e. a very long word) */
242 	else
243 	    q = p;
244 	if (q == -1)	/* end of paragraph */
245 	    break;
246 	if (q)
247 	    t[q - 1] = '\n';
248     }
249 }
250 
replace_at(WEdit * edit,long q,int c)251 static void replace_at (WEdit * edit, long q, int c)
252 {
253     edit_cursor_move (edit, q - edit->curs1);
254     edit_delete (edit);
255     edit_insert_ahead (edit, c);
256 }
257 
258 void edit_insert_indent (WEdit * edit, int indent);
259 
260 /* replaces a block of text */
put_paragraph(WEdit * edit,unsigned char * t,long p,long q,int indent,int size)261 static void put_paragraph (WEdit * edit, unsigned char *t, long p, long q, int indent, int size)
262 {
263     long cursor;
264     int i, c = 0;
265     cursor = edit->curs1;
266     if (indent)
267 	while (strchr ("\t ", edit_get_byte (edit, p)))
268 	    p++;
269     for (i = 0; i < size; i++, p++) {
270 	if (i && indent) {
271 	    if (t[i - 1] == '\n' && c == '\n') {
272 		while (strchr ("\t ", edit_get_byte (edit, p)))
273 		    p++;
274 	    } else if (t[i - 1] == '\n') {
275 		long curs;
276 		edit_cursor_move (edit, p - edit->curs1);
277 		curs = edit->curs1;
278 		edit_insert_indent (edit, indent);
279 		if (cursor >= curs)
280 		    cursor += edit->curs1 - p;
281 		p = edit->curs1;
282 	    } else if (c == '\n') {
283 		edit_cursor_move (edit, p - edit->curs1);
284 		while (strchr ("\t ", edit_get_byte (edit, p))) {
285 		    edit_delete (edit);
286 		    if (cursor > edit->curs1)
287 			cursor--;
288 		}
289 		p = edit->curs1;
290 	    }
291 	}
292 	c = edit_get_byte (edit, p);
293 	if (c != t[i])
294 	    replace_at (edit, p, t[i]);
295     }
296     edit_cursor_move (edit, cursor - edit->curs1);	/* restore cursor position */
297 }
298 
299 int edit_indent_width (WEdit * edit, long p);
300 
test_indent(WEdit * edit,long p,long q)301 static int test_indent (WEdit * edit, long p, long q)
302 {
303     int indent;
304     indent = edit_indent_width (edit, p++);
305     if (!indent)
306 	return 0;
307     for (; p < q; p++)
308 	if (edit_get_byte (edit, p - 1) == '\n')
309 	    if (indent != edit_indent_width (edit, p))
310 		return 0;
311     return indent;
312 }
313 
new_behaviour_message(WEdit * edit)314 static void new_behaviour_message (WEdit * edit)
315 {
316     char *filename;
317     int fd;
318     filename = catstrs (home_dir, PARMESS_FILE, NULL);
319     if ((fd = open (filename, O_RDONLY)) < 0) {
320 	edit_message_dialog (_(" Format Paragraph "), "\
321 This message will not be displayed again\n\
322 \n\
323 The new \"format paragraph\" command will format\n\
324 text inside the selected region if one is highlighted.\n\
325 Otherwise it would try to find the bounds of the\n\
326 current paragraph using heuristics.");
327 	fd = creat (filename, 0400);
328     }
329     close (fd);
330 }
331 
format_paragraph(WEdit * edit,int force)332 void format_paragraph (WEdit * edit, int force)
333 {
334     long p, q;
335     int size;
336     unsigned char *t;
337     int indent = 0;
338     if (option_word_wrap_line_length < 2)
339 	return;
340     if (force)
341 	new_behaviour_message (edit);
342     if (force && !eval_marks (edit, &p, &q)) {
343 	p = edit_bol (edit, p);
344 	q = edit_eol (edit, q);
345     } else {
346 	if (line_is_blank (edit, edit->curs_line))
347 	    return;
348 	p = begin_paragraph (edit, edit->curs1, force);
349 	q = end_paragraph (edit, edit->curs1, force);
350     }
351     CPushFont ("editor", 0);
352     indent = test_indent (edit, p, q);
353     t = get_paragraph (edit, p, q, indent, &size);
354     if (!t)
355 	return;
356     if (!force) {
357 	int i;
358 	if (strchr (NO_FORMAT_CHARS_START, *t)) {
359 	    free (t);
360 	    return;
361 	}
362 	for (i = 0; i < size - 1; i++) {
363 	    if (t[i] == '\n') {
364 		if (strchr (NO_FORMAT_CHARS_START "\t ", t[i + 1])) {
365 		    free (t);
366 		    return;
367 		}
368 	    }
369 	}
370     }
371     format_this (t, q - p, indent);
372     put_paragraph (edit, t, p, q, indent, size);
373     free (t);
374     CPopFont ();
375 }
376 
377 
378 
379 
380 
381 
382 
383 
384 
385 
386