Fossil SCM

fossil-scm / src / comformat.c
Blame History Raw 856 lines
1
/*
2
** Copyright (c) 2007 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
**
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code used to format and print comments or other
19
** text on a TTY.
20
*/
21
#include "config.h"
22
#include "comformat.h"
23
#include <assert.h>
24
25
#if INTERFACE
26
#define COMMENT_PRINT_NONE ((u32)0x00000000) /* No flags */
27
#define COMMENT_PRINT_CANONICAL ((u32)0x00000001) /* Use canonical algorithm */
28
#define COMMENT_PRINT_DEFAULT COMMENT_PRINT_CANONICAL /* Default */
29
#define COMMENT_PRINT_UNSET (-1) /* Not initialized */
30
31
/* The canonical comment printing algorithm is recommended. We make
32
** no promise of on-going support for any of the following flags:
33
*/
34
#define COMMENT_PRINT_TRIM_CRLF ((u32)0x00000002) /* Trim leading CR/LF. */
35
#define COMMENT_PRINT_TRIM_SPACE ((u32)0x00000004) /* Trim leading/trailing. */
36
#define COMMENT_PRINT_WORD_BREAK ((u32)0x00000008) /* Break lines on words. */
37
#define COMMENT_PRINT_ORIG_BREAK ((u32)0x00000010) /* Break before original. */
38
#define COMMENT_PRINT_SUMMARY ((u32)0x00000020)
39
/* Truncate after first blank line. */
40
#endif
41
42
/********* Code copied from SQLite src/shell.c.in on 2024-09-30 **********/
43
/* Lookup table to estimate the number of columns consumed by a Unicode
44
** character.
45
*/
46
static const struct {
47
unsigned char w; /* Width of the character in columns */
48
int iFirst; /* First character in a span having this width */
49
} aUWidth[] = {
50
/* {1, 0x00000}, */
51
{0, 0x00300}, {1, 0x00370}, {0, 0x00483}, {1, 0x00487}, {0, 0x00488},
52
{1, 0x0048a}, {0, 0x00591}, {1, 0x005be}, {0, 0x005bf}, {1, 0x005c0},
53
{0, 0x005c1}, {1, 0x005c3}, {0, 0x005c4}, {1, 0x005c6}, {0, 0x005c7},
54
{1, 0x005c8}, {0, 0x00600}, {1, 0x00604}, {0, 0x00610}, {1, 0x00616},
55
{0, 0x0064b}, {1, 0x0065f}, {0, 0x00670}, {1, 0x00671}, {0, 0x006d6},
56
{1, 0x006e5}, {0, 0x006e7}, {1, 0x006e9}, {0, 0x006ea}, {1, 0x006ee},
57
{0, 0x0070f}, {1, 0x00710}, {0, 0x00711}, {1, 0x00712}, {0, 0x00730},
58
{1, 0x0074b}, {0, 0x007a6}, {1, 0x007b1}, {0, 0x007eb}, {1, 0x007f4},
59
{0, 0x00901}, {1, 0x00903}, {0, 0x0093c}, {1, 0x0093d}, {0, 0x00941},
60
{1, 0x00949}, {0, 0x0094d}, {1, 0x0094e}, {0, 0x00951}, {1, 0x00955},
61
{0, 0x00962}, {1, 0x00964}, {0, 0x00981}, {1, 0x00982}, {0, 0x009bc},
62
{1, 0x009bd}, {0, 0x009c1}, {1, 0x009c5}, {0, 0x009cd}, {1, 0x009ce},
63
{0, 0x009e2}, {1, 0x009e4}, {0, 0x00a01}, {1, 0x00a03}, {0, 0x00a3c},
64
{1, 0x00a3d}, {0, 0x00a41}, {1, 0x00a43}, {0, 0x00a47}, {1, 0x00a49},
65
{0, 0x00a4b}, {1, 0x00a4e}, {0, 0x00a70}, {1, 0x00a72}, {0, 0x00a81},
66
{1, 0x00a83}, {0, 0x00abc}, {1, 0x00abd}, {0, 0x00ac1}, {1, 0x00ac6},
67
{0, 0x00ac7}, {1, 0x00ac9}, {0, 0x00acd}, {1, 0x00ace}, {0, 0x00ae2},
68
{1, 0x00ae4}, {0, 0x00b01}, {1, 0x00b02}, {0, 0x00b3c}, {1, 0x00b3d},
69
{0, 0x00b3f}, {1, 0x00b40}, {0, 0x00b41}, {1, 0x00b44}, {0, 0x00b4d},
70
{1, 0x00b4e}, {0, 0x00b56}, {1, 0x00b57}, {0, 0x00b82}, {1, 0x00b83},
71
{0, 0x00bc0}, {1, 0x00bc1}, {0, 0x00bcd}, {1, 0x00bce}, {0, 0x00c3e},
72
{1, 0x00c41}, {0, 0x00c46}, {1, 0x00c49}, {0, 0x00c4a}, {1, 0x00c4e},
73
{0, 0x00c55}, {1, 0x00c57}, {0, 0x00cbc}, {1, 0x00cbd}, {0, 0x00cbf},
74
{1, 0x00cc0}, {0, 0x00cc6}, {1, 0x00cc7}, {0, 0x00ccc}, {1, 0x00cce},
75
{0, 0x00ce2}, {1, 0x00ce4}, {0, 0x00d41}, {1, 0x00d44}, {0, 0x00d4d},
76
{1, 0x00d4e}, {0, 0x00dca}, {1, 0x00dcb}, {0, 0x00dd2}, {1, 0x00dd5},
77
{0, 0x00dd6}, {1, 0x00dd7}, {0, 0x00e31}, {1, 0x00e32}, {0, 0x00e34},
78
{1, 0x00e3b}, {0, 0x00e47}, {1, 0x00e4f}, {0, 0x00eb1}, {1, 0x00eb2},
79
{0, 0x00eb4}, {1, 0x00eba}, {0, 0x00ebb}, {1, 0x00ebd}, {0, 0x00ec8},
80
{1, 0x00ece}, {0, 0x00f18}, {1, 0x00f1a}, {0, 0x00f35}, {1, 0x00f36},
81
{0, 0x00f37}, {1, 0x00f38}, {0, 0x00f39}, {1, 0x00f3a}, {0, 0x00f71},
82
{1, 0x00f7f}, {0, 0x00f80}, {1, 0x00f85}, {0, 0x00f86}, {1, 0x00f88},
83
{0, 0x00f90}, {1, 0x00f98}, {0, 0x00f99}, {1, 0x00fbd}, {0, 0x00fc6},
84
{1, 0x00fc7}, {0, 0x0102d}, {1, 0x01031}, {0, 0x01032}, {1, 0x01033},
85
{0, 0x01036}, {1, 0x0103b}, {0, 0x01058},
86
{1, 0x0105a}, {2, 0x01100}, {0, 0x01160}, {1, 0x01200}, {0, 0x0135f},
87
{1, 0x01360}, {0, 0x01712}, {1, 0x01715}, {0, 0x01732}, {1, 0x01735},
88
{0, 0x01752}, {1, 0x01754}, {0, 0x01772}, {1, 0x01774}, {0, 0x017b4},
89
{1, 0x017b6}, {0, 0x017b7}, {1, 0x017be}, {0, 0x017c6}, {1, 0x017c7},
90
{0, 0x017c9}, {1, 0x017d4}, {0, 0x017dd}, {1, 0x017de}, {0, 0x0180b},
91
{1, 0x0180e}, {0, 0x018a9}, {1, 0x018aa}, {0, 0x01920}, {1, 0x01923},
92
{0, 0x01927}, {1, 0x01929}, {0, 0x01932}, {1, 0x01933}, {0, 0x01939},
93
{1, 0x0193c}, {0, 0x01a17}, {1, 0x01a19}, {0, 0x01b00}, {1, 0x01b04},
94
{0, 0x01b34}, {1, 0x01b35}, {0, 0x01b36}, {1, 0x01b3b}, {0, 0x01b3c},
95
{1, 0x01b3d}, {0, 0x01b42}, {1, 0x01b43}, {0, 0x01b6b}, {1, 0x01b74},
96
{0, 0x01dc0}, {1, 0x01dcb}, {0, 0x01dfe}, {1, 0x01e00}, {0, 0x0200b},
97
{1, 0x02010}, {0, 0x0202a}, {1, 0x0202f}, {0, 0x02060}, {1, 0x02064},
98
{0, 0x0206a}, {1, 0x02070}, {0, 0x020d0}, {1, 0x020f0}, {2, 0x02329},
99
{1, 0x0232b}, {2, 0x02e80}, {0, 0x0302a}, {2, 0x03030}, {1, 0x0303f},
100
{2, 0x03040}, {0, 0x03099}, {2, 0x0309b}, {1, 0x0a4d0}, {0, 0x0a806},
101
{1, 0x0a807}, {0, 0x0a80b}, {1, 0x0a80c}, {0, 0x0a825}, {1, 0x0a827},
102
{2, 0x0ac00}, {1, 0x0d7a4}, {2, 0x0f900}, {1, 0x0fb00}, {0, 0x0fb1e},
103
{1, 0x0fb1f}, {0, 0x0fe00}, {2, 0x0fe10}, {1, 0x0fe1a}, {0, 0x0fe20},
104
{1, 0x0fe24}, {2, 0x0fe30}, {1, 0x0fe70}, {0, 0x0feff}, {2, 0x0ff00},
105
{1, 0x0ff61}, {2, 0x0ffe0}, {1, 0x0ffe7}, {0, 0x0fff9}, {1, 0x0fffc},
106
{0, 0x10a01}, {1, 0x10a04}, {0, 0x10a05}, {1, 0x10a07}, {0, 0x10a0c},
107
{1, 0x10a10}, {0, 0x10a38}, {1, 0x10a3b}, {0, 0x10a3f}, {1, 0x10a40},
108
{0, 0x1d167}, {1, 0x1d16a}, {0, 0x1d173}, {1, 0x1d183}, {0, 0x1d185},
109
{1, 0x1d18c}, {0, 0x1d1aa}, {1, 0x1d1ae}, {0, 0x1d242}, {1, 0x1d245},
110
{2, 0x20000}, {1, 0x2fffe}, {2, 0x30000}, {1, 0x3fffe}, {0, 0xe0001},
111
{1, 0xe0002}, {0, 0xe0020}, {1, 0xe0080}, {0, 0xe0100}, {1, 0xe01f0}
112
};
113
114
/*
115
** Return an estimate of the width, in columns, for the single Unicode
116
** character c. For normal characters, the answer is always 1. But the
117
** estimate might be 0 or 2 for zero-width and double-width characters.
118
**
119
** Different display devices display unicode using different widths. So
120
** it is impossible to know that true display width with 100% accuracy.
121
** Inaccuracies in the width estimates might cause columns to be misaligned.
122
** Unfortunately, there is nothing we can do about that.
123
*/
124
static int cli_wcwidth(int c){
125
int iFirst, iLast;
126
127
/* Fast path for common characters */
128
if( c<0x300 ) return 1;
129
130
/* The general case */
131
iFirst = 0;
132
iLast = sizeof(aUWidth)/sizeof(aUWidth[0]) - 1;
133
while( iFirst<iLast-1 ){
134
int iMid = (iFirst+iLast)/2;
135
int cMid = aUWidth[iMid].iFirst;
136
if( cMid < c ){
137
iFirst = iMid;
138
}else if( cMid > c ){
139
iLast = iMid - 1;
140
}else{
141
return aUWidth[iMid].w;
142
}
143
}
144
if( aUWidth[iLast].iFirst > c ) return aUWidth[iFirst].w;
145
return aUWidth[iLast].w;
146
}
147
/******* End of code copied from SQLite *************************************/
148
149
/*
150
** This is the previous value used by most external callers when they
151
** needed to specify a default maximum line length to be used with the
152
** comment_print() function.
153
*/
154
#ifndef COMMENT_LEGACY_LINE_LENGTH
155
# define COMMENT_LEGACY_LINE_LENGTH (78)
156
#endif
157
158
/*
159
** This is the number of spaces to print when a tab character is seen.
160
*/
161
#ifndef COMMENT_TAB_WIDTH
162
# define COMMENT_TAB_WIDTH (8)
163
#endif
164
165
/*
166
** This function sets the maximum number of characters to print per line
167
** based on the detected terminal line width, if available; otherwise, it
168
** uses the legacy default terminal line width minus the amount to indent.
169
**
170
** Zero is returned to indicate any failure. One is returned to indicate
171
** the successful detection of the terminal line width. Negative one is
172
** returned to indicate the terminal line width is using the hard-coded
173
** legacy default value.
174
*/
175
static int comment_set_maxchars(
176
int indent,
177
int *pMaxChars
178
){
179
struct TerminalSize ts;
180
if ( !terminal_get_size(&ts) ){
181
return 0;
182
}
183
184
if( ts.nColumns ){
185
*pMaxChars = ts.nColumns - indent;
186
return 1;
187
}else{
188
/*
189
** Fallback to using more-or-less the "legacy semantics" of hard-coding
190
** the maximum line length to a value reasonable for the vast majority
191
** of supported systems.
192
*/
193
*pMaxChars = COMMENT_LEGACY_LINE_LENGTH - indent;
194
return -1;
195
}
196
}
197
198
/*
199
** This function checks the current line being printed against the original
200
** comment text. Upon matching, it updates the provided character and line
201
** counts, if applicable. The caller needs to emit a new line, if desired.
202
*/
203
static int comment_check_orig(
204
const char *zOrigText, /* [in] Original comment text ONLY, may be NULL. */
205
const char *zLine, /* [in] The comment line to print. */
206
int *pCharCnt, /* [in/out] Pointer to the line character count. */
207
int *pLineCnt /* [in/out] Pointer to the total line count. */
208
){
209
if( zOrigText && fossil_strcmp(zLine, zOrigText)==0 ){
210
if( pCharCnt ) *pCharCnt = 0;
211
if( pLineCnt ) (*pLineCnt)++;
212
return 1;
213
}
214
return 0;
215
}
216
217
/*
218
** This function scans the specified comment line starting just after the
219
** initial index and returns the index of the next spacing character -OR-
220
** zero if such a character cannot be found. For the purposes of this
221
** algorithm, the NUL character is treated the same as a spacing character.
222
*/
223
static int comment_next_space(
224
const char *zLine, /* [in] The comment line being printed. */
225
int index, /* [in] The current character index being handled. */
226
int maxChars, /* [in] Optimization hint to abort before space found. */
227
int *sumWidth /* [out] Summated width of all characters to next space. */
228
){
229
int cchUTF8, utf32, wcwidth = 0;
230
int nextIndex = index;
231
if( zLine[index]==0 ) return index;
232
for(;;){
233
char_info_utf8(&zLine[nextIndex],&cchUTF8,&utf32);
234
nextIndex += cchUTF8;
235
wcwidth += cli_wcwidth(utf32);
236
if( zLine[nextIndex]==0 || fossil_isspace(zLine[nextIndex]) ||
237
wcwidth>maxChars ){
238
*sumWidth = wcwidth;
239
return nextIndex;
240
}
241
}
242
return 0; /* NOT REACHED */
243
}
244
245
/*
246
** Return information about the next (single- or multi-byte) character in
247
** z[0]. Two values are computed:
248
**
249
** * The number of bytes needed to represent the character.
250
** * The UTF code point value.
251
**
252
** Incomplete, ill-formed and overlong sequences are consumed together as
253
** one invalid code point. The invalid lead bytes 0xC0 to 0xC1 and 0xF5 to
254
** 0xF7 are allowed to initiate (ill-formed) 2- and 4-byte sequences,
255
** respectively, the other invalid lead bytes 0xF8 to 0xFF are treated
256
** as invalid 1-byte sequences (as lone trail bytes), all resulting
257
** in one invalid code point. Invalid UTF-8 sequences encoding a
258
** non-scalar code point (UTF-16 surrogates U+D800 to U+DFFF) are allowed.
259
**
260
** ANSI escape sequences of the form "\033[...X" are interpreted as a
261
** zero-width character.
262
*/
263
void char_info_utf8(
264
const char *z, /* The character to be analyzed */
265
int *pCchUTF8, /* OUT: The number of bytes used by this character */
266
int *pUtf32 /* OUT: The UTF8 code point (used to determine width) */
267
){
268
int i = 0; /* Counted bytes. */
269
int cchUTF8 = 1; /* Code units consumed. */
270
int maxUTF8 = 1; /* Expected sequence length. */
271
char c = z[i++];
272
if( c==0x1b && z[i]=='[' ){
273
i++;
274
while( z[i]>=0x30 && z[i]<=0x3f ){ i++; }
275
while( z[i]>=0x20 && z[i]<=0x2f ){ i++; }
276
if( z[i]>=0x40 && z[i]<=0x7e ){
277
*pCchUTF8 = i+1;
278
*pUtf32 = 0x301; /* A zero-width character */
279
return;
280
}
281
}
282
if( (c&0x80)==0x00 ){ /* 7-bit ASCII character. */
283
*pCchUTF8 = 1;
284
*pUtf32 = (int)z[0];
285
return;
286
}
287
else if( (c&0xe0)==0xc0 ) maxUTF8 = 2; /* UTF-8 lead byte 110vvvvv */
288
else if( (c&0xf0)==0xe0 ) maxUTF8 = 3; /* UTF-8 lead byte 1110vvvv */
289
else if( (c&0xf8)==0xf0 ) maxUTF8 = 4; /* UTF-8 lead byte 11110vvv */
290
while( cchUTF8<maxUTF8 &&
291
(z[i]&0xc0)==0x80 ){ /* UTF-8 trail byte 10vvvvvv */
292
cchUTF8++;
293
i++;
294
}
295
*pCchUTF8 = cchUTF8;
296
if( cchUTF8!=maxUTF8 || /* Incomplete UTF-8 sequence. */
297
( cchUTF8==1 && (c&0x80)==0x80 )){ /* Lone UTF-8 trail byte. */
298
*pUtf32 = 0xfffd; /* U+FFFD Replacement Character */
299
#ifdef FOSSIL_DEBUG
300
assert( *pUtf32!=0xfffd ); /* Invalid UTF-8 sequence. */
301
#endif
302
return;
303
}
304
switch( cchUTF8 ){
305
case 4:
306
*pUtf32 =
307
( (z[0] & 0x0f)<<18 ) |
308
( (z[1] & 0x3f)<<12 ) |
309
( (z[2] & 0x3f)<< 6 ) |
310
( (z[3] & 0x3f)<< 0 ) ;
311
break;
312
case 3:
313
*pUtf32 =
314
( (z[0] & 0x0f)<<12 ) |
315
( (z[1] & 0x3f)<< 6 ) |
316
( (z[2] & 0x3f)<< 0 ) ;
317
break;
318
case 2:
319
*pUtf32 =
320
( (z[0] & 0x1f)<< 6 ) |
321
( (z[1] & 0x3f)<< 0 ) ;
322
break;
323
default:
324
*pUtf32 = 0xfffd; /* U+FFFD Replacement Character */
325
break;
326
}
327
#ifdef FOSSIL_DEBUG
328
assert(
329
*pUtf32>=0 && *pUtf32<=0x10ffff && /* Valid range U+0000 to U+10FFFF. */
330
*pUtf32<0xd800 && *pUtf32>0xdfff /* Non-scalar (UTF-16 surrogates). */
331
);
332
#endif
333
}
334
335
/*
336
** This function is called when printing a logical comment line to calculate
337
** the necessary indenting. The caller needs to emit the indenting spaces.
338
*/
339
static void comment_calc_indent(
340
const char *zLine, /* [in] The comment line being printed. */
341
int indent, /* [in] Number of spaces to indent, zero for none. */
342
int trimCrLf, /* [in] Non-zero to trim leading/trailing CR/LF. */
343
int trimSpace, /* [in] Non-zero to trim leading/trailing spaces. */
344
int *piIndex /* [in/out] Pointer to first non-space character. */
345
){
346
if( zLine && piIndex ){
347
int index = *piIndex;
348
if( trimCrLf ){
349
while( zLine[index]=='\r' || zLine[index]=='\n' ){ index++; }
350
}
351
if( trimSpace ){
352
while( fossil_isspace(zLine[index]) ){ index++; }
353
}
354
*piIndex = index;
355
}
356
}
357
358
/*
359
** This function prints one logical line of a comment, stopping when it hits
360
** a new line -OR- runs out of space on the logical line.
361
*/
362
static void comment_print_line(
363
const char *zOrigText, /* [in] Original comment text ONLY, may be NULL. */
364
const char *zLine, /* [in] The comment line to print. */
365
int origIndent, /* [in] Number of spaces to indent before the original
366
** comment. */
367
int indent, /* [in] Number of spaces to indent, before the line
368
** to print. */
369
int lineChars, /* [in] Maximum number of characters to print. */
370
int trimCrLf, /* [in] Non-zero to trim leading/trailing CR/LF. */
371
int trimSpace, /* [in] Non-zero to trim leading/trailing spaces. */
372
int wordBreak, /* [in] Non-zero to try breaking on word boundaries. */
373
int origBreak, /* [in] Non-zero to break before original comment. */
374
int *pLineCnt, /* [in/out] Pointer to the total line count. */
375
const char **pzLine /* [out] Pointer to the end of the logical line. */
376
){
377
int index = 0, charCnt = 0, lineCnt = 0, maxChars, i;
378
char zBuf[400]; int iBuf=0; /* Output buffer and counter. */
379
if( !zLine ) return;
380
if( lineChars<=0 ) return;
381
#if 0
382
assert( indent<sizeof(zBuf)-5 ); /* See following comments to explain */
383
assert( origIndent<sizeof(zBuf)-5 ); /* these limits. */
384
#endif
385
if( indent>(int)sizeof(zBuf)-6 ){
386
/* Limit initial indent to fit output buffer. */
387
indent = sizeof(zBuf)-6;
388
}
389
comment_calc_indent(zLine, indent, trimCrLf, trimSpace, &index);
390
if( indent>0 ){
391
for(i=0; i<indent; i++){
392
zBuf[iBuf++] = ' ';
393
}
394
}
395
if( origIndent>(int)sizeof(zBuf)-6 ){
396
/* Limit line indent to fit output buffer. */
397
origIndent = sizeof(zBuf)-6;
398
}
399
maxChars = lineChars;
400
for(;;){
401
int cchUTF8, utf32;
402
int useChars = 1;
403
char c = zLine[index];
404
/* Flush the output buffer if there's no space left for at least one more
405
** (potentially 4-byte) UTF-8 sequence, one level of indentation spaces,
406
** a new line, and a terminating NULL. */
407
if( iBuf>(int)sizeof(zBuf)-origIndent-6 ){
408
zBuf[iBuf]=0;
409
iBuf=0;
410
fossil_print("%s", zBuf);
411
}
412
if( c==0 ){
413
break;
414
}else{
415
if( origBreak && index>0 ){
416
const char *zCurrent = &zLine[index];
417
if( comment_check_orig(zOrigText, zCurrent, &charCnt, &lineCnt) ){
418
zBuf[iBuf++] = '\n';
419
comment_calc_indent(zLine, origIndent, trimCrLf, trimSpace, &index);
420
for( i=0; i<origIndent; i++ ){
421
zBuf[iBuf++] = ' ';
422
}
423
maxChars = lineChars;
424
}
425
}
426
index++;
427
}
428
if( c=='\n' ){
429
lineCnt++;
430
charCnt = 0;
431
useChars = 0;
432
}else if( c=='\t' ){
433
int sumWidth;
434
int nextIndex = comment_next_space(zLine, index, maxChars, &sumWidth);
435
if( nextIndex<=0 || sumWidth>maxChars ){
436
break;
437
}
438
charCnt++;
439
useChars = COMMENT_TAB_WIDTH;
440
if( maxChars<useChars ){
441
zBuf[iBuf++] = ' ';
442
break;
443
}
444
}else if( wordBreak && fossil_isspace(c) ){
445
int sumWidth;
446
int nextIndex = comment_next_space(zLine, index, maxChars, &sumWidth);
447
if( nextIndex<=0 || sumWidth>=maxChars ){
448
break;
449
}
450
charCnt++;
451
}else{
452
charCnt++;
453
}
454
assert( c!='\n' || charCnt==0 );
455
zBuf[iBuf++] = c;
456
char_info_utf8(&zLine[index-1],&cchUTF8,&utf32);
457
if( cchUTF8>1 ){
458
int wcwidth;
459
wcwidth = cli_wcwidth(utf32);
460
if( wcwidth>maxChars && lineChars>=wcwidth ){ /* rollback */
461
index--;
462
iBuf--;
463
zBuf[iBuf] = 0;
464
break;
465
}
466
for( ; cchUTF8>1; cchUTF8-- ){
467
zBuf[iBuf++] = zLine[index++];
468
}
469
useChars += wcwidth - 1;
470
}
471
maxChars -= useChars;
472
if( maxChars<=0 ) break;
473
if( c=='\n' ) break;
474
}
475
if( charCnt>0 ){
476
zBuf[iBuf++] = '\n';
477
lineCnt++;
478
}
479
/* Flush the remaining output buffer. */
480
if( iBuf>0 ){
481
zBuf[iBuf]=0;
482
iBuf=0;
483
fossil_print("%s", zBuf);
484
}
485
if( pLineCnt ){
486
*pLineCnt += lineCnt;
487
}
488
if( pzLine ){
489
*pzLine = zLine + index;
490
}
491
}
492
493
/*
494
** This is the canonical comment printing algorithm. This is the algorithm
495
** that is recommended and that is used unless the administrator has made
496
** special arrangements to use a customized algorithm.
497
**
498
** Given a comment string, format that string for printing on a TTY.
499
** Assume that the output cursor is indent spaces from the left margin
500
** and that a single line can contain no more than 'width' characters.
501
** Indent all subsequent lines by 'indent'.
502
**
503
** Formatting features:
504
**
505
** * Leading whitespace is removed.
506
** * Internal whitespace sequences are changed into a single space (0x20)
507
** character.
508
** * Lines are broken at a space, or at a hyphen ("-") whenever possible.
509
**
510
** Returns the number of new lines emitted.
511
*/
512
static int comment_print_canonical(
513
const char *zText, /* The comment text to be printed. */
514
int indent, /* Number of spaces to indent each non-initial line. */
515
int width /* Maximum number of characters per line. */
516
){
517
int maxChars = width - indent;
518
int si, sk, i, k, kc;
519
int doIndent = 0;
520
char *zBuf;
521
char zBuffer[400];
522
int lineCnt = 0;
523
524
if( width<0 ){
525
comment_set_maxchars(indent, &maxChars);
526
}
527
if( zText==0 ) zText = "(NULL)";
528
if( maxChars<=0 ){
529
maxChars = strlen(zText);
530
}
531
/* Ensure the buffer can hold the longest-possible UTF-8 sequences. */
532
if( maxChars >= ((int)sizeof(zBuffer)/4-1) ){
533
zBuf = fossil_malloc(maxChars*4+1);
534
}else{
535
zBuf = zBuffer;
536
}
537
for(;;){
538
while( fossil_isspace(zText[0]) ){ zText++; }
539
if( zText[0]==0 ){
540
if( doIndent==0 ){
541
fossil_print("\n");
542
lineCnt = 1;
543
}
544
if( zBuf!=zBuffer) fossil_free(zBuf);
545
return lineCnt;
546
}
547
for(sk=si=i=k=kc=0; zText[i] && kc<maxChars; i++){
548
int cchUTF8, utf32;
549
char c = zText[i];
550
kc++; /* Count complete UTF-8 sequences. */
551
char_info_utf8(&zText[i],&cchUTF8,&utf32);
552
if( cchUTF8>1 ){
553
int wcwidth;
554
wcwidth = cli_wcwidth(utf32);
555
if( kc+wcwidth-1>maxChars && maxChars>=wcwidth ){ /* rollback */
556
kc--;
557
break;
558
}
559
for( i--; cchUTF8>0; cchUTF8-- ){
560
zBuf[k++] = zText[++i];
561
}
562
kc += wcwidth - 1;
563
}
564
else if( fossil_isspace(c) ){
565
si = i;
566
sk = k;
567
if( k==0 || zBuf[k-1]!=' ' ){
568
zBuf[k++] = ' ';
569
}
570
}else{
571
zBuf[k] = c;
572
if( c=='-' && k>0 && fossil_isalpha(zBuf[k-1]) ){
573
si = i+1;
574
sk = k+1;
575
}
576
k++;
577
}
578
}
579
if( doIndent ){
580
fossil_print("%*s", indent, "");
581
}
582
doIndent = 1;
583
if( sk>0 && zText[i] ){
584
zText += si;
585
zBuf[sk] = 0;
586
}else{
587
zText += i;
588
zBuf[k] = 0;
589
}
590
fossil_print("%s\n", zBuf);
591
lineCnt++;
592
}
593
}
594
595
/*
596
** This is the comment printing function. The comment printing algorithm
597
** contained within it attempts to preserve the formatting present within
598
** the comment string itself while honoring line width limitations. There
599
** are several flags that modify the default behavior of this function:
600
**
601
** COMMENT_PRINT_CANONICAL: Use the canonical printing algorithm:
602
** * Omit leading and trailing whitespace
603
** * Collapse internal whitespace into a
604
** single space (0x20) character.
605
** * Attempt to break lines at whitespace
606
** or hyphens.
607
** This is the recommended algorithm and is
608
** used in most cases.
609
**
610
** COMMENT_PRINT_TRIM_CRLF: Trims leading and trailing carriage-returns
611
** and line-feeds where they do not materially
612
** impact pre-existing formatting (i.e. at the
613
** start of the comment string -AND- right
614
** before line indentation). This flag does
615
** not apply to the legacy comment printing
616
** algorithm. This flag may be combined with
617
** COMMENT_PRINT_TRIM_SPACE.
618
**
619
** COMMENT_PRINT_TRIM_SPACE: Trims leading and trailing spaces where they
620
** do not materially impact the pre-existing
621
** formatting (i.e. at the start of the comment
622
** string -AND- right before line indentation).
623
** This flag does not apply to the legacy
624
** comment printing algorithm. This flag may
625
** be combined with COMMENT_PRINT_TRIM_CRLF.
626
**
627
** COMMENT_PRINT_WORD_BREAK: Attempts to break lines on word boundaries
628
** while honoring the logical line length.
629
** If this flag is not specified, honoring the
630
** logical line length may result in breaking
631
** lines in the middle of words. This flag
632
** does not apply to the legacy comment
633
** printing algorithm.
634
**
635
** COMMENT_PRINT_ORIG_BREAK: Looks for the original comment text within
636
** the text being printed. Upon matching, a
637
** new line will be emitted, thus preserving
638
** more of the pre-existing formatting.
639
**
640
** Given a comment string, format that string for printing on a TTY.
641
** Assume that the output cursors is indent spaces from the left margin
642
** and that a single line can contain no more than 'width' characters.
643
** Indent all subsequent lines by 'indent'.
644
**
645
** Returns the number of new lines emitted.
646
*/
647
int comment_print(
648
const char *zText, /* The comment text to be printed. */
649
const char *zOrigText, /* Original comment text ONLY, may be NULL. */
650
int indent, /* Spaces to indent each non-initial line. */
651
int width, /* Maximum number of characters per line. */
652
int flags /* Zero or more "COMMENT_PRINT_*" flags. */
653
){
654
int maxChars = width - indent;
655
656
if( flags & COMMENT_PRINT_CANONICAL ){
657
/* Use the canonical algorithm. This is what happens in almost
658
** all cases. */
659
return comment_print_canonical(zText, indent, width);
660
}else{
661
/* The remaining is a more complex formatting algorithm that is very
662
** seldom used and is considered deprecated.
663
*/
664
int trimCrLf = flags & COMMENT_PRINT_TRIM_CRLF;
665
int trimSpace = flags & COMMENT_PRINT_TRIM_SPACE;
666
int wordBreak = flags & COMMENT_PRINT_WORD_BREAK;
667
int origBreak = flags & COMMENT_PRINT_ORIG_BREAK;
668
int lineCnt = 0;
669
const char *zLine;
670
671
if( width<0 ){
672
comment_set_maxchars(indent, &maxChars);
673
}
674
if( zText==0 ) zText = "(NULL)";
675
if( maxChars<=0 ){
676
maxChars = strlen(zText);
677
}
678
if( trimSpace ){
679
while( fossil_isspace(zText[0]) ){ zText++; }
680
}
681
if( zText[0]==0 ){
682
fossil_print("\n");
683
lineCnt++;
684
return lineCnt;
685
}
686
zLine = zText;
687
for(;;){
688
comment_print_line(zOrigText, zLine, indent, zLine>zText ? indent : 0,
689
maxChars, trimCrLf, trimSpace, wordBreak, origBreak,
690
&lineCnt, &zLine);
691
if( zLine==0 ) break;
692
while( fossil_isspace(zLine[0]) ) zLine++;
693
if( zLine[0]==0 ) break;
694
}
695
return lineCnt;
696
}
697
}
698
699
/*
700
** Return the "COMMENT_PRINT_*" flags specified by the following sources,
701
** evaluated in the following cascading order:
702
**
703
** 1. The local (per-repository) "comment-format" setting.
704
** 2. The global (all-repositories) "comment-format" setting.
705
** 3. The default value COMMENT_PRINT_DEFAULT.
706
*/
707
int get_comment_format(){
708
int comFmtFlags;
709
710
/* We must cache this result, else running the timeline can end up
711
** querying the comment-format setting from the global db once per
712
** timeline entry, which brings it to a crawl if that db is
713
** network-mounted. Discussed in:
714
** https://fossil-scm.org/forum/forumpost/9aaefe4e536e01bf */
715
716
/* The global command-line option is present, or the value has been cached. */
717
if( g.comFmtFlags!=COMMENT_PRINT_UNSET ){
718
return g.comFmtFlags;
719
}
720
/* Load the local (per-repository) or global (all-repositories) value, and use
721
** g.comFmtFlags as a cache. */
722
comFmtFlags = db_get_int("comment-format", COMMENT_PRINT_UNSET);
723
if( comFmtFlags!=COMMENT_PRINT_UNSET ){
724
g.comFmtFlags = comFmtFlags;
725
return comFmtFlags;
726
}
727
/* Fallback to the default value. */
728
g.comFmtFlags = COMMENT_PRINT_DEFAULT;
729
return g.comFmtFlags;
730
}
731
732
/*
733
**
734
** COMMAND: test-comment-format
735
**
736
** Usage: %fossil test-comment-format [OPTIONS] TEXT [PREFIX] [ORIGTEXT]
737
**
738
** Test comment formatting and printing. Use for testing only.
739
**
740
** The default (canonical) formatting algorithm is:
741
**
742
** * Omit leading/trailing whitespace
743
** * Collapse internal whitespace into a single space character.
744
** * Attempt to break lines at whitespace or at a hyphen.
745
**
746
** Use --whitespace, --origbreak, --trimcrlf, --trimspace,
747
** and/or --wordbreak to disable the canonical processing and do
748
** the special processing specified by those other options.
749
**
750
** Options:
751
** --decode Decode the text using the same method used when
752
** handling the value of a C-card from a manifest.
753
** --file FILE Omit the TEXT argument and read the comment text
754
** from FILE.
755
** --indent Number of spaces to indent (default (-1) is to
756
** auto-detect). Zero means no indent.
757
** --orig FILE Take the value for the ORIGTEXT argument from FILE.
758
** --origbreak Attempt to break when the original comment text
759
** is detected.
760
** --trimcrlf Enable trimming of leading/trailing CR/LF.
761
** --trimspace Enable trimming of leading/trailing spaces.
762
** --whitespace Keep all internal whitespace.
763
** --wordbreak Attempt to break lines on word boundaries.
764
** -W|--width NUM Width of lines (default (-1) is to auto-detect).
765
** Zero means no limit.
766
*/
767
void test_comment_format(void){
768
const char *zWidth;
769
const char *zIndent;
770
const char *zPrefix = 0;
771
char *zText = 0;
772
char *zOrigText = 0;
773
int indent, width;
774
int i;
775
const char *fromFile = find_option("file", 0, 1);
776
int decode = find_option("decode", 0, 0)!=0;
777
int flags = COMMENT_PRINT_CANONICAL;
778
const char *fromOrig = find_option("orig", 0, 1);
779
if( find_option("whitespace",0,0) ){
780
flags = 0;
781
}
782
if( find_option("trimcrlf", 0, 0) ){
783
flags = COMMENT_PRINT_TRIM_CRLF;
784
}
785
if( find_option("trimspace", 0, 0) ){
786
flags |= COMMENT_PRINT_TRIM_SPACE;
787
flags &= COMMENT_PRINT_CANONICAL;
788
}
789
if( find_option("wordbreak", 0, 0) ){
790
flags |= COMMENT_PRINT_WORD_BREAK;
791
flags &= COMMENT_PRINT_CANONICAL;
792
}
793
if( find_option("origbreak", 0, 0) ){
794
flags |= COMMENT_PRINT_ORIG_BREAK;
795
flags &= COMMENT_PRINT_CANONICAL;
796
}
797
zWidth = find_option("width","W",1);
798
if( zWidth ){
799
width = atoi(zWidth);
800
}else{
801
width = -1; /* automatic */
802
}
803
zIndent = find_option("indent",0,1);
804
if( zIndent ){
805
indent = atoi(zIndent);
806
}else{
807
indent = -1; /* automatic */
808
}
809
verify_all_options();
810
zPrefix = zText = zOrigText = 0;
811
if( fromFile ){
812
Blob fileData;
813
blob_read_from_file(&fileData, fromFile, ExtFILE);
814
zText = fossil_strdup(blob_str(&fileData));
815
blob_reset(&fileData);
816
}
817
if( fromOrig ){
818
Blob fileData;
819
blob_read_from_file(&fileData, fromOrig, ExtFILE);
820
zOrigText = fossil_strdup(blob_str(&fileData));
821
blob_reset(&fileData);
822
}
823
for(i=2; i<g.argc; i++){
824
if( zText==0 ){
825
zText = g.argv[i];
826
continue;
827
}
828
if( zPrefix==0 ){
829
zPrefix = g.argv[i];
830
continue;
831
}
832
if( zOrigText==0 ){
833
zOrigText = g.argv[i];
834
continue;
835
}
836
usage("[OPTIONS] TEXT [PREFIX] [ORIGTEXT]");
837
}
838
if( decode ){
839
zText = mprintf(fromFile?"%z":"%s" /*works-like:"%s"*/, zText);
840
defossilize(zText);
841
if( zOrigText ){
842
zOrigText = mprintf(fromFile?"%z":"%s" /*works-like:"%s"*/, zOrigText);
843
defossilize(zOrigText);
844
}
845
}
846
if( zPrefix==0 ) zPrefix = "00:00:00 ";
847
if( indent<0 ){
848
indent = strlen(zPrefix);
849
}
850
if( zPrefix && *zPrefix ){
851
fossil_print("%s", zPrefix);
852
}
853
fossil_print("(%d lines output)\n",
854
comment_print(zText, zOrigText, indent, width, flags));
855
}
856

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button