root/src/editor/format.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. line_start
  2. bad_line_start
  3. begin_paragraph
  4. end_paragraph
  5. get_paragraph
  6. strip_newlines
  7. next_tab_pos
  8. line_pixel_length
  9. next_word_start
  10. word_start
  11. format_this
  12. replace_at
  13. edit_indent_width
  14. edit_insert_indent
  15. put_paragraph
  16. test_indent
  17. format_paragraph

   1 /*
   2    Dynamic paragraph formatting.
   3 
   4    Copyright (C) 2011-2025
   5    Free Software Foundation, Inc.
   6 
   7    Copyright (C) 1996 Paul Sheer
   8 
   9    Written by:
  10    Paul Sheer, 1996
  11    Andrew Borodin <aborodin@vmail.ru>, 2013, 2014
  12 
  13    This file is part of the Midnight Commander.
  14 
  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.
  19 
  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.
  24 
  25    You should have received a copy of the GNU General Public License
  26    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  27  */
  28 
  29 /** \file
  30  *  \brief Source: Dynamic paragraph formatting
  31  *  \author Paul Sheer
  32  *  \date 1996
  33  *  \author Andrew Borodin
  34  *  \date 2013, 2014
  35  */
  36 
  37 #include <config.h>
  38 
  39 #include <stdio.h>
  40 #include <stdarg.h>
  41 #include <sys/types.h>
  42 #include <unistd.h>
  43 #include <string.h>
  44 #include <ctype.h>
  45 #include <sys/stat.h>
  46 
  47 #include <stdlib.h>
  48 
  49 #include "lib/global.h"
  50 #include "lib/util.h"  // whitespace()
  51 
  52 #include "edit-impl.h"
  53 #include "editwidget.h"
  54 
  55 /*** global variables ****************************************************************************/
  56 
  57 /*** file scope macro definitions ****************************************************************/
  58 
  59 #define FONT_MEAN_WIDTH 1
  60 
  61 /*** file scope type declarations ****************************************************************/
  62 
  63 /*** forward declarations (file scope functions) *************************************************/
  64 
  65 /*** file scope variables ************************************************************************/
  66 
  67 /* --------------------------------------------------------------------------------------------- */
  68 /*** file scope functions ************************************************************************/
  69 /* --------------------------------------------------------------------------------------------- */
  70 
  71 static off_t
  72 line_start (const edit_buffer_t *buf, long line)
     /* [previous][next][first][last][top][bottom][index][help]  */
  73 {
  74     off_t p;
  75     long l;
  76 
  77     l = buf->curs_line;
  78     p = buf->curs1;
  79 
  80     if (line < l)
  81         p = edit_buffer_get_backward_offset (buf, p, l - line);
  82     else if (line > l)
  83         p = edit_buffer_get_forward_offset (buf, p, line - l, 0);
  84 
  85     p = edit_buffer_get_bol (buf, p);
  86     while (strchr ("\t ", edit_buffer_get_byte (buf, p)) != NULL)
  87         p++;
  88     return p;
  89 }
  90 
  91 /* --------------------------------------------------------------------------------------------- */
  92 
  93 static gboolean
  94 bad_line_start (const edit_buffer_t *buf, off_t p)
     /* [previous][next][first][last][top][bottom][index][help]  */
  95 {
  96     int c;
  97 
  98     c = edit_buffer_get_byte (buf, p);
  99     if (c == '.')
 100     {
 101         // `...' is acceptable
 102         return !(edit_buffer_get_byte (buf, p + 1) == '.'
 103                  && edit_buffer_get_byte (buf, p + 2) == '.');
 104     }
 105     if (c == '-')
 106     {
 107         // `---' is acceptable
 108         return !(edit_buffer_get_byte (buf, p + 1) == '-'
 109                  && edit_buffer_get_byte (buf, p + 2) == '-');
 110     }
 111 
 112     return (edit_options.stop_format_chars != NULL
 113             && strchr (edit_options.stop_format_chars, c) != NULL);
 114 }
 115 
 116 /* --------------------------------------------------------------------------------------------- */
 117 /**
 118  * Find the start of the current paragraph for the purpose of formatting.
 119  * Return position in the file.
 120  */
 121 
 122 static off_t
 123 begin_paragraph (WEdit *edit, gboolean force, long *lines)
     /* [previous][next][first][last][top][bottom][index][help]  */
 124 {
 125     long i;
 126 
 127     for (i = edit->buffer.curs_line - 1; i >= 0; i--)
 128         if (edit_line_is_blank (edit, i)
 129             || (force && bad_line_start (&edit->buffer, line_start (&edit->buffer, i))))
 130         {
 131             i++;
 132             break;
 133         }
 134 
 135     *lines = edit->buffer.curs_line - i;
 136 
 137     return edit_buffer_get_backward_offset (&edit->buffer,
 138                                             edit_buffer_get_current_bol (&edit->buffer), *lines);
 139 }
 140 
 141 /* --------------------------------------------------------------------------------------------- */
 142 /**
 143  * Find the end of the current paragraph for the purpose of formatting.
 144  * Return position in the file.
 145  */
 146 
 147 static off_t
 148 end_paragraph (WEdit *edit, gboolean force)
     /* [previous][next][first][last][top][bottom][index][help]  */
 149 {
 150     long i;
 151 
 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))))
 155         {
 156             i--;
 157             break;
 158         }
 159 
 160     return edit_buffer_get_eol (
 161         &edit->buffer,
 162         edit_buffer_get_forward_offset (&edit->buffer, edit_buffer_get_current_bol (&edit->buffer),
 163                                         i - edit->buffer.curs_line, 0));
 164 }
 165 
 166 /* --------------------------------------------------------------------------------------------- */
 167 
 168 static GString *
 169 get_paragraph (const edit_buffer_t *buf, off_t p, off_t q, gboolean indent)
     /* [previous][next][first][last][top][bottom][index][help]  */
 170 {
 171     GString *t;
 172 
 173     t = g_string_sized_new (128);
 174 
 175     for (; p < q; p++)
 176     {
 177         if (indent && edit_buffer_get_byte (buf, p - 1) == '\n')
 178             while (strchr ("\t ", edit_buffer_get_byte (buf, p)) != NULL)
 179                 p++;
 180 
 181         g_string_append_c (t, edit_buffer_get_byte (buf, p));
 182     }
 183 
 184     g_string_append_c (t, '\n');
 185 
 186     return t;
 187 }
 188 
 189 /* --------------------------------------------------------------------------------------------- */
 190 
 191 static inline void
 192 strip_newlines (unsigned char *t, off_t size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 193 {
 194     unsigned char *p;
 195 
 196     for (p = t; size-- != 0; p++)
 197         if (*p == '\n')
 198             *p = ' ';
 199 }
 200 
 201 /* --------------------------------------------------------------------------------------------- */
 202 /**
 203    This function calculates the number of chars in a line specified to length l in pixels
 204  */
 205 
 206 static inline off_t
 207 next_tab_pos (off_t x)
     /* [previous][next][first][last][top][bottom][index][help]  */
 208 {
 209     x += TAB_SIZE - x % TAB_SIZE;
 210     return x;
 211 }
 212 
 213 /* --------------------------------------------------------------------------------------------- */
 214 
 215 static inline off_t
 216 line_pixel_length (unsigned char *t, off_t b, off_t l, gboolean utf8)
     /* [previous][next][first][last][top][bottom][index][help]  */
 217 {
 218     off_t xn, x;            // position counters
 219     off_t char_length = 0;  // character length in bytes
 220 
 221 #ifndef HAVE_CHARSET
 222     (void) utf8;
 223 #endif
 224 
 225     for (xn = 0, x = 0; xn <= l; x = xn)
 226     {
 227         char *tb;
 228 
 229         b += char_length;
 230         tb = (char *) t + b;
 231         char_length = 1;
 232 
 233         switch (tb[0])
 234         {
 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)
 243             {
 244                 gunichar ch;
 245 
 246                 ch = g_utf8_get_char_validated (tb, -1);
 247                 if (ch != (gunichar) (-2) && ch != (gunichar) (-1))
 248                 {
 249                     char *next_ch;
 250 
 251                     // Calculate UTF-8 char length
 252                     next_ch = g_utf8_next_char (tb);
 253                     char_length = next_ch - tb;
 254 
 255                     if (g_unichar_iswide (ch))
 256                         x++;
 257                 }
 258             }
 259 #endif
 260 
 261             xn = x + 1;
 262             break;
 263         }
 264     }
 265 
 266     return b;
 267 }
 268 
 269 /* --------------------------------------------------------------------------------------------- */
 270 
 271 static off_t
 272 next_word_start (unsigned char *t, off_t q, off_t size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 273 {
 274     off_t i;
 275     gboolean saw_ws = FALSE;
 276 
 277     for (i = q; i < size; i++)
 278     {
 279         switch (t[i])
 280         {
 281         case '\n':
 282             return -1;
 283         case '\t':
 284         case ' ':
 285             saw_ws = TRUE;
 286             break;
 287         default:
 288             if (saw_ws)
 289                 return i;
 290             break;
 291         }
 292     }
 293     return (-1);
 294 }
 295 
 296 /* --------------------------------------------------------------------------------------------- */
 297 /** find the start of a word */
 298 
 299 static inline int
 300 word_start (unsigned char *t, off_t q, off_t size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 301 {
 302     off_t i;
 303 
 304     if (whitespace (t[q]))
 305         return next_word_start (t, q, size);
 306 
 307     for (i = q;; i--)
 308     {
 309         unsigned char c;
 310 
 311         if (i == 0)
 312             return (-1);
 313         c = t[i - 1];
 314         if (c == '\n')
 315             return (-1);
 316         if (whitespace (c))
 317             return i;
 318     }
 319 }
 320 
 321 /* --------------------------------------------------------------------------------------------- */
 322 /** replaces ' ' with '\n' to properly format a paragraph */
 323 
 324 static inline void
 325 format_this (unsigned char *t, off_t size, long indent, gboolean utf8)
     /* [previous][next][first][last][top][bottom][index][help]  */
 326 {
 327     off_t q = 0, ww;
 328 
 329     strip_newlines (t, size);
 330     ww = edit_options.word_wrap_line_length * FONT_MEAN_WIDTH - indent;
 331     if (ww < FONT_MEAN_WIDTH * 2)
 332         ww = FONT_MEAN_WIDTH * 2;
 333 
 334     while (TRUE)
 335     {
 336         off_t p;
 337 
 338         q = line_pixel_length (t, q, ww, utf8);
 339         if (q > size)
 340             break;
 341         if (t[q] == '\n')
 342             break;
 343         p = word_start (t, q, size);
 344         if (p == -1)
 345             q = next_word_start (t, q, size); /* Return the end of the word if the beginning
 346                                                  of the word is at the beginning of a line
 347                                                  (i.e. a very long word) */
 348         else
 349             q = p;
 350         if (q == -1)  // end of paragraph
 351             break;
 352         if (q != 0)
 353             t[q - 1] = '\n';
 354     }
 355 }
 356 
 357 /* --------------------------------------------------------------------------------------------- */
 358 
 359 static inline void
 360 replace_at (WEdit *edit, off_t q, int c)
     /* [previous][next][first][last][top][bottom][index][help]  */
 361 {
 362     edit_cursor_move (edit, q - edit->buffer.curs1);
 363     edit_delete (edit, TRUE);
 364     edit_insert_ahead (edit, c);
 365 }
 366 
 367 /* --------------------------------------------------------------------------------------------- */
 368 
 369 static long
 370 edit_indent_width (const WEdit *edit, off_t p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 371 {
 372     off_t q = p;
 373 
 374     // move to the end of the leading whitespace of the line
 375     while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, q)) != NULL
 376            && q < edit->buffer.size - 1)
 377         q++;
 378     // count the number of columns of indentation
 379     return (long) edit_move_forward3 (edit, p, 0, q);
 380 }
 381 
 382 /* --------------------------------------------------------------------------------------------- */
 383 
 384 static void
 385 edit_insert_indent (WEdit *edit, long indent)
     /* [previous][next][first][last][top][bottom][index][help]  */
 386 {
 387     if (!edit_options.fill_tabs_with_spaces)
 388         while (indent >= TAB_SIZE)
 389         {
 390             edit_insert (edit, '\t');
 391             indent -= TAB_SIZE;
 392         }
 393 
 394     while (indent-- > 0)
 395         edit_insert (edit, ' ');
 396 }
 397 
 398 /* --------------------------------------------------------------------------------------------- */
 399 /** replaces a block of text */
 400 
 401 static inline void
 402 put_paragraph (WEdit *edit, unsigned char *t, off_t p, long indent, off_t size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 403 {
 404     off_t cursor;
 405     off_t i;
 406     int c = '\0';
 407 
 408     cursor = edit->buffer.curs1;
 409     if (indent != 0)
 410         while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
 411             p++;
 412     for (i = 0; i < size; i++, p++)
 413     {
 414         if (i != 0 && indent != 0)
 415         {
 416             if (t[i - 1] == '\n' && c == '\n')
 417             {
 418                 while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
 419                     p++;
 420             }
 421             else if (t[i - 1] == '\n')
 422             {
 423                 off_t curs;
 424 
 425                 edit_cursor_move (edit, p - edit->buffer.curs1);
 426                 curs = edit->buffer.curs1;
 427                 edit_insert_indent (edit, indent);
 428                 if (cursor >= curs)
 429                     cursor += edit->buffer.curs1 - p;
 430                 p = edit->buffer.curs1;
 431             }
 432             else if (c == '\n')
 433             {
 434                 edit_cursor_move (edit, p - edit->buffer.curs1);
 435                 while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
 436                 {
 437                     edit_delete (edit, TRUE);
 438                     if (cursor > edit->buffer.curs1)
 439                         cursor--;
 440                 }
 441                 p = edit->buffer.curs1;
 442             }
 443         }
 444 
 445         c = edit_buffer_get_byte (&edit->buffer, p);
 446         if (c != t[i])
 447             replace_at (edit, p, t[i]);
 448     }
 449     edit_cursor_move (edit, cursor - edit->buffer.curs1);  // restore cursor position
 450 }
 451 
 452 /* --------------------------------------------------------------------------------------------- */
 453 
 454 static inline long
 455 test_indent (const WEdit *edit, off_t p, off_t q)
     /* [previous][next][first][last][top][bottom][index][help]  */
 456 {
 457     long indent;
 458 
 459     indent = edit_indent_width (edit, p++);
 460     if (indent == 0)
 461         return 0;
 462 
 463     for (; p < q; p++)
 464         if (edit_buffer_get_byte (&edit->buffer, p - 1) == '\n'
 465             && indent != edit_indent_width (edit, p))
 466             return 0;
 467     return indent;
 468 }
 469 
 470 /* --------------------------------------------------------------------------------------------- */
 471 /*** public functions ****************************************************************************/
 472 /* --------------------------------------------------------------------------------------------- */
 473 
 474 void
 475 format_paragraph (WEdit *edit, gboolean force)
     /* [previous][next][first][last][top][bottom][index][help]  */
 476 {
 477     off_t p, q;
 478     long lines;
 479     off_t size;
 480     GString *t;
 481     long indent;
 482     unsigned char *t2;
 483     gboolean utf8 = FALSE;
 484 
 485     if (edit_options.word_wrap_line_length < 2)
 486         return;
 487     if (edit_line_is_blank (edit, edit->buffer.curs_line))
 488         return;
 489 
 490     p = begin_paragraph (edit, force, &lines);
 491     q = end_paragraph (edit, force);
 492     indent = test_indent (edit, p, q);
 493 
 494     t = get_paragraph (&edit->buffer, p, q, indent != 0);
 495     size = t->len - 1;
 496 
 497     if (!force)
 498     {
 499         off_t i;
 500         char *stop_format_chars;
 501 
 502         if (edit_options.stop_format_chars != NULL
 503             && strchr (edit_options.stop_format_chars, t->str[0]) != NULL)
 504         {
 505             g_string_free (t, TRUE);
 506             return;
 507         }
 508 
 509         if (edit_options.stop_format_chars == NULL || *edit_options.stop_format_chars == '\0')
 510             stop_format_chars = g_strdup ("\t");
 511         else
 512             stop_format_chars = g_strconcat (edit_options.stop_format_chars, "\t", (char *) NULL);
 513 
 514         for (i = 0; i < size - 1; i++)
 515             if (t->str[i] == '\n' && strchr (stop_format_chars, t->str[i + 1]) != NULL)
 516             {
 517                 g_free (stop_format_chars);
 518                 g_string_free (t, TRUE);
 519                 return;
 520             }
 521 
 522         g_free (stop_format_chars);
 523     }
 524 
 525     t2 = (unsigned char *) g_string_free (t, FALSE);
 526 #ifdef HAVE_CHARSET
 527     utf8 = edit->utf8;
 528 #endif
 529     format_this (t2, q - p, indent, utf8);
 530     put_paragraph (edit, t2, p, indent, size);
 531     g_free ((char *) t2);
 532 
 533     // Scroll left as much as possible to show the formatted paragraph
 534     edit_scroll_left (edit, -edit->start_col);
 535 }
 536 
 537 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */