diff options
Diffstat (limited to 'src/comformat.c')
| -rw-r--r-- | src/comformat.c | 855 |
1 files changed, 855 insertions, 0 deletions
diff --git a/src/comformat.c b/src/comformat.c new file mode 100644 index 0000000..b7b9f00 --- /dev/null +++ b/src/comformat.c @@ -0,0 +1,855 @@ +/* +** Copyright (c) 2007 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) +** +** This program 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. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code used to format and print comments or other +** text on a TTY. +*/ +#include "config.h" +#include "comformat.h" +#include <assert.h> + +#if INTERFACE +#define COMMENT_PRINT_NONE ((u32)0x00000000) /* No flags */ +#define COMMENT_PRINT_CANONICAL ((u32)0x00000001) /* Use canonical algorithm */ +#define COMMENT_PRINT_DEFAULT COMMENT_PRINT_CANONICAL /* Default */ +#define COMMENT_PRINT_UNSET (-1) /* Not initialized */ + +/* The canonical comment printing algorithm is recommended. We make +** no promise of on-going support for any of the following flags: +*/ +#define COMMENT_PRINT_TRIM_CRLF ((u32)0x00000002) /* Trim leading CR/LF. */ +#define COMMENT_PRINT_TRIM_SPACE ((u32)0x00000004) /* Trim leading/trailing. */ +#define COMMENT_PRINT_WORD_BREAK ((u32)0x00000008) /* Break lines on words. */ +#define COMMENT_PRINT_ORIG_BREAK ((u32)0x00000010) /* Break before original. */ +#define COMMENT_PRINT_SUMMARY ((u32)0x00000020) + /* Truncate after first blank line. */ +#endif + +/********* Code copied from SQLite src/shell.c.in on 2024-09-30 **********/ +/* Lookup table to estimate the number of columns consumed by a Unicode +** character. +*/ +static const struct { + unsigned char w; /* Width of the character in columns */ + int iFirst; /* First character in a span having this width */ +} aUWidth[] = { + /* {1, 0x00000}, */ + {0, 0x00300}, {1, 0x00370}, {0, 0x00483}, {1, 0x00487}, {0, 0x00488}, + {1, 0x0048a}, {0, 0x00591}, {1, 0x005be}, {0, 0x005bf}, {1, 0x005c0}, + {0, 0x005c1}, {1, 0x005c3}, {0, 0x005c4}, {1, 0x005c6}, {0, 0x005c7}, + {1, 0x005c8}, {0, 0x00600}, {1, 0x00604}, {0, 0x00610}, {1, 0x00616}, + {0, 0x0064b}, {1, 0x0065f}, {0, 0x00670}, {1, 0x00671}, {0, 0x006d6}, + {1, 0x006e5}, {0, 0x006e7}, {1, 0x006e9}, {0, 0x006ea}, {1, 0x006ee}, + {0, 0x0070f}, {1, 0x00710}, {0, 0x00711}, {1, 0x00712}, {0, 0x00730}, + {1, 0x0074b}, {0, 0x007a6}, {1, 0x007b1}, {0, 0x007eb}, {1, 0x007f4}, + {0, 0x00901}, {1, 0x00903}, {0, 0x0093c}, {1, 0x0093d}, {0, 0x00941}, + {1, 0x00949}, {0, 0x0094d}, {1, 0x0094e}, {0, 0x00951}, {1, 0x00955}, + {0, 0x00962}, {1, 0x00964}, {0, 0x00981}, {1, 0x00982}, {0, 0x009bc}, + {1, 0x009bd}, {0, 0x009c1}, {1, 0x009c5}, {0, 0x009cd}, {1, 0x009ce}, + {0, 0x009e2}, {1, 0x009e4}, {0, 0x00a01}, {1, 0x00a03}, {0, 0x00a3c}, + {1, 0x00a3d}, {0, 0x00a41}, {1, 0x00a43}, {0, 0x00a47}, {1, 0x00a49}, + {0, 0x00a4b}, {1, 0x00a4e}, {0, 0x00a70}, {1, 0x00a72}, {0, 0x00a81}, + {1, 0x00a83}, {0, 0x00abc}, {1, 0x00abd}, {0, 0x00ac1}, {1, 0x00ac6}, + {0, 0x00ac7}, {1, 0x00ac9}, {0, 0x00acd}, {1, 0x00ace}, {0, 0x00ae2}, + {1, 0x00ae4}, {0, 0x00b01}, {1, 0x00b02}, {0, 0x00b3c}, {1, 0x00b3d}, + {0, 0x00b3f}, {1, 0x00b40}, {0, 0x00b41}, {1, 0x00b44}, {0, 0x00b4d}, + {1, 0x00b4e}, {0, 0x00b56}, {1, 0x00b57}, {0, 0x00b82}, {1, 0x00b83}, + {0, 0x00bc0}, {1, 0x00bc1}, {0, 0x00bcd}, {1, 0x00bce}, {0, 0x00c3e}, + {1, 0x00c41}, {0, 0x00c46}, {1, 0x00c49}, {0, 0x00c4a}, {1, 0x00c4e}, + {0, 0x00c55}, {1, 0x00c57}, {0, 0x00cbc}, {1, 0x00cbd}, {0, 0x00cbf}, + {1, 0x00cc0}, {0, 0x00cc6}, {1, 0x00cc7}, {0, 0x00ccc}, {1, 0x00cce}, + {0, 0x00ce2}, {1, 0x00ce4}, {0, 0x00d41}, {1, 0x00d44}, {0, 0x00d4d}, + {1, 0x00d4e}, {0, 0x00dca}, {1, 0x00dcb}, {0, 0x00dd2}, {1, 0x00dd5}, + {0, 0x00dd6}, {1, 0x00dd7}, {0, 0x00e31}, {1, 0x00e32}, {0, 0x00e34}, + {1, 0x00e3b}, {0, 0x00e47}, {1, 0x00e4f}, {0, 0x00eb1}, {1, 0x00eb2}, + {0, 0x00eb4}, {1, 0x00eba}, {0, 0x00ebb}, {1, 0x00ebd}, {0, 0x00ec8}, + {1, 0x00ece}, {0, 0x00f18}, {1, 0x00f1a}, {0, 0x00f35}, {1, 0x00f36}, + {0, 0x00f37}, {1, 0x00f38}, {0, 0x00f39}, {1, 0x00f3a}, {0, 0x00f71}, + {1, 0x00f7f}, {0, 0x00f80}, {1, 0x00f85}, {0, 0x00f86}, {1, 0x00f88}, + {0, 0x00f90}, {1, 0x00f98}, {0, 0x00f99}, {1, 0x00fbd}, {0, 0x00fc6}, + {1, 0x00fc7}, {0, 0x0102d}, {1, 0x01031}, {0, 0x01032}, {1, 0x01033}, + {0, 0x01036}, {1, 0x0103b}, {0, 0x01058}, + {1, 0x0105a}, {2, 0x01100}, {0, 0x01160}, {1, 0x01200}, {0, 0x0135f}, + {1, 0x01360}, {0, 0x01712}, {1, 0x01715}, {0, 0x01732}, {1, 0x01735}, + {0, 0x01752}, {1, 0x01754}, {0, 0x01772}, {1, 0x01774}, {0, 0x017b4}, + {1, 0x017b6}, {0, 0x017b7}, {1, 0x017be}, {0, 0x017c6}, {1, 0x017c7}, + {0, 0x017c9}, {1, 0x017d4}, {0, 0x017dd}, {1, 0x017de}, {0, 0x0180b}, + {1, 0x0180e}, {0, 0x018a9}, {1, 0x018aa}, {0, 0x01920}, {1, 0x01923}, + {0, 0x01927}, {1, 0x01929}, {0, 0x01932}, {1, 0x01933}, {0, 0x01939}, + {1, 0x0193c}, {0, 0x01a17}, {1, 0x01a19}, {0, 0x01b00}, {1, 0x01b04}, + {0, 0x01b34}, {1, 0x01b35}, {0, 0x01b36}, {1, 0x01b3b}, {0, 0x01b3c}, + {1, 0x01b3d}, {0, 0x01b42}, {1, 0x01b43}, {0, 0x01b6b}, {1, 0x01b74}, + {0, 0x01dc0}, {1, 0x01dcb}, {0, 0x01dfe}, {1, 0x01e00}, {0, 0x0200b}, + {1, 0x02010}, {0, 0x0202a}, {1, 0x0202f}, {0, 0x02060}, {1, 0x02064}, + {0, 0x0206a}, {1, 0x02070}, {0, 0x020d0}, {1, 0x020f0}, {2, 0x02329}, + {1, 0x0232b}, {2, 0x02e80}, {0, 0x0302a}, {2, 0x03030}, {1, 0x0303f}, + {2, 0x03040}, {0, 0x03099}, {2, 0x0309b}, {1, 0x0a4d0}, {0, 0x0a806}, + {1, 0x0a807}, {0, 0x0a80b}, {1, 0x0a80c}, {0, 0x0a825}, {1, 0x0a827}, + {2, 0x0ac00}, {1, 0x0d7a4}, {2, 0x0f900}, {1, 0x0fb00}, {0, 0x0fb1e}, + {1, 0x0fb1f}, {0, 0x0fe00}, {2, 0x0fe10}, {1, 0x0fe1a}, {0, 0x0fe20}, + {1, 0x0fe24}, {2, 0x0fe30}, {1, 0x0fe70}, {0, 0x0feff}, {2, 0x0ff00}, + {1, 0x0ff61}, {2, 0x0ffe0}, {1, 0x0ffe7}, {0, 0x0fff9}, {1, 0x0fffc}, + {0, 0x10a01}, {1, 0x10a04}, {0, 0x10a05}, {1, 0x10a07}, {0, 0x10a0c}, + {1, 0x10a10}, {0, 0x10a38}, {1, 0x10a3b}, {0, 0x10a3f}, {1, 0x10a40}, + {0, 0x1d167}, {1, 0x1d16a}, {0, 0x1d173}, {1, 0x1d183}, {0, 0x1d185}, + {1, 0x1d18c}, {0, 0x1d1aa}, {1, 0x1d1ae}, {0, 0x1d242}, {1, 0x1d245}, + {2, 0x20000}, {1, 0x2fffe}, {2, 0x30000}, {1, 0x3fffe}, {0, 0xe0001}, + {1, 0xe0002}, {0, 0xe0020}, {1, 0xe0080}, {0, 0xe0100}, {1, 0xe01f0} +}; + +/* +** Return an estimate of the width, in columns, for the single Unicode +** character c. For normal characters, the answer is always 1. But the +** estimate might be 0 or 2 for zero-width and double-width characters. +** +** Different display devices display unicode using different widths. So +** it is impossible to know that true display width with 100% accuracy. +** Inaccuracies in the width estimates might cause columns to be misaligned. +** Unfortunately, there is nothing we can do about that. +*/ +static int cli_wcwidth(int c){ + int iFirst, iLast; + + /* Fast path for common characters */ + if( c<0x300 ) return 1; + + /* The general case */ + iFirst = 0; + iLast = sizeof(aUWidth)/sizeof(aUWidth[0]) - 1; + while( iFirst<iLast-1 ){ + int iMid = (iFirst+iLast)/2; + int cMid = aUWidth[iMid].iFirst; + if( cMid < c ){ + iFirst = iMid; + }else if( cMid > c ){ + iLast = iMid - 1; + }else{ + return aUWidth[iMid].w; + } + } + if( aUWidth[iLast].iFirst > c ) return aUWidth[iFirst].w; + return aUWidth[iLast].w; +} +/******* End of code copied from SQLite *************************************/ + +/* +** This is the previous value used by most external callers when they +** needed to specify a default maximum line length to be used with the +** comment_print() function. +*/ +#ifndef COMMENT_LEGACY_LINE_LENGTH +# define COMMENT_LEGACY_LINE_LENGTH (78) +#endif + +/* +** This is the number of spaces to print when a tab character is seen. +*/ +#ifndef COMMENT_TAB_WIDTH +# define COMMENT_TAB_WIDTH (8) +#endif + +/* +** This function sets the maximum number of characters to print per line +** based on the detected terminal line width, if available; otherwise, it +** uses the legacy default terminal line width minus the amount to indent. +** +** Zero is returned to indicate any failure. One is returned to indicate +** the successful detection of the terminal line width. Negative one is +** returned to indicate the terminal line width is using the hard-coded +** legacy default value. +*/ +static int comment_set_maxchars( + int indent, + int *pMaxChars +){ + struct TerminalSize ts; + if ( !terminal_get_size(&ts) ){ + return 0; + } + + if( ts.nColumns ){ + *pMaxChars = ts.nColumns - indent; + return 1; + }else{ + /* + ** Fallback to using more-or-less the "legacy semantics" of hard-coding + ** the maximum line length to a value reasonable for the vast majority + ** of supported systems. + */ + *pMaxChars = COMMENT_LEGACY_LINE_LENGTH - indent; + return -1; + } +} + +/* +** This function checks the current line being printed against the original +** comment text. Upon matching, it updates the provided character and line +** counts, if applicable. The caller needs to emit a new line, if desired. +*/ +static int comment_check_orig( + const char *zOrigText, /* [in] Original comment text ONLY, may be NULL. */ + const char *zLine, /* [in] The comment line to print. */ + int *pCharCnt, /* [in/out] Pointer to the line character count. */ + int *pLineCnt /* [in/out] Pointer to the total line count. */ +){ + if( zOrigText && fossil_strcmp(zLine, zOrigText)==0 ){ + if( pCharCnt ) *pCharCnt = 0; + if( pLineCnt ) (*pLineCnt)++; + return 1; + } + return 0; +} + +/* +** This function scans the specified comment line starting just after the +** initial index and returns the index of the next spacing character -OR- +** zero if such a character cannot be found. For the purposes of this +** algorithm, the NUL character is treated the same as a spacing character. +*/ +static int comment_next_space( + const char *zLine, /* [in] The comment line being printed. */ + int index, /* [in] The current character index being handled. */ + int maxChars, /* [in] Optimization hint to abort before space found. */ + int *sumWidth /* [out] Summated width of all characters to next space. */ +){ + int cchUTF8, utf32, wcwidth = 0; + int nextIndex = index; + if( zLine[index]==0 ) return index; + for(;;){ + char_info_utf8(&zLine[nextIndex],&cchUTF8,&utf32); + nextIndex += cchUTF8; + wcwidth += cli_wcwidth(utf32); + if( zLine[nextIndex]==0 || fossil_isspace(zLine[nextIndex]) || + wcwidth>maxChars ){ + *sumWidth = wcwidth; + return nextIndex; + } + } + return 0; /* NOT REACHED */ +} + +/* +** Return information about the next (single- or multi-byte) character in +** z[0]. Two values are computed: +** +** * The number of bytes needed to represent the character. +** * The UTF code point value. +** +** Incomplete, ill-formed and overlong sequences are consumed together as +** one invalid code point. The invalid lead bytes 0xC0 to 0xC1 and 0xF5 to +** 0xF7 are allowed to initiate (ill-formed) 2- and 4-byte sequences, +** respectively, the other invalid lead bytes 0xF8 to 0xFF are treated +** as invalid 1-byte sequences (as lone trail bytes), all resulting +** in one invalid code point. Invalid UTF-8 sequences encoding a +** non-scalar code point (UTF-16 surrogates U+D800 to U+DFFF) are allowed. +** +** ANSI escape sequences of the form "\033[...X" are interpreted as a +** zero-width character. +*/ +void char_info_utf8( + const char *z, /* The character to be analyzed */ + int *pCchUTF8, /* OUT: The number of bytes used by this character */ + int *pUtf32 /* OUT: The UTF8 code point (used to determine width) */ +){ + int i = 0; /* Counted bytes. */ + int cchUTF8 = 1; /* Code units consumed. */ + int maxUTF8 = 1; /* Expected sequence length. */ + char c = z[i++]; + if( c==0x1b && z[i]=='[' ){ + i++; + while( z[i]>=0x30 && z[i]<=0x3f ){ i++; } + while( z[i]>=0x20 && z[i]<=0x2f ){ i++; } + if( z[i]>=0x40 && z[i]<=0x7e ){ + *pCchUTF8 = i+1; + *pUtf32 = 0x301; /* A zero-width character */ + return; + } + } + if( (c&0x80)==0x00 ){ /* 7-bit ASCII character. */ + *pCchUTF8 = 1; + *pUtf32 = (int)z[0]; + return; + } + else if( (c&0xe0)==0xc0 ) maxUTF8 = 2; /* UTF-8 lead byte 110vvvvv */ + else if( (c&0xf0)==0xe0 ) maxUTF8 = 3; /* UTF-8 lead byte 1110vvvv */ + else if( (c&0xf8)==0xf0 ) maxUTF8 = 4; /* UTF-8 lead byte 11110vvv */ + while( cchUTF8<maxUTF8 && + (z[i]&0xc0)==0x80 ){ /* UTF-8 trail byte 10vvvvvv */ + cchUTF8++; + i++; + } + *pCchUTF8 = cchUTF8; + if( cchUTF8!=maxUTF8 || /* Incomplete UTF-8 sequence. */ + ( cchUTF8==1 && (c&0x80)==0x80 )){ /* Lone UTF-8 trail byte. */ + *pUtf32 = 0xfffd; /* U+FFFD Replacement Character */ +#ifdef FOSSIL_DEBUG + assert( *pUtf32!=0xfffd ); /* Invalid UTF-8 sequence. */ +#endif + return; + } + switch( cchUTF8 ){ + case 4: + *pUtf32 = + ( (z[0] & 0x0f)<<18 ) | + ( (z[1] & 0x3f)<<12 ) | + ( (z[2] & 0x3f)<< 6 ) | + ( (z[3] & 0x3f)<< 0 ) ; + break; + case 3: + *pUtf32 = + ( (z[0] & 0x0f)<<12 ) | + ( (z[1] & 0x3f)<< 6 ) | + ( (z[2] & 0x3f)<< 0 ) ; + break; + case 2: + *pUtf32 = + ( (z[0] & 0x1f)<< 6 ) | + ( (z[1] & 0x3f)<< 0 ) ; + break; + default: + *pUtf32 = 0xfffd; /* U+FFFD Replacement Character */ + break; + } +#ifdef FOSSIL_DEBUG + assert( + *pUtf32>=0 && *pUtf32<=0x10ffff && /* Valid range U+0000 to U+10FFFF. */ + *pUtf32<0xd800 && *pUtf32>0xdfff /* Non-scalar (UTF-16 surrogates). */ + ); +#endif +} + +/* +** This function is called when printing a logical comment line to calculate +** the necessary indenting. The caller needs to emit the indenting spaces. +*/ +static void comment_calc_indent( + const char *zLine, /* [in] The comment line being printed. */ + int indent, /* [in] Number of spaces to indent, zero for none. */ + int trimCrLf, /* [in] Non-zero to trim leading/trailing CR/LF. */ + int trimSpace, /* [in] Non-zero to trim leading/trailing spaces. */ + int *piIndex /* [in/out] Pointer to first non-space character. */ +){ + if( zLine && piIndex ){ + int index = *piIndex; + if( trimCrLf ){ + while( zLine[index]=='\r' || zLine[index]=='\n' ){ index++; } + } + if( trimSpace ){ + while( fossil_isspace(zLine[index]) ){ index++; } + } + *piIndex = index; + } +} + +/* +** This function prints one logical line of a comment, stopping when it hits +** a new line -OR- runs out of space on the logical line. +*/ +static void comment_print_line( + const char *zOrigText, /* [in] Original comment text ONLY, may be NULL. */ + const char *zLine, /* [in] The comment line to print. */ + int origIndent, /* [in] Number of spaces to indent before the original + ** comment. */ + int indent, /* [in] Number of spaces to indent, before the line + ** to print. */ + int lineChars, /* [in] Maximum number of characters to print. */ + int trimCrLf, /* [in] Non-zero to trim leading/trailing CR/LF. */ + int trimSpace, /* [in] Non-zero to trim leading/trailing spaces. */ + int wordBreak, /* [in] Non-zero to try breaking on word boundaries. */ + int origBreak, /* [in] Non-zero to break before original comment. */ + int *pLineCnt, /* [in/out] Pointer to the total line count. */ + const char **pzLine /* [out] Pointer to the end of the logical line. */ +){ + int index = 0, charCnt = 0, lineCnt = 0, maxChars, i; + char zBuf[400]; int iBuf=0; /* Output buffer and counter. */ + if( !zLine ) return; + if( lineChars<=0 ) return; +#if 0 + assert( indent<sizeof(zBuf)-5 ); /* See following comments to explain */ + assert( origIndent<sizeof(zBuf)-5 ); /* these limits. */ +#endif + if( indent>(int)sizeof(zBuf)-6 ){ + /* Limit initial indent to fit output buffer. */ + indent = sizeof(zBuf)-6; + } + comment_calc_indent(zLine, indent, trimCrLf, trimSpace, &index); + if( indent>0 ){ + for(i=0; i<indent; i++){ + zBuf[iBuf++] = ' '; + } + } + if( origIndent>(int)sizeof(zBuf)-6 ){ + /* Limit line indent to fit output buffer. */ + origIndent = sizeof(zBuf)-6; + } + maxChars = lineChars; + for(;;){ + int cchUTF8, utf32; + int useChars = 1; + char c = zLine[index]; + /* Flush the output buffer if there's no space left for at least one more + ** (potentially 4-byte) UTF-8 sequence, one level of indentation spaces, + ** a new line, and a terminating NULL. */ + if( iBuf>(int)sizeof(zBuf)-origIndent-6 ){ + zBuf[iBuf]=0; + iBuf=0; + fossil_print("%s", zBuf); + } + if( c==0 ){ + break; + }else{ + if( origBreak && index>0 ){ + const char *zCurrent = &zLine[index]; + if( comment_check_orig(zOrigText, zCurrent, &charCnt, &lineCnt) ){ + zBuf[iBuf++] = '\n'; + comment_calc_indent(zLine, origIndent, trimCrLf, trimSpace, &index); + for( i=0; i<origIndent; i++ ){ + zBuf[iBuf++] = ' '; + } + maxChars = lineChars; + } + } + index++; + } + if( c=='\n' ){ + lineCnt++; + charCnt = 0; + useChars = 0; + }else if( c=='\t' ){ + int sumWidth; + int nextIndex = comment_next_space(zLine, index, maxChars, &sumWidth); + if( nextIndex<=0 || sumWidth>maxChars ){ + break; + } + charCnt++; + useChars = COMMENT_TAB_WIDTH; + if( maxChars<useChars ){ + zBuf[iBuf++] = ' '; + break; + } + }else if( wordBreak && fossil_isspace(c) ){ + int sumWidth; + int nextIndex = comment_next_space(zLine, index, maxChars, &sumWidth); + if( nextIndex<=0 || sumWidth>=maxChars ){ + break; + } + charCnt++; + }else{ + charCnt++; + } + assert( c!='\n' || charCnt==0 ); + zBuf[iBuf++] = c; + char_info_utf8(&zLine[index-1],&cchUTF8,&utf32); + if( cchUTF8>1 ){ + int wcwidth; + wcwidth = cli_wcwidth(utf32); + if( wcwidth>maxChars && lineChars>=wcwidth ){ /* rollback */ + index--; + iBuf--; + zBuf[iBuf] = 0; + break; + } + for( ; cchUTF8>1; cchUTF8-- ){ + zBuf[iBuf++] = zLine[index++]; + } + useChars += wcwidth - 1; + } + maxChars -= useChars; + if( maxChars<=0 ) break; + if( c=='\n' ) break; + } + if( charCnt>0 ){ + zBuf[iBuf++] = '\n'; + lineCnt++; + } + /* Flush the remaining output buffer. */ + if( iBuf>0 ){ + zBuf[iBuf]=0; + iBuf=0; + fossil_print("%s", zBuf); + } + if( pLineCnt ){ + *pLineCnt += lineCnt; + } + if( pzLine ){ + *pzLine = zLine + index; + } +} + +/* +** This is the canonical comment printing algorithm. This is the algorithm +** that is recommended and that is used unless the administrator has made +** special arrangements to use a customized algorithm. +** +** Given a comment string, format that string for printing on a TTY. +** Assume that the output cursor is indent spaces from the left margin +** and that a single line can contain no more than 'width' characters. +** Indent all subsequent lines by 'indent'. +** +** Formatting features: +** +** * Leading whitespace is removed. +** * Internal whitespace sequences are changed into a single space (0x20) +** character. +** * Lines are broken at a space, or at a hyphen ("-") whenever possible. +** +** Returns the number of new lines emitted. +*/ +static int comment_print_canonical( + const char *zText, /* The comment text to be printed. */ + int indent, /* Number of spaces to indent each non-initial line. */ + int width /* Maximum number of characters per line. */ +){ + int maxChars = width - indent; + int si, sk, i, k, kc; + int doIndent = 0; + char *zBuf; + char zBuffer[400]; + int lineCnt = 0; + + if( width<0 ){ + comment_set_maxchars(indent, &maxChars); + } + if( zText==0 ) zText = "(NULL)"; + if( maxChars<=0 ){ + maxChars = strlen(zText); + } + /* Ensure the buffer can hold the longest-possible UTF-8 sequences. */ + if( maxChars >= ((int)sizeof(zBuffer)/4-1) ){ + zBuf = fossil_malloc(maxChars*4+1); + }else{ + zBuf = zBuffer; + } + for(;;){ + while( fossil_isspace(zText[0]) ){ zText++; } + if( zText[0]==0 ){ + if( doIndent==0 ){ + fossil_print("\n"); + lineCnt = 1; + } + if( zBuf!=zBuffer) fossil_free(zBuf); + return lineCnt; + } + for(sk=si=i=k=kc=0; zText[i] && kc<maxChars; i++){ + int cchUTF8, utf32; + char c = zText[i]; + kc++; /* Count complete UTF-8 sequences. */ + char_info_utf8(&zText[i],&cchUTF8,&utf32); + if( cchUTF8>1 ){ + int wcwidth; + wcwidth = cli_wcwidth(utf32); + if( kc+wcwidth-1>maxChars && maxChars>=wcwidth ){ /* rollback */ + kc--; + break; + } + for( i--; cchUTF8>0; cchUTF8-- ){ + zBuf[k++] = zText[++i]; + } + kc += wcwidth - 1; + } + else if( fossil_isspace(c) ){ + si = i; + sk = k; + if( k==0 || zBuf[k-1]!=' ' ){ + zBuf[k++] = ' '; + } + }else{ + zBuf[k] = c; + if( c=='-' && k>0 && fossil_isalpha(zBuf[k-1]) ){ + si = i+1; + sk = k+1; + } + k++; + } + } + if( doIndent ){ + fossil_print("%*s", indent, ""); + } + doIndent = 1; + if( sk>0 && zText[i] ){ + zText += si; + zBuf[sk] = 0; + }else{ + zText += i; + zBuf[k] = 0; + } + fossil_print("%s\n", zBuf); + lineCnt++; + } +} + +/* +** This is the comment printing function. The comment printing algorithm +** contained within it attempts to preserve the formatting present within +** the comment string itself while honoring line width limitations. There +** are several flags that modify the default behavior of this function: +** +** COMMENT_PRINT_CANONICAL: Use the canonical printing algorithm: +** * Omit leading and trailing whitespace +** * Collapse internal whitespace into a +** single space (0x20) character. +** * Attempt to break lines at whitespace +** or hyphens. +** This is the recommended algorithm and is +** used in most cases. +** +** COMMENT_PRINT_TRIM_CRLF: Trims leading and trailing carriage-returns +** and line-feeds where they do not materially +** impact pre-existing formatting (i.e. at the +** start of the comment string -AND- right +** before line indentation). This flag does +** not apply to the legacy comment printing +** algorithm. This flag may be combined with +** COMMENT_PRINT_TRIM_SPACE. +** +** COMMENT_PRINT_TRIM_SPACE: Trims leading and trailing spaces where they +** do not materially impact the pre-existing +** formatting (i.e. at the start of the comment +** string -AND- right before line indentation). +** This flag does not apply to the legacy +** comment printing algorithm. This flag may +** be combined with COMMENT_PRINT_TRIM_CRLF. +** +** COMMENT_PRINT_WORD_BREAK: Attempts to break lines on word boundaries +** while honoring the logical line length. +** If this flag is not specified, honoring the +** logical line length may result in breaking +** lines in the middle of words. This flag +** does not apply to the legacy comment +** printing algorithm. +** +** COMMENT_PRINT_ORIG_BREAK: Looks for the original comment text within +** the text being printed. Upon matching, a +** new line will be emitted, thus preserving +** more of the pre-existing formatting. +** +** Given a comment string, format that string for printing on a TTY. +** Assume that the output cursors is indent spaces from the left margin +** and that a single line can contain no more than 'width' characters. +** Indent all subsequent lines by 'indent'. +** +** Returns the number of new lines emitted. +*/ +int comment_print( + const char *zText, /* The comment text to be printed. */ + const char *zOrigText, /* Original comment text ONLY, may be NULL. */ + int indent, /* Spaces to indent each non-initial line. */ + int width, /* Maximum number of characters per line. */ + int flags /* Zero or more "COMMENT_PRINT_*" flags. */ +){ + int maxChars = width - indent; + + if( flags & COMMENT_PRINT_CANONICAL ){ + /* Use the canonical algorithm. This is what happens in almost + ** all cases. */ + return comment_print_canonical(zText, indent, width); + }else{ + /* The remaining is a more complex formatting algorithm that is very + ** seldom used and is considered deprecated. + */ + int trimCrLf = flags & COMMENT_PRINT_TRIM_CRLF; + int trimSpace = flags & COMMENT_PRINT_TRIM_SPACE; + int wordBreak = flags & COMMENT_PRINT_WORD_BREAK; + int origBreak = flags & COMMENT_PRINT_ORIG_BREAK; + int lineCnt = 0; + const char *zLine; + + if( width<0 ){ + comment_set_maxchars(indent, &maxChars); + } + if( zText==0 ) zText = "(NULL)"; + if( maxChars<=0 ){ + maxChars = strlen(zText); + } + if( trimSpace ){ + while( fossil_isspace(zText[0]) ){ zText++; } + } + if( zText[0]==0 ){ + fossil_print("\n"); + lineCnt++; + return lineCnt; + } + zLine = zText; + for(;;){ + comment_print_line(zOrigText, zLine, indent, zLine>zText ? indent : 0, + maxChars, trimCrLf, trimSpace, wordBreak, origBreak, + &lineCnt, &zLine); + if( zLine==0 ) break; + while( fossil_isspace(zLine[0]) ) zLine++; + if( zLine[0]==0 ) break; + } + return lineCnt; + } +} + +/* +** Return the "COMMENT_PRINT_*" flags specified by the following sources, +** evaluated in the following cascading order: +** +** 1. The local (per-repository) "comment-format" setting. +** 2. The global (all-repositories) "comment-format" setting. +** 3. The default value COMMENT_PRINT_DEFAULT. +*/ +int get_comment_format(){ + int comFmtFlags; + + /* We must cache this result, else running the timeline can end up + ** querying the comment-format setting from the global db once per + ** timeline entry, which brings it to a crawl if that db is + ** network-mounted. Discussed in: + ** https://fossil-scm.org/forum/forumpost/9aaefe4e536e01bf */ + + /* The global command-line option is present, or the value has been cached. */ + if( g.comFmtFlags!=COMMENT_PRINT_UNSET ){ + return g.comFmtFlags; + } + /* Load the local (per-repository) or global (all-repositories) value, and use + ** g.comFmtFlags as a cache. */ + comFmtFlags = db_get_int("comment-format", COMMENT_PRINT_UNSET); + if( comFmtFlags!=COMMENT_PRINT_UNSET ){ + g.comFmtFlags = comFmtFlags; + return comFmtFlags; + } + /* Fallback to the default value. */ + g.comFmtFlags = COMMENT_PRINT_DEFAULT; + return g.comFmtFlags; +} + +/* +** +** COMMAND: test-comment-format +** +** Usage: %fossil test-comment-format [OPTIONS] TEXT [PREFIX] [ORIGTEXT] +** +** Test comment formatting and printing. Use for testing only. +** +** The default (canonical) formatting algorithm is: +** +** * Omit leading/trailing whitespace +** * Collapse internal whitespace into a single space character. +** * Attempt to break lines at whitespace or at a hyphen. +** +** Use --whitespace, --origbreak, --trimcrlf, --trimspace, +** and/or --wordbreak to disable the canonical processing and do +** the special processing specified by those other options. +** +** Options: +** --decode Decode the text using the same method used when +** handling the value of a C-card from a manifest. +** --file FILE Omit the TEXT argument and read the comment text +** from FILE. +** --indent Number of spaces to indent (default (-1) is to +** auto-detect). Zero means no indent. +** --orig FILE Take the value for the ORIGTEXT argument from FILE. +** --origbreak Attempt to break when the original comment text +** is detected. +** --trimcrlf Enable trimming of leading/trailing CR/LF. +** --trimspace Enable trimming of leading/trailing spaces. +** --whitespace Keep all internal whitespace. +** --wordbreak Attempt to break lines on word boundaries. +** -W|--width NUM Width of lines (default (-1) is to auto-detect). +** Zero means no limit. +*/ +void test_comment_format(void){ + const char *zWidth; + const char *zIndent; + const char *zPrefix = 0; + char *zText = 0; + char *zOrigText = 0; + int indent, width; + int i; + const char *fromFile = find_option("file", 0, 1); + int decode = find_option("decode", 0, 0)!=0; + int flags = COMMENT_PRINT_CANONICAL; + const char *fromOrig = find_option("orig", 0, 1); + if( find_option("whitespace",0,0) ){ + flags = 0; + } + if( find_option("trimcrlf", 0, 0) ){ + flags = COMMENT_PRINT_TRIM_CRLF; + } + if( find_option("trimspace", 0, 0) ){ + flags |= COMMENT_PRINT_TRIM_SPACE; + flags &= COMMENT_PRINT_CANONICAL; + } + if( find_option("wordbreak", 0, 0) ){ + flags |= COMMENT_PRINT_WORD_BREAK; + flags &= COMMENT_PRINT_CANONICAL; + } + if( find_option("origbreak", 0, 0) ){ + flags |= COMMENT_PRINT_ORIG_BREAK; + flags &= COMMENT_PRINT_CANONICAL; + } + zWidth = find_option("width","W",1); + if( zWidth ){ + width = atoi(zWidth); + }else{ + width = -1; /* automatic */ + } + zIndent = find_option("indent",0,1); + if( zIndent ){ + indent = atoi(zIndent); + }else{ + indent = -1; /* automatic */ + } + verify_all_options(); + zPrefix = zText = zOrigText = 0; + if( fromFile ){ + Blob fileData; + blob_read_from_file(&fileData, fromFile, ExtFILE); + zText = fossil_strdup(blob_str(&fileData)); + blob_reset(&fileData); + } + if( fromOrig ){ + Blob fileData; + blob_read_from_file(&fileData, fromOrig, ExtFILE); + zOrigText = fossil_strdup(blob_str(&fileData)); + blob_reset(&fileData); + } + for(i=2; i<g.argc; i++){ + if( zText==0 ){ + zText = g.argv[i]; + continue; + } + if( zPrefix==0 ){ + zPrefix = g.argv[i]; + continue; + } + if( zOrigText==0 ){ + zOrigText = g.argv[i]; + continue; + } + usage("[OPTIONS] TEXT [PREFIX] [ORIGTEXT]"); + } + if( decode ){ + zText = mprintf(fromFile?"%z":"%s" /*works-like:"%s"*/, zText); + defossilize(zText); + if( zOrigText ){ + zOrigText = mprintf(fromFile?"%z":"%s" /*works-like:"%s"*/, zOrigText); + defossilize(zOrigText); + } + } + if( zPrefix==0 ) zPrefix = "00:00:00 "; + if( indent<0 ){ + indent = strlen(zPrefix); + } + if( zPrefix && *zPrefix ){ + fossil_print("%s", zPrefix); + } + fossil_print("(%d lines output)\n", + comment_print(zText, zOrigText, indent, width, flags)); +} |
