Manual pages: mcmcdiffmceditmcview

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

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