/* Dynamic paragraph formatting. Copyright (C) 2011-2024 Free Software Foundation, Inc. Copyright (C) 1996 Paul Sheer Written by: Paul Sheer, 1996 Andrew Borodin , 2013, 2014 This file is part of the Midnight Commander. The Midnight Commander is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The Midnight Commander is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /** \file * \brief Source: Dynamic paragraph formatting * \author Paul Sheer * \date 1996 * \author Andrew Borodin * \date 2013, 2014 */ #include #include #include #include #include #include #include #include #include #include "lib/global.h" #include "lib/util.h" /* whitespace() */ #include "edit-impl.h" #include "editwidget.h" /*** global variables ****************************************************************************/ /*** file scope macro definitions ****************************************************************/ #define FONT_MEAN_WIDTH 1 /*** file scope type declarations ****************************************************************/ /*** forward declarations (file scope functions) *************************************************/ /*** file scope variables ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ /*** file scope functions ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ static off_t line_start (const edit_buffer_t * buf, long line) { off_t p; long l; l = buf->curs_line; p = buf->curs1; if (line < l) p = edit_buffer_get_backward_offset (buf, p, l - line); else if (line > l) p = edit_buffer_get_forward_offset (buf, p, line - l, 0); p = edit_buffer_get_bol (buf, p); while (strchr ("\t ", edit_buffer_get_byte (buf, p)) != NULL) p++; return p; } /* --------------------------------------------------------------------------------------------- */ static gboolean bad_line_start (const edit_buffer_t * buf, off_t p) { int c; c = edit_buffer_get_byte (buf, p); if (c == '.') { /* `...' is acceptable */ return !(edit_buffer_get_byte (buf, p + 1) == '.' && edit_buffer_get_byte (buf, p + 2) == '.'); } if (c == '-') { /* `---' is acceptable */ return !(edit_buffer_get_byte (buf, p + 1) == '-' && edit_buffer_get_byte (buf, p + 2) == '-'); } return (edit_options.stop_format_chars != NULL && strchr (edit_options.stop_format_chars, c) != NULL); } /* --------------------------------------------------------------------------------------------- */ /** * Find the start of the current paragraph for the purpose of formatting. * Return position in the file. */ static off_t begin_paragraph (WEdit * edit, gboolean force, long *lines) { long i; for (i = edit->buffer.curs_line - 1; i >= 0; i--) if (edit_line_is_blank (edit, i) || (force && bad_line_start (&edit->buffer, line_start (&edit->buffer, i)))) { i++; break; } *lines = edit->buffer.curs_line - i; return edit_buffer_get_backward_offset (&edit->buffer, edit_buffer_get_current_bol (&edit->buffer), *lines); } /* --------------------------------------------------------------------------------------------- */ /** * Find the end of the current paragraph for the purpose of formatting. * Return position in the file. */ static off_t end_paragraph (WEdit * edit, gboolean force) { long i; for (i = edit->buffer.curs_line + 1; i <= edit->buffer.lines; i++) if (edit_line_is_blank (edit, i) || (force && bad_line_start (&edit->buffer, line_start (&edit->buffer, i)))) { i--; break; } return edit_buffer_get_eol (&edit->buffer, edit_buffer_get_forward_offset (&edit->buffer, edit_buffer_get_current_bol (&edit->buffer), i - edit->buffer.curs_line, 0)); } /* --------------------------------------------------------------------------------------------- */ static GString * get_paragraph (const edit_buffer_t * buf, off_t p, off_t q, gboolean indent) { GString *t; t = g_string_sized_new (128); for (; p < q; p++) { if (indent && edit_buffer_get_byte (buf, p - 1) == '\n') while (strchr ("\t ", edit_buffer_get_byte (buf, p)) != NULL) p++; g_string_append_c (t, edit_buffer_get_byte (buf, p)); } g_string_append_c (t, '\n'); return t; } /* --------------------------------------------------------------------------------------------- */ static inline void strip_newlines (unsigned char *t, off_t size) { unsigned char *p; for (p = t; size-- != 0; p++) if (*p == '\n') *p = ' '; } /* --------------------------------------------------------------------------------------------- */ /** This function calculates the number of chars in a line specified to length l in pixels */ static inline off_t next_tab_pos (off_t x) { x += TAB_SIZE - x % TAB_SIZE; return x; } /* --------------------------------------------------------------------------------------------- */ static inline off_t line_pixel_length (unsigned char *t, off_t b, off_t l, gboolean utf8) { off_t xn, x; /* position counters */ off_t char_length = 0; /* character length in bytes */ #ifndef HAVE_CHARSET (void) utf8; #endif for (xn = 0, x = 0; xn <= l; x = xn) { char *tb; b += char_length; tb = (char *) t + b; char_length = 1; switch (tb[0]) { case '\n': return b; case '\t': xn = next_tab_pos (x); break; default: #ifdef HAVE_CHARSET if (utf8) { gunichar ch; ch = g_utf8_get_char_validated (tb, -1); if (ch != (gunichar) (-2) && ch != (gunichar) (-1)) { char *next_ch; /* Calculate UTF-8 char length */ next_ch = g_utf8_next_char (tb); char_length = next_ch - tb; if (g_unichar_iswide (ch)) x++; } } #endif xn = x + 1; break; } } return b; } /* --------------------------------------------------------------------------------------------- */ static off_t next_word_start (unsigned char *t, off_t q, off_t size) { off_t i; gboolean saw_ws = FALSE; for (i = q; i < size; i++) { switch (t[i]) { case '\n': return -1; case '\t': case ' ': saw_ws = TRUE; break; default: if (saw_ws) return i; break; } } return (-1); } /* --------------------------------------------------------------------------------------------- */ /** find the start of a word */ static inline int word_start (unsigned char *t, off_t q, off_t size) { off_t i; if (whitespace (t[q])) return next_word_start (t, q, size); for (i = q;; i--) { unsigned char c; if (i == 0) return (-1); c = t[i - 1]; if (c == '\n') return (-1); if (whitespace (c)) return i; } } /* --------------------------------------------------------------------------------------------- */ /** replaces ' ' with '\n' to properly format a paragraph */ static inline void format_this (unsigned char *t, off_t size, long indent, gboolean utf8) { off_t q = 0, ww; strip_newlines (t, size); ww = edit_options.word_wrap_line_length * FONT_MEAN_WIDTH - indent; if (ww < FONT_MEAN_WIDTH * 2) ww = FONT_MEAN_WIDTH * 2; while (TRUE) { off_t p; q = line_pixel_length (t, q, ww, utf8); if (q > size) break; if (t[q] == '\n') break; p = word_start (t, q, size); if (p == -1) q = next_word_start (t, q, size); /* Return the end of the word if the beginning of the word is at the beginning of a line (i.e. a very long word) */ else q = p; if (q == -1) /* end of paragraph */ break; if (q != 0) t[q - 1] = '\n'; } } /* --------------------------------------------------------------------------------------------- */ static inline void replace_at (WEdit * edit, off_t q, int c) { edit_cursor_move (edit, q - edit->buffer.curs1); edit_delete (edit, TRUE); edit_insert_ahead (edit, c); } /* --------------------------------------------------------------------------------------------- */ static long edit_indent_width (const WEdit * edit, off_t p) { off_t q = p; /* move to the end of the leading whitespace of the line */ while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, q)) != NULL && q < edit->buffer.size - 1) q++; /* count the number of columns of indentation */ return (long) edit_move_forward3 (edit, p, 0, q); } /* --------------------------------------------------------------------------------------------- */ static void edit_insert_indent (WEdit * edit, long indent) { if (!edit_options.fill_tabs_with_spaces) while (indent >= TAB_SIZE) { edit_insert (edit, '\t'); indent -= TAB_SIZE; } while (indent-- > 0) edit_insert (edit, ' '); } /* --------------------------------------------------------------------------------------------- */ /** replaces a block of text */ static inline void put_paragraph (WEdit * edit, unsigned char *t, off_t p, long indent, off_t size) { off_t cursor; off_t i; int c = '\0'; cursor = edit->buffer.curs1; if (indent != 0) while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL) p++; for (i = 0; i < size; i++, p++) { if (i != 0 && indent != 0) { if (t[i - 1] == '\n' && c == '\n') { while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL) p++; } else if (t[i - 1] == '\n') { off_t curs; edit_cursor_move (edit, p - edit->buffer.curs1); curs = edit->buffer.curs1; edit_insert_indent (edit, indent); if (cursor >= curs) cursor += edit->buffer.curs1 - p; p = edit->buffer.curs1; } else if (c == '\n') { edit_cursor_move (edit, p - edit->buffer.curs1); while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL) { edit_delete (edit, TRUE); if (cursor > edit->buffer.curs1) cursor--; } p = edit->buffer.curs1; } } c = edit_buffer_get_byte (&edit->buffer, p); if (c != t[i]) replace_at (edit, p, t[i]); } edit_cursor_move (edit, cursor - edit->buffer.curs1); /* restore cursor position */ } /* --------------------------------------------------------------------------------------------- */ static inline long test_indent (const WEdit * edit, off_t p, off_t q) { long indent; indent = edit_indent_width (edit, p++); if (indent == 0) return 0; for (; p < q; p++) if (edit_buffer_get_byte (&edit->buffer, p - 1) == '\n' && indent != edit_indent_width (edit, p)) return 0; return indent; } /* --------------------------------------------------------------------------------------------- */ /*** public functions ****************************************************************************/ /* --------------------------------------------------------------------------------------------- */ void format_paragraph (WEdit * edit, gboolean force) { off_t p, q; long lines; off_t size; GString *t; long indent; unsigned char *t2; gboolean utf8 = FALSE; if (edit_options.word_wrap_line_length < 2) return; if (edit_line_is_blank (edit, edit->buffer.curs_line)) return; p = begin_paragraph (edit, force, &lines); q = end_paragraph (edit, force); indent = test_indent (edit, p, q); t = get_paragraph (&edit->buffer, p, q, indent != 0); size = t->len - 1; if (!force) { off_t i; char *stop_format_chars; if (edit_options.stop_format_chars != NULL && strchr (edit_options.stop_format_chars, t->str[0]) != NULL) { g_string_free (t, TRUE); return; } if (edit_options.stop_format_chars == NULL || *edit_options.stop_format_chars == '\0') stop_format_chars = g_strdup ("\t"); else stop_format_chars = g_strconcat (edit_options.stop_format_chars, "\t", (char *) NULL); for (i = 0; i < size - 1; i++) if (t->str[i] == '\n' && strchr (stop_format_chars, t->str[i + 1]) != NULL) { g_free (stop_format_chars); g_string_free (t, TRUE); return; } g_free (stop_format_chars); } t2 = (unsigned char *) g_string_free (t, FALSE); #ifdef HAVE_CHARSET utf8 = edit->utf8; #endif format_this (t2, q - p, indent, utf8); put_paragraph (edit, t2, p, indent, size); g_free ((char *) t2); /* Scroll left as much as possible to show the formatted paragraph */ edit_scroll_left (edit, -edit->start_col); } /* --------------------------------------------------------------------------------------------- */