From: drh <> Date: Sun, 3 May 2026 15:53:52 +0000 (+0000) Subject: Improvements to the non-printing character delimiter logic in the CLI X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=2bc658c387e4b9b2047bf2b7ef2d37a754bbb895;p=thirdparty%2Fsqlite.git Improvements to the non-printing character delimiter logic in the CLI prompt, for improved accuracy and for improved testability. FossilOrigin-Name: f5649795cd8d7a1f270037ef0b212ea605c1665ec05a75a6da0b250f2aeab070 --- diff --git a/manifest b/manifest index f5fedd3d85..44153a9f7f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Change\sthe\snon-printing\scharacter\sescapes\sfor\seditline\sto\sU+0001\sand\nU+0002.\s\sAlso\sinsert\sthose\sescapes\sfor\sreadline.\s\sReadline\sdoes\snot\nneed\sit\s(because\sit\scan\sfigure\sthem\sout\son\sits\sown)\sbut\sthey\sare\sneeded\nwhen\seditline\sis\sbeing\sused\sin\sreadline-compatibility\smode,\sand\swe\shave\nno\sway\sof\sdistinguishing\sbetween\seditline\sand\sreadline\sin\sthat\scase,\sso\nwe\smight\sas\swell\sinclude\sthem. -D 2026-05-02T23:40:40.608 +C Improvements\sto\sthe\snon-printing\scharacter\sdelimiter\slogic\sin\sthe\sCLI\nprompt,\sfor\simproved\saccuracy\sand\sfor\simproved\stestability. +D 2026-05-03T15:53:52.474 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -736,7 +736,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c fcc406bfb055bee9954ee77c023f4a2a66a24bcdf1573516a72280811a269c20 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c 4c05cde130f26991b7411d8c6809e0630625e18078742c963a047b4b9cc01d49 -F src/shell.c.in 28e4b97a6746973c935b8510c7926ee9fe3d03bb35056064e9a3b7cb29c73178 +F src/shell.c.in e0eb5d8c91b0fa29dc25b4511e46c28d800a11e94d8763a58610983e4a120810 F src/sqlite.h.in 39d2e09114d2bdb7afd998f4a469c8f8cd065f8093835a7d0422f260fc78fb4f F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 9788c301f95370fa30e808861f1d2e6f022a816ddbe2a4f67486784c1b31db2e @@ -1624,7 +1624,7 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell-prompt.sql 868ca1711677ada45720532fe23dbbbe881f3612dc29692dac45b19129648b3f +F test/shell-prompt.sql 96c055ac09aff7f96b7e2bca8f0192c36ed34d11df4cf4a58b9d4540b387ff3f F test/shell1.test c84eff209f93ad17ccdf7e1634969fc8231684254edeb21d9b13d67c3179cdb5 F test/shell2.test dc541d2681503e55466a24d35a4cbf8ca5b90b8fcdef37fc4db07373a67d31d3 F test/shell3.test 91efdd545097a61a1f72cf79c9ad5b49da080f3f10282eaf4c3c272cd1012db2 @@ -2203,8 +2203,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 63050bee6e5ed46cead14bd5a02b0cb6a7dbcdcb6a56601b90c10cf1c8d84efc -R 3d27d61ea4b8d5c891af38a2c8c58401 +P b9545b0f0b6fa0a777a064e1410b17f52cb49d853c188f9b8b3a772bd810ece3 +R 2722a106bcabd9853ab1b6613af68b1b U drh -Z f6ed14e12eebf843b747f8d53d83a51a +Z 4349d7842f8a2c527da152c639edc6d0 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 1c02524f5b..f8354382e2 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b9545b0f0b6fa0a777a064e1410b17f52cb49d853c188f9b8b3a772bd810ece3 +f5649795cd8d7a1f270037ef0b212ea605c1665ec05a75a6da0b250f2aeab070 diff --git a/src/shell.c.in b/src/shell.c.in index 8e08823166..9c1acbdbc7 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -382,6 +382,7 @@ struct ShellState { u8 nPopOutput; /* Revert .output settings when reaching zero */ u8 nPopMode; /* Revert .mode settings when reaching zero */ u8 enableTimer; /* Enable the timer. 2: permanently 1: only once */ + u8 bDelimitNonprint; /* Add \001...\002 around non-printing in prompts */ int inputNesting; /* Track nesting level of .read and other redirects */ double prevTimer; /* Last reported timer value */ double tmProgress; /* --timeout option for .progress */ @@ -1036,9 +1037,9 @@ static const char *prompt_filename(ShellState *p, const char *zMemoryName){ } if( zFN==0 || zFN[0]==0 ){ zFN = p->pAuxDb->zDbFilename; - if( zFN==0 || zFN[0]==0 || cli_strcmp(zFN,":memory:")==0 ){ - zFN = zMemoryName; - } + } + if( zFN==0 || zFN[0]==0 || cli_strcmp(zFN,":memory:")==0 ){ + zFN = zMemoryName; } return zFN; } @@ -1082,6 +1083,22 @@ static const char *prompt_user(void){ return z; } +/* If z[] begins with one or more ANSI X3.64 (VT100) escape sequences +** return the number of bytes in all such escape sequences. Return +** zero if there are no valid escape sequences. +*/ +static int nAnsiEscape(const char *z){ + int i = 0; + while( z[i]=='\033' && z[i+1]=='[' ){ + int k = i+2; + while( z[k]>=0x30 && z[k]<=0x3f ){ k++; } + while( z[k]>=0x20 && z[k]<=0x2f ){ k++; } + if( z[k]<0x40 || z[k]>0x7e ) break; + i = k+1; + } + return i; +} + /* ** Expand escapes in the given input prompt string. Return the ** expanded prompt in memory obtained from sqlite3_malloc(). The @@ -1357,36 +1374,26 @@ static char *expand_prompt( sqlite3_str_appendchar(pOut, 1, '\0'); } -#if HAVE_EDITLINE || HAVE_READLINE /* Editline does not recognize ANSI X3.64 escape sequences. So we have - ** to find them all and enclose them inside '\1'...'\2' delimiters. - ** Readline does not require this crutch, but it does not hurt to include - ** it, for the cases where editline is running in readline compatibility - ** mode. + ** to find them all and enclose them inside '\001'...'\002' delimiters. + ** Some versions of readline also require this, but others do not. */ - if( strstr(sqlite3_str_value(pOut),"\033[")!=0 ){ + if( p->bDelimitNonprint && strstr(sqlite3_str_value(pOut),"\033[")!=0 ){ char *zOrig = sqlite3_str_finish(pOut); + int n; pOut = sqlite3_str_new(0); for(i=0; zOrig[i]; i++){ - if( zOrig[i]=='\033' && zOrig[i+1]=='[' ){ - int k = i+2; + if( zOrig[i]=='\033' && (n = nAnsiEscape(zOrig+i))>0 ){ sqlite3_str_appendchar(pOut, 1, 1); - while( 1 /*exit-by-break*/ ){ - for( ; zOrig[k]>=0x20 && zOrig[k]<=0x3f; k++){} - if( zOrig[k]>=0x40 ) k++; - if( zOrig[k]!='\033' || zOrig[k+1]!='[' || zOrig[k+2]==0 ) break; - k += 2; - } - sqlite3_str_append(pOut, &zOrig[i], k-i); + sqlite3_str_append(pOut, &zOrig[i], n); sqlite3_str_appendchar(pOut, 1, 2); - i = k-1; + i += n-1; }else{ sqlite3_str_appendchar(pOut, 1, zOrig[i]); } } sqlite3_free(zOrig); } -#endif return sqlite3_str_finish(pOut); } @@ -1762,9 +1769,7 @@ static void shellAddSchemaName( } /* -** SQL function: shell_prompt_test(PROMPT) -** shell_prompt_test(PROMPT,PRIOR) -** shell_prompt_test(PROMPT,PRIOR,FILENAME) +** SQL function: shell_prompt_test(PROMPT,PRIOR,FILENAME,FLAGS) ** ** Return the shell prompt, with escapes expanded, for testing purposes. ** The first argument is the raw (unexpanded) prompt string. Or if the @@ -1772,7 +1777,15 @@ static void shellAddSchemaName( ** configured. If the second argument exists and is not NULL, then the ** second argument is understood to be prior incomplete text and a ** continuation prompt is generated. If a third argument is provided, -** it is assumed to be the full pathname of the database file. +** it is assumed to be the full pathname of the database file. The +** fourth argument, if provided, is an integer of flags: +** +** 0x0001 Always insert \001..\002 delimiters around ANSI escapes +** 0x0002 Never insert \001..\002 delimiters +** +** This function is for testing purposes only. The interface may change. +** The function itself might be renamed or removed in future releases. Do +** not use this function in applications. */ static void shellExpandPrompt( sqlite3_context *pCtx, @@ -1786,7 +1799,10 @@ static void shellExpandPrompt( int mSavedFlgs; const char *zFName; char *zRes; + int mFlags; + char bSavedDelimit = p->bDelimitNonprint; + if( nVal<1 ) return; if( nVal<2 || (zPrior = (const char*)sqlite3_value_text(apVal[1]))==0 || zPrior[0]==0 @@ -1803,7 +1819,14 @@ static void shellExpandPrompt( p->pAuxDb->zDbFilename = zFName; p->pAuxDb->mFlgs |= 0x001; } + mFlags = nVal>=4 ? sqlite3_value_int(apVal[3]) : 0; + if( mFlags & 0x0001 ){ + p->bDelimitNonprint = 1; + }else if( mFlags & 0x0002 ){ + p->bDelimitNonprint = 0; + } zRes = expand_prompt(p, zPrior, zPrompt); + p->bDelimitNonprint = bSavedDelimit; p->pAuxDb->zDbFilename = zSavedDbFile; p->pAuxDb->mFlgs = mSavedFlgs; sqlite3_result_text(pCtx, zRes, -1, SQLITE_TRANSIENT); @@ -5071,11 +5094,7 @@ static void open_db(ShellState *p, int openFlags){ sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0, editFunc, 0, 0); #endif - sqlite3_create_function(p->db, "shell_prompt_test", 1, SQLITE_UTF8, - p, shellExpandPrompt, 0, 0); - sqlite3_create_function(p->db, "shell_prompt_test", 2, SQLITE_UTF8, - p, shellExpandPrompt, 0, 0); - sqlite3_create_function(p->db, "shell_prompt_test", 3, SQLITE_UTF8, + sqlite3_create_function(p->db, "shell_prompt_test", -1, SQLITE_UTF8, p, shellExpandPrompt, 0, 0); sqlite3_create_function(p->db, "shell_temp_filename", 1, SQLITE_UTF8, p, shellTempFilenameFunc, 0, 0); @@ -13389,6 +13408,16 @@ static void main_init(ShellState *p) { sqlite3_config(SQLITE_CONFIG_URI, 1); sqlite3_config(SQLITE_CONFIG_MULTITHREAD); globalShellState = p; +#if HAVE_EDITLINE || HAVE_READLINE + /* Editline requires \001...\002 delimiters around ANSI x3.64 escapes in + ** prompt strings. Readline does sometimes, depending on how it is + ** compiled and installed. */ + p->bDelimitNonprint = 1; +#else + /* No \001...\002 escapes required for linenoise or when not using a + ** command-line editing library */ + p->bDelimitNonprint = 0; +#endif } /* diff --git a/test/shell-prompt.sql b/test/shell-prompt.sql index 120d490377..9ac83b0ef3 100644 --- a/test/shell-prompt.sql +++ b/test/shell-prompt.sql @@ -95,25 +95,26 @@ SELECT shell_prompt_test(NULL); SELECT shell_prompt_test(NULL,'SELECT'); .check --glob ' *;*-> '; .testcase 1002 -SELECT shell_prompt_test(NULL,'SELECT ((("'); +SELECT shell_prompt_test(NULL,'SELECT ((("',null,2); .check --glob ' *[ m]")));*-> '; .testcase 1003 -SELECT shell_prompt_test(NULL,'SELECT ((()['); +SELECT shell_prompt_test(NULL,'SELECT ((()[',null,2); .check --glob ' *[ m]]));*-> '; .testcase 1004 -SELECT shell_prompt_test(NULL,'SELECT '''); +SELECT shell_prompt_test(NULL,'SELECT ''',null,2); .check --glob " *[ m]';*-> "; .testcase 1005 -SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN'); +SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN',null,2); .check --glob " *[ m];END;*-> "; .testcase 1006 -SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN SELECT (((['); +SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN SELECT ((([',null,2); .check --glob " *[ m]])));END;*-> "; .testcase 1007 -SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN SELECT ((/*a(((''bc'); +SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN SELECT ((/*a(((''bc', + null,2); .check --glob " *[ m][*]/));END;*-> "; .testcase 1008 -SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN SELECT 1;'); +SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN SELECT 1;',null,2); .check --glob " *[ m]END;*-> "; .testcase 2000 @@ -146,3 +147,31 @@ SELECT shell_prompt_test('(/A-/v)'); .testcase 3002 SELECT shell_prompt_test('(/A-/D%Y-%m-%dT%H:%M:%S/D)'); .check --glob '(SQLite-20#-#-#T#:#:#)' + +.testcase 4000 +.mode list -rowsep '' -escape ascii +SELECT shell_prompt_test('',NULL,':memory:'); +.check +.testcase 4001 +.mode list -rowsep '' -escape ascii +SELECT shell_prompt_test('',NULL,'t1.db'); +.check + +.testcase 5000 +SELECT shell_prompt_test('abc/e[0mxyz',NULL,NULL,1); +.check abc^A^[[0m^Bxyz +.testcase 5001 +SELECT shell_prompt_test('abc/e[0mxyz',NULL,NULL,2); +.check abc^[[0mxyz +.testcase 5002 +SELECT shell_prompt_test('/e[0mxyz',NULL,NULL,1); +.check ^A^[[0m^Bxyz +.testcase 5003 +SELECT shell_prompt_test('abc/e[0m',NULL,NULL,1); +.check abc^A^[[0m^B +.testcase 5004 +SELECT shell_prompt_test('abc/e[1;32m/e[0m/e[/exyz',NULL,NULL,1); +.check abc^A^[[1;32m^[[0m^B^[[^[xyz +.testcase 5005 +SELECT shell_prompt_test('abc/e[1,32m/e[0m/e[/exyz',NULL,NULL,1); +.check abc^[[1,32m^A^[[0m^B^[[^[xyz