diff options
Diffstat (limited to 'src/editor/format.c')
-rw-r--r-- | src/editor/format.c | 538 |
1 files changed, 538 insertions, 0 deletions
diff --git a/src/editor/format.c b/src/editor/format.c new file mode 100644 index 0000000..3193067 --- /dev/null +++ b/src/editor/format.c @@ -0,0 +1,538 @@ +/* + Dynamic paragraph formatting. + + Copyright (C) 2011-2023 + Free Software Foundation, Inc. + + Copyright (C) 1996 Paul Sheer + + Written by: + Paul Sheer, 1996 + Andrew Borodin <aborodin@vmail.ru>, 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 <http://www.gnu.org/licenses/>. + */ + +/** \file + * \brief Source: Dynamic paragraph formatting + * \author Paul Sheer + * \date 1996 + * \author Andrew Borodin + * \date 2013, 2014 + */ + +#include <config.h> + +#include <stdio.h> +#include <stdarg.h> +#include <sys/types.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <sys/stat.h> + +#include <stdlib.h> + +#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); +} + +/* --------------------------------------------------------------------------------------------- */ |