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-2024
   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 <http://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 (&edit->buffer,
 161                                 edit_buffer_get_forward_offset (&edit->buffer,
 162                                                                 edit_buffer_get_current_bol
 163                                                                 (&edit->buffer),
 164                                                                 i - edit->buffer.curs_line, 0));
 165 }
 166 
 167 /* --------------------------------------------------------------------------------------------- */
 168 
 169 static GString *
 170 get_paragraph (const edit_buffer_t *buf, off_t p, off_t q, gboolean indent)
     /* [previous][next][first][last][top][bottom][index][help]  */
 171 {
 172     GString *t;
 173 
 174     t = g_string_sized_new (128);
 175 
 176     for (; p < q; p++)
 177     {
 178         if (indent && edit_buffer_get_byte (buf, p - 1) == '\n')
 179             while (strchr ("\t ", edit_buffer_get_byte (buf, p)) != NULL)
 180                 p++;
 181 
 182         g_string_append_c (t, edit_buffer_get_byte (buf, p));
 183     }
 184 
 185     g_string_append_c (t, '\n');
 186 
 187     return t;
 188 }
 189 
 190 /* --------------------------------------------------------------------------------------------- */
 191 
 192 static inline void
 193 strip_newlines (unsigned char *t, off_t size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 194 {
 195     unsigned char *p;
 196 
 197     for (p = t; size-- != 0; p++)
 198         if (*p == '\n')
 199             *p = ' ';
 200 }
 201 
 202 /* --------------------------------------------------------------------------------------------- */
 203 /**
 204    This function calculates the number of chars in a line specified to length l in pixels
 205  */
 206 
 207 static inline off_t
 208 next_tab_pos (off_t x)
     /* [previous][next][first][last][top][bottom][index][help]  */
 209 {
 210     x += TAB_SIZE - x % TAB_SIZE;
 211     return x;
 212 }
 213 
 214 /* --------------------------------------------------------------------------------------------- */
 215 
 216 static inline off_t
 217 line_pixel_length (unsigned char *t, off_t b, off_t l, gboolean utf8)
     /* [previous][next][first][last][top][bottom][index][help]  */
 218 {
 219     off_t xn, x;                /* position counters */
 220     off_t char_length = 0;      /* character length in bytes */
 221 
 222 #ifndef HAVE_CHARSET
 223     (void) utf8;
 224 #endif
 225 
 226     for (xn = 0, x = 0; xn <= l; x = xn)
 227     {
 228         char *tb;
 229 
 230         b += char_length;
 231         tb = (char *) t + b;
 232         char_length = 1;
 233 
 234         switch (tb[0])
 235         {
 236         case '\n':
 237             return b;
 238         case '\t':
 239             xn = next_tab_pos (x);
 240             break;
 241         default:
 242 #ifdef HAVE_CHARSET
 243             if (utf8)
 244             {
 245                 gunichar ch;
 246 
 247                 ch = g_utf8_get_char_validated (tb, -1);
 248                 if (ch != (gunichar) (-2) && ch != (gunichar) (-1))
 249                 {
 250                     char *next_ch;
 251 
 252                     /* Calculate UTF-8 char length */
 253                     next_ch = g_utf8_next_char (tb);
 254                     char_length = next_ch - tb;
 255 
 256                     if (g_unichar_iswide (ch))
 257                         x++;
 258                 }
 259             }
 260 #endif
 261 
 262             xn = x + 1;
 263             break;
 264         }
 265     }
 266 
 267     return b;
 268 }
 269 
 270 /* --------------------------------------------------------------------------------------------- */
 271 
 272 static off_t
 273 next_word_start (unsigned char *t, off_t q, off_t size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 274 {
 275     off_t i;
 276     gboolean saw_ws = FALSE;
 277 
 278     for (i = q; i < size; i++)
 279     {
 280         switch (t[i])
 281         {
 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;
 292         }
 293     }
 294     return (-1);
 295 }
 296 
 297 /* --------------------------------------------------------------------------------------------- */
 298 /** find the start of a word */
 299 
 300 static inline int
 301 word_start (unsigned char *t, off_t q, off_t size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 302 {
 303     off_t i;
 304 
 305     if (whitespace (t[q]))
 306         return next_word_start (t, q, size);
 307 
 308     for (i = q;; i--)
 309     {
 310         unsigned char c;
 311 
 312         if (i == 0)
 313             return (-1);
 314         c = t[i - 1];
 315         if (c == '\n')
 316             return (-1);
 317         if (whitespace (c))
 318             return i;
 319     }
 320 }
 321 
 322 /* --------------------------------------------------------------------------------------------- */
 323 /** replaces ' ' with '\n' to properly format a paragraph */
 324 
 325 static inline void
 326 format_this (unsigned char *t, off_t size, long indent, gboolean utf8)
     /* [previous][next][first][last][top][bottom][index][help]  */
 327 {
 328     off_t q = 0, ww;
 329 
 330     strip_newlines (t, size);
 331     ww = edit_options.word_wrap_line_length * FONT_MEAN_WIDTH - indent;
 332     if (ww < FONT_MEAN_WIDTH * 2)
 333         ww = FONT_MEAN_WIDTH * 2;
 334 
 335     while (TRUE)
 336     {
 337         off_t p;
 338 
 339         q = line_pixel_length (t, q, ww, utf8);
 340         if (q > size)
 341             break;
 342         if (t[q] == '\n')
 343             break;
 344         p = word_start (t, q, size);
 345         if (p == -1)
 346             q = next_word_start (t, q, size);   /* Return the end of the word if the beginning
 347                                                    of the word is at the beginning of a line
 348                                                    (i.e. a very long word) */
 349         else
 350             q = p;
 351         if (q == -1)            /* end of paragraph */
 352             break;
 353         if (q != 0)
 354             t[q - 1] = '\n';
 355     }
 356 }
 357 
 358 /* --------------------------------------------------------------------------------------------- */
 359 
 360 static inline void
 361 replace_at (WEdit *edit, off_t q, int c)
     /* [previous][next][first][last][top][bottom][index][help]  */
 362 {
 363     edit_cursor_move (edit, q - edit->buffer.curs1);
 364     edit_delete (edit, TRUE);
 365     edit_insert_ahead (edit, c);
 366 }
 367 
 368 /* --------------------------------------------------------------------------------------------- */
 369 
 370 static long
 371 edit_indent_width (const WEdit *edit, off_t p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 372 {
 373     off_t q = p;
 374 
 375     /* move to the end of the leading whitespace of the line */
 376     while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, q)) != NULL
 377            && q < edit->buffer.size - 1)
 378         q++;
 379     /* count the number of columns of indentation */
 380     return (long) edit_move_forward3 (edit, p, 0, q);
 381 }
 382 
 383 /* --------------------------------------------------------------------------------------------- */
 384 
 385 static void
 386 edit_insert_indent (WEdit *edit, long indent)
     /* [previous][next][first][last][top][bottom][index][help]  */
 387 {
 388     if (!edit_options.fill_tabs_with_spaces)
 389         while (indent >= TAB_SIZE)
 390         {
 391             edit_insert (edit, '\t');
 392             indent -= TAB_SIZE;
 393         }
 394 
 395     while (indent-- > 0)
 396         edit_insert (edit, ' ');
 397 }
 398 
 399 /* --------------------------------------------------------------------------------------------- */
 400 /** replaces a block of text */
 401 
 402 static inline void
 403 put_paragraph (WEdit *edit, unsigned char *t, off_t p, long indent, off_t size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 404 {
 405     off_t cursor;
 406     off_t i;
 407     int c = '\0';
 408 
 409     cursor = edit->buffer.curs1;
 410     if (indent != 0)
 411         while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
 412             p++;
 413     for (i = 0; i < size; i++, p++)
 414     {
 415         if (i != 0 && indent != 0)
 416         {
 417             if (t[i - 1] == '\n' && c == '\n')
 418             {
 419                 while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
 420                     p++;
 421             }
 422             else if (t[i - 1] == '\n')
 423             {
 424                 off_t curs;
 425 
 426                 edit_cursor_move (edit, p - edit->buffer.curs1);
 427                 curs = edit->buffer.curs1;
 428                 edit_insert_indent (edit, indent);
 429                 if (cursor >= curs)
 430                     cursor += edit->buffer.curs1 - p;
 431                 p = edit->buffer.curs1;
 432             }
 433             else if (c == '\n')
 434             {
 435                 edit_cursor_move (edit, p - edit->buffer.curs1);
 436                 while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
 437                 {
 438                     edit_delete (edit, TRUE);
 439                     if (cursor > edit->buffer.curs1)
 440                         cursor--;
 441                 }
 442                 p = edit->buffer.curs1;
 443             }
 444         }
 445 
 446         c = edit_buffer_get_byte (&edit->buffer, p);
 447         if (c != t[i])
 448             replace_at (edit, p, t[i]);
 449     }
 450     edit_cursor_move (edit, cursor - edit->buffer.curs1);       /* restore cursor position */
 451 }
 452 
 453 /* --------------------------------------------------------------------------------------------- */
 454 
 455 static inline long
 456 test_indent (const WEdit *edit, off_t p, off_t q)
     /* [previous][next][first][last][top][bottom][index][help]  */
 457 {
 458     long indent;
 459 
 460     indent = edit_indent_width (edit, p++);
 461     if (indent == 0)
 462         return 0;
 463 
 464     for (; p < q; p++)
 465         if (edit_buffer_get_byte (&edit->buffer, p - 1) == '\n'
 466             && indent != edit_indent_width (edit, p))
 467             return 0;
 468     return indent;
 469 }
 470 
 471 /* --------------------------------------------------------------------------------------------- */
 472 /*** public functions ****************************************************************************/
 473 /* --------------------------------------------------------------------------------------------- */
 474 
 475 void
 476 format_paragraph (WEdit *edit, gboolean force)
     /* [previous][next][first][last][top][bottom][index][help]  */
 477 {
 478     off_t p, q;
 479     long lines;
 480     off_t size;
 481     GString *t;
 482     long indent;
 483     unsigned char *t2;
 484     gboolean utf8 = FALSE;
 485 
 486     if (edit_options.word_wrap_line_length < 2)
 487         return;
 488     if (edit_line_is_blank (edit, edit->buffer.curs_line))
 489         return;
 490 
 491     p = begin_paragraph (edit, force, &lines);
 492     q = end_paragraph (edit, force);
 493     indent = test_indent (edit, p, q);
 494 
 495     t = get_paragraph (&edit->buffer, p, q, indent != 0);
 496     size = t->len - 1;
 497 
 498     if (!force)
 499     {
 500         off_t i;
 501         char *stop_format_chars;
 502 
 503         if (edit_options.stop_format_chars != NULL
 504             && strchr (edit_options.stop_format_chars, t->str[0]) != NULL)
 505         {
 506             g_string_free (t, TRUE);
 507             return;
 508         }
 509 
 510         if (edit_options.stop_format_chars == NULL || *edit_options.stop_format_chars == '\0')
 511             stop_format_chars = g_strdup ("\t");
 512         else
 513             stop_format_chars = g_strconcat (edit_options.stop_format_chars, "\t", (char *) NULL);
 514 
 515         for (i = 0; i < size - 1; i++)
 516             if (t->str[i] == '\n' && strchr (stop_format_chars, t->str[i + 1]) != NULL)
 517             {
 518                 g_free (stop_format_chars);
 519                 g_string_free (t, TRUE);
 520                 return;
 521             }
 522 
 523         g_free (stop_format_chars);
 524     }
 525 
 526     t2 = (unsigned char *) g_string_free (t, FALSE);
 527 #ifdef HAVE_CHARSET
 528     utf8 = edit->utf8;
 529 #endif
 530     format_this (t2, q - p, indent, utf8);
 531     put_paragraph (edit, t2, p, indent, size);
 532     g_free ((char *) t2);
 533 
 534     /* Scroll left as much as possible to show the formatted paragraph */
 535     edit_scroll_left (edit, -edit->start_col);
 536 }
 537 
 538 /* --------------------------------------------------------------------------------------------- */

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