From: drh <> Date: Sat, 15 Nov 2025 11:28:23 +0000 (+0000) Subject: Data structure improvements on columnar layout. Prep work for getting X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3338ca5f56800e82a962dbc494dbcdee82b97a92;p=thirdparty%2Fsqlite.git Data structure improvements on columnar layout. Prep work for getting columnar layouts to respond to nScreenWidth. FossilOrigin-Name: 777eeb2ed2708faf42559387bd582b9345a794798a0327e4fcd75e37948eac60 --- diff --git a/ext/qrf/qrf.c b/ext/qrf/qrf.c index e5181caba2..8aaf737537 100644 --- a/ext/qrf/qrf.c +++ b/ext/qrf/qrf.c @@ -1062,62 +1062,6 @@ static void qrfWidthPrint(Qrf *p, sqlite3_str *pOut, int w, const char *zUtf){ } } -/* -** Data for columnar layout, collected into a single object so -** that it can be more easily passed into subroutines. -*/ -typedef struct qrfColData qrfColData; -struct qrfColData { - Qrf *p; /* The QRF instance */ - int nCol; /* Number of columns in the table */ - unsigned char bMultiRow; /* One or more cells will span multiple lines */ - sqlite3_int64 nRow; /* Number of rows */ - sqlite3_int64 nAlloc; /* Number of cells allocated */ - sqlite3_int64 n; /* Number of cells. nCol*nRow */ - char **azThis; /* Cache of pointers to current row */ - char **az; /* Content of all cells */ - int *aiWth; /* Width of each cell */ - int *aiCol; /* Width of each column */ - unsigned char *aAlign; /* Alignment for each column */ -}; - -/* -** Free all the memory allocates in the qrfColData object -*/ -static void qrfColDataFree(qrfColData *p){ - sqlite3_int64 i; - for(i=0; in; i++) sqlite3_free(p->az[i]); - sqlite3_free(p->az); - sqlite3_free(p->aiWth); - sqlite3_free(p->azThis); - memset(p, 0, sizeof(*p)); -} - -/* -** Allocate space for more cells in the qrfColData object. -** Return non-zero if a memory allocation fails. -*/ -static int qrfColDataEnlarge(qrfColData *p){ - char **azData; - int *aiWth; - p->nAlloc = 2*p->nAlloc + 10*p->nCol; - azData = sqlite3_realloc64(p->az, p->nAlloc*sizeof(char*)); - if( azData==0 ){ - qrfOom(p->p); - qrfColDataFree(p); - return 1; - } - p->az = azData; - aiWth = sqlite3_realloc64(p->aiWth, p->nAlloc*sizeof(int)); - if( aiWth==0 ){ - qrfOom(p->p); - qrfColDataFree(p); - return 1; - } - p->aiWth = aiWth; - return 0; -} - /* ** (*pz)[] is a line of text that is to be displayed the box or table or ** similar tabular formats. z[] contain newlines or might be too wide @@ -1217,6 +1161,161 @@ static void qrfWrapLine( *piNext = i; } +/* +** Append nVal bytes of text from zVal onto the end of pOut. +** Convert tab characters in zVal to the appropriate number of +** spaces. +*/ +static void qrfAppendWithTabs( + sqlite3_str *pOut, /* Append text here */ + const char *zVal, /* Text to append */ + int nVal /* Use only the first nVal bytes of zVal[] */ +){ + int i = 0; + unsigned int col = 0; + unsigned char *z = (unsigned char *)zVal; + while( i0 ){ + sqlite3_str_append(pOut, (const char*)z, k); + z += k; + nVal -= k; + }else if( c=='\t' ){ + k = 8 - (col&7); + sqlite3_str_appendchar(pOut, k, ' '); + col += k; + z++; + nVal--; + }else{ + char zCtrlPik[4]; + col++; + zCtrlPik[0] = 0xe2; + zCtrlPik[1] = 0x90; + zCtrlPik[2] = 0x80+c; + sqlite3_str_append(pOut, zCtrlPik, 3); + z++; + nVal--; + } + }else if( (0x80&c)==0 ){ + i++; + col++; + }else{ + int u = 0; + int len = sqlite3_qrf_decode_utf8(&z[i], &u); + i += len; + col += sqlite3_qrf_wcwidth(u); + } + } + sqlite3_str_append(pOut, (const char*)z, i); +} + +/* +** Output horizontally justified text into pOut. The text is the +** first nVal bytes of zVal. Include nWS bytes of whitespace, either +** split between both sides, or on the left, or on the right, depending +** on eAlign. +*/ +static void qrfPrintAligned( + sqlite3_str *pOut, /* Append text here */ + const char *zVal, /* Text to append */ + int nVal, /* Use only the first nVal bytes of zVal[] */ + int nWS, /* Whitespace for horizonal alignment */ + unsigned char eAlign /* Alignment type */ +){ + eAlign &= QRF_ALIGN_HMASK; + if( eAlign==QRF_ALIGN_Center ){ + /* Center the text */ + sqlite3_str_appendchar(pOut, nWS/2, ' '); + qrfAppendWithTabs(pOut, zVal, nVal); + sqlite3_str_appendchar(pOut, nWS - nWS/2, ' '); + }else if( eAlign==QRF_ALIGN_Right){ + /* Right justify the text */ + sqlite3_str_appendchar(pOut, nWS, ' '); + qrfAppendWithTabs(pOut, zVal, nVal); + }else{ + /* Left justify the next */ + qrfAppendWithTabs(pOut, zVal, nVal); + sqlite3_str_appendchar(pOut, nWS, ' '); + } +} + +/* +** GCC does not define the offsetof() macro so we'll have to do it +** ourselves. +*/ +#ifndef offsetof +# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0)) +#endif + +/* +** Data for columnar layout, collected into a single object so +** that it can be more easily passed into subroutines. +*/ +typedef struct qrfColData qrfColData; +struct qrfColData { + Qrf *p; /* The QRF instance */ + int nCol; /* Number of columns in the table */ + unsigned char bMultiRow; /* One or more cells will span multiple lines */ + unsigned char nMargin; /* Width of column margins */ + sqlite3_int64 nRow; /* Number of rows */ + sqlite3_int64 nAlloc; /* Number of cells allocated */ + sqlite3_int64 n; /* Number of cells. nCol*nRow */ + char **az; /* Content of all cells */ + int *aiWth; /* Width of each cell */ + struct qrfPerCol { /* Per-column data */ + char *z; /* Cache of text for current row */ + sqlite3_int64 nW; /* Total text width across all rows */ + int w; /* Computed width of this column */ + int nLn; /* Lines of text over all rows */ + int mxW; /* Maximum natural (unwrapped) width */ + unsigned char e; /* Alignment */ + unsigned char fx; /* Width is fixed */ + } *a; /* One per column */ +}; + +/* +** Free all the memory allocates in the qrfColData object +*/ +static void qrfColDataFree(qrfColData *p){ + sqlite3_int64 i; + for(i=0; in; i++) sqlite3_free(p->az[i]); + sqlite3_free(p->az); + sqlite3_free(p->aiWth); + sqlite3_free(p->a); + memset(p, 0, sizeof(*p)); +} + +/* +** Allocate space for more cells in the qrfColData object. +** Return non-zero if a memory allocation fails. +*/ +static int qrfColDataEnlarge(qrfColData *p){ + char **azData; + int *aiWth; + p->nAlloc = 2*p->nAlloc + 10*p->nCol; + azData = sqlite3_realloc64(p->az, p->nAlloc*sizeof(char*)); + if( azData==0 ){ + qrfOom(p->p); + qrfColDataFree(p); + return 1; + } + p->az = azData; + aiWth = sqlite3_realloc64(p->aiWth, p->nAlloc*sizeof(int)); + if( aiWth==0 ){ + qrfOom(p->p); + qrfColDataFree(p); + return 1; + } + p->aiWth = aiWth; + return 0; +} + /* ** Print a markdown or table-style row separator using ascii-art */ @@ -1224,10 +1323,10 @@ static void qrfRowSeparator(sqlite3_str *pOut, qrfColData *p, char cSep){ int i; if( p->nCol>0 ){ sqlite3_str_append(pOut, &cSep, 1); - sqlite3_str_appendchar(pOut, p->aiCol[0]+2, '-'); + sqlite3_str_appendchar(pOut, p->a[0].w+p->nMargin, '-'); for(i=1; inCol; i++){ sqlite3_str_append(pOut, &cSep, 1); - sqlite3_str_appendchar(pOut, p->aiCol[i]+2, '-'); + sqlite3_str_appendchar(pOut, p->a[i].w+p->nMargin, '-'); } sqlite3_str_append(pOut, &cSep, 1); } @@ -1288,10 +1387,10 @@ static void qrfBoxSeparator( int i; if( p->nCol>0 ){ sqlite3_str_appendall(pOut, zSep1); - qrfBoxLine(pOut, p->aiCol[0]+2); + qrfBoxLine(pOut, p->a[0].w+p->nMargin); for(i=1; inCol; i++){ sqlite3_str_appendall(pOut, zSep2); - qrfBoxLine(pOut, p->aiCol[i]+2); + qrfBoxLine(pOut, p->a[i].w+p->nMargin); } sqlite3_str_appendall(pOut, zSep3); } @@ -1303,109 +1402,23 @@ static void qrfBoxSeparator( */ static void qrfLoadAlignment(qrfColData *pData, Qrf *p){ sqlite3_int64 i; - memset(pData->aAlign, p->spec.eDfltAlign, pData->nCol); for(i=0; inCol; i++){ + pData->a[i].e = p->spec.eDfltAlign; if( ispec.nAlign ){ unsigned char ax = p->spec.aAlign[i]; if( (ax & QRF_ALIGN_HMASK)!=0 ){ - pData->aAlign[i] = (ax & QRF_ALIGN_HMASK) | - (pData->aAlign[i] & QRF_ALIGN_VMASK); + pData->a[i].e = (ax & QRF_ALIGN_HMASK) | + (pData->a[i].e & QRF_ALIGN_VMASK); } }else if( ispec.nWidth ){ if( p->spec.aWidth[i]<0 ){ - pData->aAlign[i] = QRF_ALIGN_Right | - (pData->aAlign[i] & QRF_ALIGN_VMASK); + pData->a[i].e = QRF_ALIGN_Right | + (pData->a[i].e & QRF_ALIGN_VMASK); } - }else{ - break; } } } -/* -** Append nVal bytes of text from zVal onto the end of pOut. -** Convert tab characters in zVal to the appropriate number of -** spaces. -*/ -static void qrfAppendWithTabs( - sqlite3_str *pOut, /* Append text here */ - const char *zVal, /* Text to append */ - int nVal /* Use only the first nVal bytes of zVal[] */ -){ - int i = 0; - unsigned int col = 0; - unsigned char *z = (unsigned char *)zVal; - while( i0 ){ - sqlite3_str_append(pOut, (const char*)z, k); - z += k; - nVal -= k; - }else if( c=='\t' ){ - k = 8 - (col&7); - sqlite3_str_appendchar(pOut, k, ' '); - col += k; - z++; - nVal--; - }else{ - char zCtrlPik[4]; - col++; - zCtrlPik[0] = 0xe2; - zCtrlPik[1] = 0x90; - zCtrlPik[2] = 0x80+c; - sqlite3_str_append(pOut, zCtrlPik, 3); - z++; - nVal--; - } - }else if( (0x80&c)==0 ){ - i++; - col++; - }else{ - int u = 0; - int len = sqlite3_qrf_decode_utf8(&z[i], &u); - i += len; - col += sqlite3_qrf_wcwidth(u); - } - } - sqlite3_str_append(pOut, (const char*)z, i); -} - -/* -** Output horizontally justified text into pOut. The text is the -** first nVal bytes of zVal. Include nWS bytes of whitespace, either -** split between both sides, or on the left, or on the right, depending -** on eAlign. -*/ -static void qrfPrintAligned( - sqlite3_str *pOut, /* Append text here */ - const char *zVal, /* Text to append */ - int nVal, /* Use only the first nVal bytes of zVal[] */ - int nWS, /* Whitespace for horizonal alignment */ - unsigned char eAlign /* Alignment type */ -){ - eAlign &= QRF_ALIGN_HMASK; - if( eAlign==QRF_ALIGN_Center ){ - /* Center the text */ - sqlite3_str_appendchar(pOut, nWS/2, ' '); - qrfAppendWithTabs(pOut, zVal, nVal); - sqlite3_str_appendchar(pOut, nWS - nWS/2, ' '); - }else if( eAlign==QRF_ALIGN_Right){ - /* Right justify the text */ - sqlite3_str_appendchar(pOut, nWS, ' '); - qrfAppendWithTabs(pOut, zVal, nVal); - }else{ - /* Left justify the next */ - qrfAppendWithTabs(pOut, zVal, nVal); - sqlite3_str_appendchar(pOut, nWS, ' '); - } -} - /* ** Columnar modes require that the entire query be evaluated first, with ** results written into memory, so that we can compute appropriate column @@ -1431,16 +1444,14 @@ static void qrfColumnar(Qrf *p){ /* Initialize the data container */ memset(&data, 0, sizeof(data)); data.nCol = p->nCol; - data.azThis = sqlite3_malloc64( nColumn*(sizeof(char*) + sizeof(int) + 1) ); - if( data.azThis==0 ){ + data.a = sqlite3_malloc64( nColumn*sizeof(struct qrfPerCol) ); + if( data.a==0 ){ qrfOom(p); return; } - data.aiCol = (int*)&data.azThis[nColumn]; - data.aAlign = (unsigned char*)&data.aiCol[nColumn]; + memset(data.a, 0, nColumn*sizeof(struct qrfPerCol) ); if( qrfColDataEnlarge(&data) ) return; assert( data.az!=0 ); - assert( data.aAlign!=0 ); /* Load the column header names and all cell content into data */ if( p->spec.bTitles==QRF_Yes ){ @@ -1449,13 +1460,16 @@ static void qrfColumnar(Qrf *p){ for(i=0; ipStmt,i); int nNL = 0; - int n; + int n, w; pStr = sqlite3_str_new(p->db); qrfEncodeText(p, pStr, z ? z : ""); n = sqlite3_str_length(pStr); z = data.az[data.n] = sqlite3_str_finish(pStr); - data.aiWth[data.n] = qrfDisplayWidth(z, n, &nNL); + data.aiWth[data.n] = w = qrfDisplayWidth(z, n, &nNL); data.n++; + data.a[i].nW += w; + if( w>data.a[i].mxW ) data.a[i].mxW = w; + data.a[i].nLn += 1+nNL; if( nNL ) data.bMultiRow = 1; } p->spec.eText = saved_eText; @@ -1468,13 +1482,16 @@ static void qrfColumnar(Qrf *p){ for(i=0; idb); qrfRenderValue(p, pStr, i); n = sqlite3_str_length(pStr); z = data.az[data.n] = sqlite3_str_finish(pStr); - data.aiWth[data.n] = qrfDisplayWidth(z, n, &nNL); + data.aiWth[data.n] = w = qrfDisplayWidth(z, n, &nNL); data.n++; + data.a[i].nW += w; + if( w>data.a[i].mxW ) data.a[i].mxW = w; + data.a[i].nLn += 1+nNL; if( nNL ) data.bMultiRow = 1; } p->nRow++; @@ -1487,10 +1504,14 @@ static void qrfColumnar(Qrf *p){ /* Compute the width and alignment of every column */ if( p->spec.bTitles==QRF_No ){ qrfLoadAlignment(&data, p); - }else if( p->spec.eTitleAlign==QRF_Auto ){ - memset(data.aAlign, QRF_ALIGN_Center, nColumn); }else{ - memset(data.aAlign, p->spec.eTitleAlign, nColumn); + unsigned char e; + if( p->spec.eTitleAlign==QRF_Auto ){ + e = QRF_ALIGN_Center; + }else{ + e = p->spec.eTitleAlign; + } + for(i=0; ispec.nAlign>i && (p->spec.aAlign[i] & QRF_ALIGN_HMASK)==0 ){ - data.aAlign[i] |= QRF_ALIGN_Right; + data.a[i].e |= QRF_ALIGN_Right; } }else if( w<0 ){ w = -w; if( p->spec.nAlign>i && (p->spec.aAlign[i] & QRF_ALIGN_HMASK)==0 ){ - data.aAlign[i] |= QRF_ALIGN_Right; + data.a[i].e |= QRF_ALIGN_Right; } } + if( w ) data.a[i].fx = 1; } if( w==0 ){ - for(j=i; j w ){ - w = data.aiWth[j]; - if( p->spec.nWrap>0 && w>p->spec.nWrap ){ - w = p->spec.nWrap; - data.bMultiRow = 1; - break; - } - } + w = data.a[i].mxW; + if( p->spec.nWrap>0 && w>p->spec.nWrap ){ + w = p->spec.nWrap; + data.bMultiRow = 1; } - }else if( data.bMultiRow==0 || w==1 ){ - for(j=i; j w ){ - data.bMultiRow = 1; - if( w==1 ){ - /* If aiWth[j] is 2 or more, then there might be a double-wide - ** character somewhere. So make the column width at least 2. */ - w = 2; - } - break; - } + }else if( (data.bMultiRow==0 || w==1) && data.a[i].mxW>w ){ + data.bMultiRow = 1; + if( w==1 ){ + /* If aiWth[j] is 2 or more, then there might be a double-wide + ** character somewhere. So make the column width at least 2. */ + w = 2; } } - data.aiCol[i] = w; + data.a[i].w = w; } /* TBD: Narrow columns so that the total is less than p->spec.nScreenWidth */ /* Draw the line across the top of the table. Also initialize ** the row boundary and column separator texts. */ + data.nMargin = 2; switch( p->spec.eStyle ){ case QRF_STYLE_Box: rowStart = BOX_13 " "; @@ -1577,7 +1590,7 @@ static void qrfColumnar(Qrf *p){ ** (if there is a title line) or a row in the body of the table. ** The column number will be j. The row number is i/nColumn. */ - for(j=0; jpOut, rowStart, szRowStart); bMore = 0; @@ -1586,11 +1599,11 @@ static void qrfColumnar(Qrf *p){ int nWide = 0; int iNext = 0; int nWS; - qrfWrapLine(data.azThis[j], data.aiCol[j], bWW, &nThis, &nWide, &iNext); - nWS = data.aiCol[j] - nWide; - qrfPrintAligned(p->pOut, data.azThis[j], nThis, nWS, data.aAlign[j]); - data.azThis[j] += iNext; - if( data.azThis[j][0]!=0 ) bMore = 1; + qrfWrapLine(data.a[j].z, data.a[j].w, bWW, &nThis, &nWide, &iNext); + nWS = data.a[j].w - nWide; + qrfPrintAligned(p->pOut, data.a[j].z, nThis, nWS, data.a[j].e); + data.a[j].z += iNext; + if( data.a[j].z[0]!=0 ) bMore = 1; if( jpOut, colSep, szColSep); }else{ @@ -1602,12 +1615,12 @@ static void qrfColumnar(Qrf *p){ /* This row was terminated by nLineLimit. Show ellipsis. */ sqlite3_str_append(p->pOut, rowStart, szRowStart); for(j=0; jpOut, data.aiCol[j], ' '); + if( data.a[j].z[0]==0 ){ + sqlite3_str_appendchar(p->pOut, data.a[j].w, ' '); }else{ int nE = 3; - if( nE>data.aiCol[j] ) nE = data.aiCol[j]; - qrfPrintAligned(p->pOut, "...", nE, data.aiCol[j]-nE, data.aAlign[j]); + if( nE>data.a[j].w ) nE = data.a[j].w; + qrfPrintAligned(p->pOut, "...", nE, data.a[j].w-nE, data.a[j].e); } if( jpOut, colSep, szColSep); @@ -1648,7 +1661,7 @@ static void qrfColumnar(Qrf *p){ case QRF_STYLE_Column: { if( isTitleDataSeparator ){ for(j=0; jpOut, data.aiCol[j], '-'); + sqlite3_str_appendchar(p->pOut, data.a[j].w, '-'); if( jpOut, colSep, szColSep); }else{ diff --git a/manifest b/manifest index f5d30d1a1e..b0f19a4afa 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Improve\scolumnar\slayout\sin\sQRF\sso\sthat\sit\scorrectly\sdeals\swith\scontrol\ncharacters,\sand\sespecially\stabs. -D 2025-11-15T00:23:09.446 +C Data\sstructure\simprovements\son\scolumnar\slayout.\s\sPrep\swork\sfor\sgetting\ncolumnar\slayouts\sto\srespond\sto\snScreenWidth. +D 2025-11-15T11:28:23.387 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -417,7 +417,7 @@ F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6 F ext/misc/zipfile.c 09e6e3a3ff40a99677de3c0bc6569bd5f4709b1844ac3d1c1452a456c5a62f1c F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee F ext/qrf/README.md 09dd538966d8ee32598fc010e7fe6755bd7190494953a02960a9c81197d20cf3 -F ext/qrf/qrf.c d84c787719d464d399d57e2f3c00b28a9a3ce7e2217f231a7948800818ad5975 +F ext/qrf/qrf.c 4b9a551c43631fee70561bec966484e1e6d0705e9390ad981bf90629ec50b43d F ext/qrf/qrf.h b4b3489b3b3683523fd248d15cf5945830643b036943efacdb772a3e00367aa2 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255 @@ -2175,8 +2175,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 2e07bc29ab1ca66049337f2cfbefcd57bdcd691a381b309fb8a5db6e72e56d03 -R 92baa41b393aca983481dff9605375cf +P 0650e2b83170b44c1ba944259a96d41e1a14a57004d4f1f80dc5640ae837a81e +R 68d6d980f828d21e49b27041ff53af43 U drh -Z 760acd97643ffb8533b6e177d763be56 +Z b607d4ec693b98f876c3e69df28145b2 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index aaff16ad7b..5cd4ed4176 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0650e2b83170b44c1ba944259a96d41e1a14a57004d4f1f80dc5640ae837a81e +777eeb2ed2708faf42559387bd582b9345a794798a0327e4fcd75e37948eac60