2 Dynamic paragraph formatting.
4 Copyright (C) 2011-2016
5 Free Software Foundation, Inc.
7 Copyright (C) 1996 Paul Sheer
11 Andrew Borodin <aborodin@vmail.ru>, 2013, 2014
13 This file is part of the Midnight Commander.
15 The Midnight Commander is free software: you can redistribute it
16 and/or modify it under the terms of the GNU General Public License as
17 published by the Free Software Foundation, either version 3 of the License,
18 or (at your option) any later version.
20 The Midnight Commander is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program. If not, see <http://www.gnu.org/licenses/>.
30 * \brief Source: Dynamic paragraph formatting
33 * \author Andrew Borodin
41 #include <sys/types.h>
50 #include "lib/global.h"
52 #include "src/setup.h" /* option_tab_spacing */
54 #include "edit-impl.h"
55 #include "editwidget.h"
57 /*** global variables ****************************************************************************/
59 char *option_stop_format_chars
= NULL
;
61 /*** file scope macro definitions ****************************************************************/
63 #define tab_width option_tab_spacing
65 #define FONT_MEAN_WIDTH 1
67 /*** file scope type declarations ****************************************************************/
69 /*** file scope variables ************************************************************************/
71 /*** file scope functions ************************************************************************/
72 /* --------------------------------------------------------------------------------------------- */
75 line_start (const edit_buffer_t
* buf
, long line
)
84 p
= edit_buffer_get_backward_offset (buf
, p
, l
- line
);
86 p
= edit_buffer_get_forward_offset (buf
, p
, line
- l
, 0);
88 p
= edit_buffer_get_bol (buf
, p
);
89 while (strchr ("\t ", edit_buffer_get_byte (buf
, p
)) != NULL
)
94 /* --------------------------------------------------------------------------------------------- */
97 bad_line_start (const edit_buffer_t
* buf
, off_t p
)
101 c
= edit_buffer_get_byte (buf
, p
);
104 /* `...' is acceptable */
105 return !(edit_buffer_get_byte (buf
, p
+ 1) == '.'
106 && edit_buffer_get_byte (buf
, p
+ 2) == '.');
110 /* `---' is acceptable */
111 return !(edit_buffer_get_byte (buf
, p
+ 1) == '-'
112 && edit_buffer_get_byte (buf
, p
+ 2) == '-');
115 return (option_stop_format_chars
!= NULL
&& strchr (option_stop_format_chars
, c
) != NULL
);
118 /* --------------------------------------------------------------------------------------------- */
120 * Find the start of the current paragraph for the purpose of formatting.
121 * Return position in the file.
125 begin_paragraph (WEdit
* edit
, gboolean force
, long *lines
)
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
))))
137 *lines
= edit
->buffer
.curs_line
- i
;
139 return edit_buffer_get_backward_offset (&edit
->buffer
,
140 edit_buffer_get_current_bol (&edit
->buffer
), *lines
);
143 /* --------------------------------------------------------------------------------------------- */
145 * Find the end of the current paragraph for the purpose of formatting.
146 * Return position in the file.
150 end_paragraph (WEdit
* edit
, gboolean force
)
154 for (i
= edit
->buffer
.curs_line
+ 1; i
<= edit
->buffer
.lines
; i
++)
155 if (edit_line_is_blank (edit
, i
) ||
156 (force
&& bad_line_start (&edit
->buffer
, line_start (&edit
->buffer
, i
))))
162 return edit_buffer_get_eol (&edit
->buffer
,
163 edit_buffer_get_forward_offset (&edit
->buffer
,
164 edit_buffer_get_current_bol
166 i
- edit
->buffer
.curs_line
, 0));
169 /* --------------------------------------------------------------------------------------------- */
172 get_paragraph (const edit_buffer_t
* buf
, off_t p
, off_t q
, gboolean indent
)
176 t
= g_string_sized_new (128);
180 if (indent
&& edit_buffer_get_byte (buf
, p
- 1) == '\n')
181 while (strchr ("\t ", edit_buffer_get_byte (buf
, p
)) != NULL
)
184 g_string_append_c (t
, edit_buffer_get_byte (buf
, p
));
187 g_string_append_c (t
, '\n');
192 /* --------------------------------------------------------------------------------------------- */
195 strip_newlines (unsigned char *t
, off_t size
)
199 for (p
= t
; size
-- != 0; p
++)
204 /* --------------------------------------------------------------------------------------------- */
206 This function calculates the number of chars in a line specified to length l in pixels
210 next_tab_pos (off_t x
)
212 x
+= tab_width
- x
% tab_width
;
216 /* --------------------------------------------------------------------------------------------- */
219 line_pixel_length (unsigned char *t
, off_t b
, off_t l
, gboolean utf8
)
221 off_t xn
, x
; /* position conters */
222 off_t char_length
; /* character length in bytes */
228 for (xn
= 0, x
= 0; xn
<= l
; x
= xn
, b
+= char_length
)
240 xn
= next_tab_pos (x
);
248 ch
= g_utf8_get_char_validated (tb
, -1);
249 if (ch
!= (gunichar
) (-2) && ch
!= (gunichar
) (-1))
253 /* Calculate UTF-8 char length */
254 next_ch
= g_utf8_next_char (tb
);
255 char_length
= next_ch
- tb
;
257 if (g_unichar_iswide (ch
))
271 /* --------------------------------------------------------------------------------------------- */
274 next_word_start (unsigned char *t
, off_t q
, off_t size
)
277 gboolean saw_ws
= FALSE
;
279 for (i
= q
; i
< size
; i
++)
298 /* --------------------------------------------------------------------------------------------- */
299 /** find the start of a word */
302 word_start (unsigned char *t
, off_t q
, off_t size
)
306 if (t
[q
] == ' ' || t
[q
] == '\t')
307 return next_word_start (t
, q
, size
);
318 if (c
== ' ' || c
== '\t')
323 /* --------------------------------------------------------------------------------------------- */
324 /** replaces ' ' with '\n' to properly format a paragraph */
327 format_this (unsigned char *t
, off_t size
, long indent
, gboolean utf8
)
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;
340 q
= line_pixel_length (t
, q
, ww
, utf8
);
345 p
= word_start (t
, q
, size
);
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) */
352 if (q
== -1) /* end of paragraph */
359 /* --------------------------------------------------------------------------------------------- */
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 /* --------------------------------------------------------------------------------------------- */
372 edit_indent_width (const WEdit
* edit
, off_t 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)
380 /* count the number of columns of indentation */
381 return (long) edit_move_forward3 (edit
, p
, 0, q
);
384 /* --------------------------------------------------------------------------------------------- */
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');
397 edit_insert (edit
, ' ');
400 /* --------------------------------------------------------------------------------------------- */
401 /** replaces a block of text */
404 put_paragraph (WEdit
* edit
, unsigned char *t
, off_t p
, long indent
, off_t size
)
410 cursor
= edit
->buffer
.curs1
;
412 while (strchr ("\t ", edit_buffer_get_byte (&edit
->buffer
, p
)) != NULL
)
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
)
423 else if (t
[i
- 1] == '\n')
427 edit_cursor_move (edit
, p
- edit
->buffer
.curs1
);
428 curs
= edit
->buffer
.curs1
;
429 edit_insert_indent (edit
, indent
);
431 cursor
+= edit
->buffer
.curs1
- p
;
432 p
= edit
->buffer
.curs1
;
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
)
443 p
= edit
->buffer
.curs1
;
447 c
= edit_buffer_get_byte (&edit
->buffer
, p
);
449 replace_at (edit
, p
, t
[i
]);
453 /* --------------------------------------------------------------------------------------------- */
456 test_indent (const WEdit
* edit
, off_t p
, off_t q
)
460 indent
= edit_indent_width (edit
, p
++);
465 if (edit_buffer_get_byte (&edit
->buffer
, p
- 1) == '\n'
466 && indent
!= edit_indent_width (edit
, p
))
471 /* --------------------------------------------------------------------------------------------- */
472 /*** public functions ****************************************************************************/
473 /* --------------------------------------------------------------------------------------------- */
476 format_paragraph (WEdit
* edit
, gboolean force
)
484 gboolean utf8
= FALSE
;
486 if (option_word_wrap_line_length
< 2)
488 if (edit_line_is_blank (edit
, edit
->buffer
.curs_line
))
491 p
= begin_paragraph (edit
, force
, &lines
);
492 q
= end_paragraph (edit
, force
);
493 indent
= test_indent (edit
, p
, q
);
495 t
= get_paragraph (&edit
->buffer
, p
, q
, indent
!= 0);
501 char *stop_format_chars
;
503 if (option_stop_format_chars
!= NULL
504 && strchr (option_stop_format_chars
, t
->str
[0]) != NULL
)
506 g_string_free (t
, TRUE
);
510 if (option_stop_format_chars
== NULL
|| *option_stop_format_chars
== '\0')
511 stop_format_chars
= g_strdup ("\t");
513 stop_format_chars
= g_strconcat (option_stop_format_chars
, "\t", (char *) NULL
);
515 for (i
= 0; i
< size
- 1; i
++)
516 if (t
->str
[i
] == '\n' && strchr (stop_format_chars
, t
->str
[i
+ 1]) != NULL
)
518 g_free (stop_format_chars
);
519 g_string_free (t
, TRUE
);
523 g_free (stop_format_chars
);
526 t2
= (unsigned char *) g_string_free (t
, FALSE
);
530 /* scroll up to show 1st line of paragraph */
531 edit_move_up (edit
, lines
, TRUE
);
532 /* scroll left as much as possible to show the formatted paragraph */
533 edit_scroll_left (edit
, -edit
->start_col
);
535 format_this (t2
, q
- p
, indent
, utf8
);
536 put_paragraph (edit
, t2
, p
, indent
, size
);
537 g_free ((char *) t2
);
539 /* move to the end of paragraph */
540 q
= end_paragraph (edit
, force
);
541 edit_cursor_move (edit
, q
- edit
->buffer
.curs1
);
543 /* try move to the start of next paragraph */
544 if (edit
->buffer
.curs_line
< edit
->buffer
.lines
)
546 edit_execute_cmd (edit
, CK_Home
, -1);
550 edit_execute_cmd (edit
, CK_Down
, -1);
552 while (edit
->buffer
.curs_line
< edit
->buffer
.lines
553 && edit_line_is_blank (edit
, edit
->buffer
.curs_line
));
557 /* --------------------------------------------------------------------------------------------- */