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

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