Remove input_set_origin(). Use widget_set_size() instead.
[midnight-commander.git] / src / editor / wordproc.c
blob8c42ce962d6bb5d6ffa6304285729297187f6347
1 /*
2 Word-processor mode for the editor: does dynamic
3 paragraph formatting.
5 Copyright (C) 2011, 2013
6 The Free Software Foundation, Inc.
8 Copyright (C) 1996 Paul Sheer
10 Writen by:
11 Paul Sheer, 1996
12 Andrew Borodin <aborodin@vmail.ru>, 2013
14 This file is part of the Midnight Commander.
16 The Midnight Commander is free software: you can redistribute it
17 and/or modify it under the terms of the GNU General Public License as
18 published by the Free Software Foundation, either version 3 of the License,
19 or (at your option) any later version.
21 The Midnight Commander is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
26 You should have received a copy of the GNU General Public License
27 along with this program. If not, see <http://www.gnu.org/licenses/>.
30 /** \file
31 * \brief Source: word-processor mode for the editor: does dynamic paragraph formatting
32 * \author Paul Sheer
33 * \date 1996
34 * \author Andrew Borodin
35 * \date 2013
38 #include <config.h>
40 #include <stdio.h>
41 #include <stdarg.h>
42 #include <sys/types.h>
43 #include <unistd.h>
44 #include <string.h>
45 #include <ctype.h>
46 #include <errno.h>
47 #include <sys/stat.h>
49 #include <stdlib.h>
51 #include "lib/global.h"
53 #include "src/setup.h" /* option_tab_spacing */
55 #include "edit-impl.h"
56 #include "editwidget.h"
58 /*** global variables ****************************************************************************/
60 /*** file scope macro definitions ****************************************************************/
62 #define tab_width option_tab_spacing
64 #define NO_FORMAT_CHARS_START "-+*\\,.;:&>"
65 #define FONT_MEAN_WIDTH 1
67 /*** file scope type declarations ****************************************************************/
69 /*** file scope variables ************************************************************************/
71 /*** file scope functions ************************************************************************/
72 /* --------------------------------------------------------------------------------------------- */
74 static off_t
75 line_start (const edit_buffer_t * buf, long line)
77 off_t p;
78 long l;
80 l = buf->curs_line;
81 p = buf->curs1;
83 if (line < l)
84 p = edit_buffer_move_backward (buf, p, l - line);
85 else if (line > l)
86 p = edit_buffer_move_forward (buf, p, line - l, 0);
88 p = edit_buffer_get_bol (buf, p);
89 while (strchr ("\t ", edit_buffer_get_byte (buf, p)) != NULL)
90 p++;
91 return p;
94 /* --------------------------------------------------------------------------------------------- */
96 static gboolean
97 bad_line_start (const edit_buffer_t * buf, off_t p)
99 int c;
101 c = edit_buffer_get_byte (buf, p);
102 if (c == '.')
104 /* `...' is acceptable */
105 return !(edit_buffer_get_byte (buf, p + 1) == '.'
106 && edit_buffer_get_byte (buf, p + 2) == '.');
108 if (c == '-')
110 /* `---' is acceptable */
111 return !(edit_buffer_get_byte (buf, p + 1) == '-'
112 && edit_buffer_get_byte (buf, p + 2) == '-');
115 return (strchr (NO_FORMAT_CHARS_START, c) != NULL);
118 /* --------------------------------------------------------------------------------------------- */
120 * Find the start of the current paragraph for the purpose of formatting.
121 * Return position in the file.
124 static off_t
125 begin_paragraph (WEdit * edit, gboolean force)
127 long i;
129 for (i = edit->buffer.curs_line - 1; i >= 0; i--)
130 if (edit_line_is_blank (edit, i) ||
131 (force && bad_line_start (&edit->buffer, line_start (&edit->buffer, i))))
133 i++;
134 break;
137 return edit_buffer_move_backward (&edit->buffer, edit_buffer_get_current_bol (&edit->buffer),
138 edit->buffer.curs_line - i);
141 /* --------------------------------------------------------------------------------------------- */
143 * Find the end of the current paragraph for the purpose of formatting.
144 * Return position in the file.
147 static off_t
148 end_paragraph (WEdit * edit, gboolean force)
150 long i;
152 for (i = edit->buffer.curs_line + 1; i <= edit->buffer.lines; i++)
153 if (edit_line_is_blank (edit, i) ||
154 (force && bad_line_start (&edit->buffer, line_start (&edit->buffer, i))))
156 i--;
157 break;
160 return edit_buffer_get_eol (&edit->buffer,
161 edit_buffer_move_forward (&edit->buffer,
162 edit_buffer_get_current_bol
163 (&edit->buffer),
164 i - edit->buffer.curs_line, 0));
167 /* --------------------------------------------------------------------------------------------- */
169 static GString *
170 get_paragraph (const edit_buffer_t * buf, off_t p, off_t q, gboolean indent)
172 GString *t;
174 t = g_string_sized_new (128);
176 for (; p < q; p++)
178 if (indent && edit_buffer_get_byte (buf, p - 1) == '\n')
179 while (strchr ("\t ", edit_buffer_get_byte (buf, p)) != NULL)
180 p++;
182 g_string_append_c (t, edit_buffer_get_byte (buf, p));
185 g_string_append_c (t, '\n');
187 return t;
190 /* --------------------------------------------------------------------------------------------- */
192 static inline void
193 strip_newlines (unsigned char *t, off_t size)
195 unsigned char *p;
197 for (p = t; size-- != 0; p++)
198 if (*p == '\n')
199 *p = ' ';
202 /* --------------------------------------------------------------------------------------------- */
204 This function calculates the number of chars in a line specified to length l in pixels
207 static inline off_t
208 next_tab_pos (off_t x)
210 x += tab_width - x % tab_width;
211 return x;
214 /* --------------------------------------------------------------------------------------------- */
216 static inline off_t
217 line_pixel_length (unsigned char *t, off_t b, off_t l, gboolean utf8)
219 off_t xn, x; /* position conters */
220 off_t cw; /* character width in bytes */
222 #ifndef HAVE_CHARSET
223 (void) utf8;
224 #endif
226 for (xn = 0, x = 0; xn <= l; x = xn, b += cw)
228 char *tb;
230 tb = (char *) t + b;
231 cw = 1;
233 switch (tb[0])
235 case '\n':
236 return b;
237 case '\t':
238 xn = next_tab_pos (x);
239 break;
240 default:
241 #ifdef HAVE_CHARSET
242 if (utf8)
244 gunichar ch;
246 ch = g_utf8_get_char_validated (tb, -1);
247 if (ch != (gunichar) (-2) && ch != (gunichar) (-1))
249 char *next_ch;
251 /* Calculate UTF-8 char width */
252 next_ch = g_utf8_next_char (tb);
253 if (next_ch != NULL)
254 cw = next_ch - tb;
256 if (g_unichar_iswide (ch))
257 x++;
260 #endif
262 xn = x + 1;
263 break;
267 return b;
270 /* --------------------------------------------------------------------------------------------- */
272 static off_t
273 next_word_start (unsigned char *t, off_t q, off_t size)
275 off_t i;
276 gboolean saw_ws = FALSE;
278 for (i = q; i < size; i++)
280 switch (t[i])
282 case '\n':
283 return -1;
284 case '\t':
285 case ' ':
286 saw_ws = TRUE;
287 break;
288 default:
289 if (saw_ws)
290 return i;
291 break;
294 return (-1);
297 /* --------------------------------------------------------------------------------------------- */
298 /** find the start of a word */
300 static inline int
301 word_start (unsigned char *t, off_t q, off_t size)
303 off_t i;
305 if (t[q] == ' ' || t[q] == '\t')
306 return next_word_start (t, q, size);
308 for (i = q;; i--)
310 unsigned char c;
312 if (i == 0)
313 return (-1);
314 c = t[i - 1];
315 if (c == '\n')
316 return (-1);
317 if (c == ' ' || c == '\t')
318 return i;
319 i--;
323 /* --------------------------------------------------------------------------------------------- */
324 /** replaces ' ' with '\n' to properly format a paragraph */
326 static inline void
327 format_this (unsigned char *t, off_t size, long indent, gboolean utf8)
329 off_t q = 0, ww;
331 strip_newlines (t, size);
332 ww = option_word_wrap_line_length * FONT_MEAN_WIDTH - indent;
333 if (ww < FONT_MEAN_WIDTH * 2)
334 ww = FONT_MEAN_WIDTH * 2;
336 while (TRUE)
338 off_t p;
340 q = line_pixel_length (t, q, ww, utf8);
341 if (q > size)
342 break;
343 if (t[q] == '\n')
344 break;
345 p = word_start (t, q, size);
346 if (p == -1)
347 q = next_word_start (t, q, size); /* Return the end of the word if the beginning
348 of the word is at the beginning of a line
349 (i.e. a very long word) */
350 else
351 q = p;
352 if (q == -1) /* end of paragraph */
353 break;
354 if (q != 0)
355 t[q - 1] = '\n';
359 /* --------------------------------------------------------------------------------------------- */
361 static inline void
362 replace_at (WEdit * edit, off_t q, int c)
364 edit_cursor_move (edit, q - edit->buffer.curs1);
365 edit_delete (edit, TRUE);
366 edit_insert_ahead (edit, c);
369 /* --------------------------------------------------------------------------------------------- */
371 static long
372 edit_indent_width (const WEdit * edit, off_t p)
374 off_t q = p;
376 /* move to the end of the leading whitespace of the line */
377 while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, q)) != NULL
378 && q < edit->buffer.size - 1)
379 q++;
380 /* count the number of columns of indentation */
381 return (long) edit_move_forward3 (edit, p, 0, q);
384 /* --------------------------------------------------------------------------------------------- */
386 static void
387 edit_insert_indent (WEdit * edit, long indent)
389 if (!option_fill_tabs_with_spaces)
390 while (indent >= TAB_SIZE)
392 edit_insert (edit, '\t');
393 indent -= TAB_SIZE;
396 while (indent-- > 0)
397 edit_insert (edit, ' ');
400 /* --------------------------------------------------------------------------------------------- */
401 /** replaces a block of text */
403 static inline void
404 put_paragraph (WEdit * edit, unsigned char *t, off_t p, long indent, off_t size)
406 off_t cursor;
407 off_t i;
408 int c = '\0';
410 cursor = edit->buffer.curs1;
411 if (indent != 0)
412 while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
413 p++;
414 for (i = 0; i < size; i++, p++)
416 if (i != 0 && indent != 0)
418 if (t[i - 1] == '\n' && c == '\n')
420 while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
421 p++;
423 else if (t[i - 1] == '\n')
425 off_t curs;
427 edit_cursor_move (edit, p - edit->buffer.curs1);
428 curs = edit->buffer.curs1;
429 edit_insert_indent (edit, indent);
430 if (cursor >= curs)
431 cursor += edit->buffer.curs1 - p;
432 p = edit->buffer.curs1;
434 else if (c == '\n')
436 edit_cursor_move (edit, p - edit->buffer.curs1);
437 while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
439 edit_delete (edit, TRUE);
440 if (cursor > edit->buffer.curs1)
441 cursor--;
443 p = edit->buffer.curs1;
447 c = edit_buffer_get_byte (&edit->buffer, p);
448 if (c != t[i])
449 replace_at (edit, p, t[i]);
451 edit_cursor_move (edit, cursor - edit->buffer.curs1); /* restore cursor position */
454 /* --------------------------------------------------------------------------------------------- */
456 static inline long
457 test_indent (const WEdit * edit, off_t p, off_t q)
459 long indent;
461 indent = edit_indent_width (edit, p++);
462 if (indent == 0)
463 return 0;
465 for (; p < q; p++)
466 if (edit_buffer_get_byte (&edit->buffer, p - 1) == '\n'
467 && indent != edit_indent_width (edit, p))
468 return 0;
469 return indent;
472 /* --------------------------------------------------------------------------------------------- */
473 /*** public functions ****************************************************************************/
474 /* --------------------------------------------------------------------------------------------- */
476 void
477 format_paragraph (WEdit * edit, gboolean force)
479 off_t p, q;
480 off_t size;
481 GString *t;
482 long indent;
483 unsigned char *t2;
484 gboolean utf8 = FALSE;
486 if (option_word_wrap_line_length < 2)
487 return;
488 if (edit_line_is_blank (edit, edit->buffer.curs_line))
489 return;
491 p = begin_paragraph (edit, force);
492 q = end_paragraph (edit, force);
493 indent = test_indent (edit, p, q);
495 t = get_paragraph (&edit->buffer, p, q, indent != 0);
496 size = t->len - 1;
498 if (!force)
500 off_t i;
502 if (strchr (NO_FORMAT_CHARS_START, t->str[0]) == NULL)
503 for (i = 0; i < size - 1; i++)
504 if (t->str[i] == '\n'
505 && strchr (NO_FORMAT_CHARS_START "\t ", t->str[i + 1]) != NULL)
506 break;
508 g_string_free (t, TRUE);
509 return;
512 t2 = (unsigned char *) g_string_free (t, FALSE);
513 #ifdef HAVE_CHARSET
514 utf8 = edit->utf8;
515 #endif
516 format_this (t2, q - p, indent, utf8);
517 put_paragraph (edit, t2, p, indent, size);
518 g_free ((char *) t2);
520 /* Scroll left as much as possible to show the formatted paragraph */
521 edit_scroll_left (edit, -edit->start_col);
524 /* --------------------------------------------------------------------------------------------- */