]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Improvements to the non-printing character delimiter logic in the CLI
authordrh <>
Sun, 3 May 2026 15:53:52 +0000 (15:53 +0000)
committerdrh <>
Sun, 3 May 2026 15:53:52 +0000 (15:53 +0000)
prompt, for improved accuracy and for improved testability.

FossilOrigin-Name: f5649795cd8d7a1f270037ef0b212ea605c1665ec05a75a6da0b250f2aeab070

manifest
manifest.uuid
src/shell.c.in
test/shell-prompt.sql

index f5fedd3d85c61e823fb051318bebf622cbfcdebe..44153a9f7fb13f62215bd177f82b8f58fec189ab 100644 (file)
--- 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.
index 1c02524f5bb71b1ce881116843b9b66cb11e7ded..f8354382e201d2ca70d9c39c5fd0c2d9009edc4b 100644 (file)
@@ -1 +1 @@
-b9545b0f0b6fa0a777a064e1410b17f52cb49d853c188f9b8b3a772bd810ece3
+f5649795cd8d7a1f270037ef0b212ea605c1665ec05a75a6da0b250f2aeab070
index 8e08823166b71ad237ea5e1e6cf52e8bd84b3c39..9c1acbdbc720b99c4d4df1dc186cc6075b02f1ed 100644 (file)
@@ -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
 }
 
 /*
index 120d490377d5bd62afd5ad6216fd86da5325653a..9ac83b0ef38fccd6203e0a53df8aab8fa30a80df 100644 (file)
@@ -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('</myes/:no/;>',NULL,':memory:');
+.check <yes>
+.testcase 4001
+.mode list -rowsep '' -escape ascii
+SELECT shell_prompt_test('</myes/:no/;>',NULL,'t1.db');
+.check <no>
+
+.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