diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.c-pp b/ext/wasm/api/EXPORTED_FUNCTIONS.c-pp index 2b8397a6d7..c489bdf83b 100644 --- a/ext/wasm/api/EXPORTED_FUNCTIONS.c-pp +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.c-pp @@ -245,4 +245,5 @@ _fiddle_reset_db _fiddle_db_handle _fiddle_db_vfs _fiddle_export_db +_fiddle_get_prompt //#/if fiddle diff --git a/ext/wasm/fiddle/fiddle-worker.js b/ext/wasm/fiddle/fiddle-worker.js index 4b1ea2c538..0336379a9c 100644 --- a/ext/wasm/fiddle/fiddle-worker.js +++ b/ext/wasm/fiddle/fiddle-worker.js @@ -186,6 +186,7 @@ if(!f._){ if(!this.runMain()) return; f._ = sqlite3.wasm.xWrap('fiddle_exec', undefined, ['string']); + f.getPrompt = sqlite3.wasm.xWrap('fiddle_get_prompt', 'string:dealloc', []); } if(fiddleModule.isDead){ stderr("shell module has exit()ed. Cannot run SQL."); @@ -207,7 +208,8 @@ wMsg('working','end'); wMsg('wasm-info', { pointerSize: sqlite3.wasm.ptr.size, - heapSize: sqlite3.wasm.heap8().byteLength + heapSize: sqlite3.wasm.heap8().byteLength, + prompt: f.getPrompt() }); } }, diff --git a/ext/wasm/fiddle/fiddle.js b/ext/wasm/fiddle/fiddle.js index bfd24b8b8c..422a2079e1 100644 --- a/ext/wasm/fiddle/fiddle.js +++ b/ext/wasm/fiddle/fiddle.js @@ -349,9 +349,10 @@ SF.e.wasmInfo.innerText = 'WASM: '+( 4===v.pointerSize ? 32 : 64 )+'-bit' - //+' heap size: '+Number(v.heapSize) + +' heap: '+Number(v.heapSize) // Heap size is not changing even when loading a huge db? ; + SF.jqTerm?.set_prompt?.(v.prompt); }); /* querySelectorAll() proxy */ @@ -865,7 +866,7 @@ const jqeTerm = window.jQuery(SF.e.terminal).empty(); SF.jqTerm = jqeTerm.terminal(SF.dbExec.bind(SF),{ prompt: 'sqlite> ', - greetings: false /* note that the docs incorrectly call this 'greeting' */ + greetings: false /* the docs incorrectly call this 'greeting' */ }); EAll('.unhide-if-terminal-available').forEach(e=>{ e.classList.remove('hidden'); diff --git a/manifest b/manifest index 2bd39ba58e..3b8382c004 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Ensure\sthat\ssqlite3-opfs-async-proxy.js\sis\scopied\sinto\sthe\sfiddle\starget\sdirs. -D 2026-04-10T23:31:29.790 +C Improvements\sto\sthe\sinteractive\sprompt\sin\sthe\sCLI:\n(1)\sCLI\shonors\sSQLITE_PS1\sand\sSQLITE_PS2\senvvars\sas\sprompt\sstrings,\n(2)\sCLI\sprompt\sstrings\sexpand\svarious\sbackslash\sescapes,\n(3)\sNo\sarbitrary\slength\slimits\son\sCLI\sprompt\sstrings,\n(4)\sNew\sdefault\sprompt\sshows\sthe\sdatabase\sfilename\susing\sthe\s\f\nescape,\n(5)\sSQLite\score\sadds\sthe\ssqlite3_incomplate()\sAPI,\sused\sby\nthe\s\H\sescape\sin\sprompt\sstring\srendering,\n(6)\sEnhancements\sto\sthe\s".prompt"\sdot-command. +D 2026-04-11T18:38:54.051 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -582,7 +582,7 @@ F ext/wasm/SQLTester/SQLTester.mjs 6b3c52ed36a5573ca4883176f326332a8d4c0cecf5efd F ext/wasm/SQLTester/SQLTester.run.mjs 57f2adb33f43f2784abbf8026c1bfd049d8013af1998e7dcb8b50c89ffc332e0 F ext/wasm/SQLTester/index.html 64f3435084c7d6139b08d1f2a713828a73f68de2ae6a3112cbb5980d991ba06f F ext/wasm/SQLTester/touint8array.c 2d5ece04ec1393a6a60c4bf96385bda5e1a10ad49f3038b96460fc5e5aa7e536 -F ext/wasm/api/EXPORTED_FUNCTIONS.c-pp de2ce128aebeff9ef4161cff7a0ff0089be866121bcb8941ad44a38a65f1d436 +F ext/wasm/api/EXPORTED_FUNCTIONS.c-pp 189935d0106bca86661efc991ab13e1d5d6e1f55ec34dc7b5055a99321397edd F ext/wasm/api/README.md a905d5c6bfc3e2df875bd391d6d6b7b48d41b43bdee02ad115b47244781a7e81 F ext/wasm/api/extern-post-js.c-pp.js 80accc53cc6ea1e61c721595f42ba95baa7c7ea636807d9507e69403301f8c54 F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41 @@ -621,8 +621,8 @@ F ext/wasm/demo-worker1-promiser.c-pp.js d210aa1e8a74ea6244fe2290de5710bb55b3a9e F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d F ext/wasm/demo-worker1.js fdfa90aa9d6b402bfed802cf1595fe4da6cc834ac38c8ff854bf1ee01f5ff9bb F ext/wasm/example_extra_init.c 2347cd69d19d839ef4e5e77b7855103a7fe3ef2af86f2e8c95839afd8b05862f -F ext/wasm/fiddle/fiddle-worker.js 6c72acac2d381480bc9f5eb538e3f2faf2c1f72dd4fcbd05d3b409818a9a8fd5 -F ext/wasm/fiddle/fiddle.js fc0f19303d00014a0f285fefd30953e953be1bd01e757bbd9eda45c9dc2c154b +F ext/wasm/fiddle/fiddle-worker.js 3cdd20c1e84494a1f28bd80265ae2a754764b08c67e146e61f99d864de4bf5f6 +F ext/wasm/fiddle/fiddle.js ed0551146eb36a0679833d6fd58ba4f025f27e673a0f19535c6386397721849b F ext/wasm/fiddle/index.c-pp.html 02f063ef30b8124f311029855c4439e77bc6505d1bf65a163d88c064a63ee9d9 F ext/wasm/index-dist.html db23748044e286773f2768eec287669501703b5d5f72755e8db73607dc54d290 F ext/wasm/index.html 5bf6cf1b0a3c8b9f5f54d77f2219d7ae87a15162055ce308109c49b1dcab4239 @@ -680,7 +680,7 @@ F src/btreeInt.h 9c0f9ea5c9b5f4dcaea18111d43efe95f2ac276cd86d770dce10fd99ccc9388 F src/build.c 8581de0af3b6c448f5d64e2d18a91ac1e7057b3bcb8b8827e1240f80d87486a4 F src/callback.c 3605bbf02bd7ed46c79cd48346db4a32fc51d67624400539c0532f4eead804ad F src/carray.c 3efe3982d5fb323334c29328a4e189ccaef6b95612a6084ad5fa124fd5db1179 -F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e +F src/complete.c 9304071c5f2ddd040cd786ec5d157c6a5592561674816e4a9e74164a0ba40647 F src/date.c 61e92f1f7e2e88e1cd91e91dc69eb2b2854e7877254470f9fabd776bfac922b8 F src/dbpage.c c9ea81c11727f27e02874611e92773e68e2a90a875ef2404b084564c235fd91f F src/dbstat.c 73362c0df0f40ad5523a6f5501224959d0976757b511299bf892313e79d14f5c @@ -735,8 +735,8 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 928ff887f2a7c64275182060d94d06fdddbe32226c569781cf7e7edc6f58d7fd F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c ffe199f025a0dd74670d2a77232bdea364a4d7b36f32c64a6572d39ba6a11576 -F src/shell.c.in 4d00b373ce9485b5f2db2dbebf3a6a3ef3ebe7b77fc7ecb3278cae2cb4a5529b -F src/sqlite.h.in e2915e4a86d5e0783afb5cb72411df38d987c7f3c5aa2d5441b8e74d30b649d8 +F src/shell.c.in bed26ce3998b32fde83acbf7f4af3bd9e64902bec6bbfa1a834c9f30497b7231 +F src/sqlite.h.in a5605faa9479bbaac16c4ab43eb09ff50632004a8e05084d3fde56063ef73766 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 1b7a0ee438bb5c2896d0609c537e917d8057b3340f6ad004d2de44f03e3d3cca F src/sqliteInt.h bc1cbc0c23dba35b324ae85a7dbb5fb182321bbd30857fb21f3d0cba049001a5 @@ -1619,6 +1619,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 c1437f778af91ae3dcb705225c09564a84197626f93de4725233c4ab6ac6daf0 F test/shell1.test eda2e527435f139224dda67db6bbd2466597408d4fe5883d647d67fa32d88f7c F test/shell2.test dc541d2681503e55466a24d35a4cbf8ca5b90b8fcdef37fc4db07373a67d31d3 F test/shell3.test 91efdd545097a61a1f72cf79c9ad5b49da080f3f10282eaf4c3c272cd1012db2 @@ -1629,7 +1630,7 @@ F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d F test/shell8.test 38c9e4d7e85d2a3ecfacaa9f6cda4f7a81bf4fffb5f3f37f9cd76827c6883192 F test/shell9.test c0e8871061a92151450b3332279a893b516fa73a6c46d4f51a0998407cbf8c89 F test/shellA.test 05cdaafa1f79913654487ce3aefa038d4106245d58f52e02faf506140a76d480 -F test/shellB.test 31df04230f6062069bb7c5d0e5c5439ca44448fa9da1a55aa461a4b872fe6bd9 +F test/shellB.test a42be39e2332877d8ee239e1ca2a78d0a1feda21d90f3dd50f32f26cc3b433de F test/shmlock.test 9f1f729a7fe2c46c88b156af819ac9b72c0714ac6f7246638a73c5752b5fd13c F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 @@ -2197,9 +2198,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 8574e5c9da3b167384faafb8ec0d01953578676cc42536bb6f7d0e73408ced36 -Q +7c9998de1b839cfbe12c4328ac5176f5fae981e139c5f8888b100752f5e4756b -R da0f6a1d55574e9d7c03a0b245667295 -U stephan -Z 0c5cea2ef0a7a704f084f44ad2fea285 +P 149200861e4690dab5f2e690b62f355b13a8370dccaab5546caa6842b4ff963a 3b4cc8d3beab8c910dd954bf8093f7ab499e4690b9cf882f981c4415dd7a7540 +R 865ef32ca21a6acb62eb499392cf595d +T +closed 3b4cc8d3beab8c910dd954bf8093f7ab499e4690b9cf882f981c4415dd7a7540 +U drh +Z adff825d993df698cc251e509641206b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index e603e903df..2bba9592bf 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -149200861e4690dab5f2e690b62f355b13a8370dccaab5546caa6842b4ff963a +55af98054e073d6eee3e85fd7564c83eca935ae205da68f5e8f1b561e7f08c00 diff --git a/src/complete.c b/src/complete.c index bb2c03098e..08496366a8 100644 --- a/src/complete.c +++ b/src/complete.c @@ -49,12 +49,51 @@ extern const char sqlite3IsEbcdicIdChar[]; #endif /* -** Return TRUE if the given SQL string ends in a semicolon. +** Return zero if the given SQL string is complete - if all comments, +** string and blob literals, and quoted identifiers have been closed and +** if the entire string ends with ";" and possible with ";END;" if the +** string is a CREATE TRIGGER statement. A non-zero return indicates +** that the string is incomplete. Bits of the return value indicate +** what is missing and is needed to close out the statement. ** ** Special handling is require for CREATE TRIGGER statements. ** Whenever the CREATE TRIGGER keywords are seen, the statement ** must end with ";END;". ** +** Let the return code be a value R. R is split up into various +** subfields, at byte boundaries: +** +** R = 0xwwwwwwww00xxyyzz +** +** In other words, zz is the least significant byte, yy is the next +** most significant byte, xx is the third byte, wwwwwwww is a 32-bit +** value from the middle. +** +** zz == SQLITE_OK Input is complete +** zz == SQLITE_ERROR Input is incomplete +** zz == SQLITE_MISUSE Input is a NULL pointer +** zz != 0 New values for zz may be added in the future +** +** yy == 0x01 Need a semicolon at the end +** yy == 0x02 Need "END" and a semicolon +** yy == 0x03 Need semicolon, "END", and semicolon +** yy != 0 New values for yy may be added in the future +** +** xx == '\'' Incomplete string or blob literal +** xx == '"' Incomplete quoted identifier +** xx == '`' Incompelte MySQL-style quoted identifier +** xx == ']' Incomplete SQLServer-style quoted identifer +** xx == '-' Incomplete SQL-style comment +** xx == '/' Incomplete C-style comment +** xx != 0 New values of xx may be added in the future +** +** wwwwwwww Interpret as a signed integer, the number +** of unmatched "(". Negative means there are +** more ")" and "(". +** +** ((R>>24)&0xff)!=0 New uses for the 4th byte may be added +** in the future +** ** This implementation uses a state machine with 8 states: ** ** (0) INVALID We have not yet seen a non-whitespace character. @@ -101,9 +140,11 @@ extern const char sqlite3IsEbcdicIdChar[]; ** to recognize the end of a trigger can be omitted. All we have to do ** is look for a semicolon that is not part of an string or comment. */ -int sqlite3_complete(const char *zSql){ +sqlite3_int64 sqlite3_incomplete(const char *zSql){ u8 state = 0; /* Current state, using numbers defined in header comment */ u8 token; /* Value of the next token */ + u8 pending = 0; /* unmatched structure character */ + int nParen = 0; /* Nested parentheses */ #ifndef SQLITE_OMIT_TRIGGER /* A complex statement machine used to detect the end of a CREATE TRIGGER @@ -133,11 +174,21 @@ int sqlite3_complete(const char *zSql){ /* 2 NORMAL: */ { 1, 2, 2, }, }; #endif /* SQLITE_OMIT_TRIGGER */ + /* Mapping state number to yy value for the return */ + static const u8 statemap[8] = { + /* 0 INVALID */ 1, + /* 1 START */ 0, + /* 2 NORMAL */ 1, + /* 3 EXPLAIN */ 1, + /* 4 CREATE */ 1, + /* 5 TRIGGER */ 3, + /* 6 SEMI */ 2, + /* 7 END */ 1, + }; #ifdef SQLITE_ENABLE_API_ARMOR if( zSql==0 ){ - (void)SQLITE_MISUSE_BKPT; - return 0; + return SQLITE_MISUSE_BKPT; } #endif @@ -162,7 +213,10 @@ int sqlite3_complete(const char *zSql){ } zSql += 2; while( zSql[0] && (zSql[0]!='*' || zSql[1]!='/') ){ zSql++; } - if( zSql[0]==0 ) return 0; + if( zSql[0]==0 ){ + pending = '/'; + goto incomplete_finish; + } zSql++; token = tkWS; break; @@ -173,14 +227,20 @@ int sqlite3_complete(const char *zSql){ break; } while( *zSql && *zSql!='\n' ){ zSql++; } - if( *zSql==0 ) return state==1; + if( *zSql==0 ){ + if( state!=1 ) pending = '-'; + goto incomplete_finish; + } token = tkWS; break; } case '[': { /* Microsoft-style identifiers in [...] */ zSql++; while( *zSql && *zSql!=']' ){ zSql++; } - if( *zSql==0 ) return 0; + if( *zSql==0 ){ + pending = ']'; + goto incomplete_finish; + } token = tkOTHER; break; } @@ -190,7 +250,20 @@ int sqlite3_complete(const char *zSql){ int c = *zSql; zSql++; while( *zSql && *zSql!=c ){ zSql++; } - if( *zSql==0 ) return 0; + if( *zSql==0 ){ + pending = c; + goto incomplete_finish; + } + token = tkOTHER; + break; + } + case '(': { + nParen++; + token = tkOTHER; + break; + } + case ')': { + nParen--; token = tkOTHER; break; } @@ -257,7 +330,15 @@ int sqlite3_complete(const char *zSql){ state = trans[state][token]; zSql++; } - return state==1; +incomplete_finish: + if( state==1 ) nParen = 0; + return (((i64)nParen)<<32) | + ((i64)pending<<16) | + ((i64)statemap[state]<<8) | + (state!=1); +} +int sqlite3_complete(const char *zSql){ + return sqlite3_incomplete(zSql)==0; } #ifndef SQLITE_OMIT_UTF16 @@ -279,7 +360,7 @@ int sqlite3_complete16(const void *zSql){ sqlite3ValueSetStr(pVal, -1, zSql, SQLITE_UTF16NATIVE, SQLITE_STATIC); zSql8 = sqlite3ValueText(pVal, SQLITE_UTF8); if( zSql8 ){ - rc = sqlite3_complete(zSql8); + rc = sqlite3_incomplete(zSql8)==0; }else{ rc = SQLITE_NOMEM_BKPT; } diff --git a/src/shell.c.in b/src/shell.c.in index 9f67675a15..095b666da9 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -355,7 +355,6 @@ typedef struct Mode { #define MFLG_CRLF 0x02 /* Use CR/LF output line endings */ #define MFLG_HDR 0x04 /* .header used to change headers on/off */ - /* ** State information about the database connection is contained in an ** instance of the following structure. @@ -397,10 +396,9 @@ struct ShellState { char *zDestTable; /* Name of destination table when MODE_Insert */ char *zTempFile; /* Temporary file that might need deleting */ char *zErrPrefix; /* Alternative error message prefix */ - char zTestcase[30]; /* Name of current test case */ - char outfile[FILENAME_MAX]; /* Filename for *out */ sqlite3_stmt *pStmt; /* Current statement if any. */ FILE *pLog; /* Write log output here */ + char *azPrompt[2]; /* Main and continuation prompt strings */ Mode mode; /* Current display mode */ Mode modePrior; /* Backup */ struct SavedMode { /* Ability to define custom mode configurations */ @@ -412,6 +410,7 @@ struct ShellState { sqlite3 *db; /* Connection pointer */ const char *zDbFilename; /* Filename used to open the connection */ char *zFreeOnClose; /* Free this memory allocation on close */ + int mFlgs; /* 0x001: Use zDbFilenaem for prompt */ #if defined(SQLITE_ENABLE_SESSION) int nSession; /* Number of active sessions */ OpenSession aSession[4]; /* Array of sessions. [0] is in focus. */ @@ -438,6 +437,8 @@ struct ShellState { const char * zDefaultDbName; /* Default name for db file */ } wasm; #endif + char zTestcase[30]; /* Name of current test case */ + char outfile[FILENAME_MAX]; /* Filename for *out */ }; #ifdef SQLITE_SHELL_FIDDLE @@ -674,16 +675,6 @@ static volatile int seenInterrupt = 0; */ static char *Argv0; -/* -** Prompt strings. Initialized in main. Settable with -** .prompt main continue -*/ -#define PROMPT_LEN_MAX 128 -/* First line prompt. default: "sqlite> " */ -static char mainPrompt[PROMPT_LEN_MAX]; -/* Continuation prompt. default: " ...> " */ -static char continuePrompt[PROMPT_LEN_MAX]; - /* ** Write I/O traces to the following stream. */ @@ -797,114 +788,6 @@ static sqlite3_int64 timeOfDay(void){ #endif } - - -/* This is variant of the standard-library strncpy() routine with the -** one change that the destination string is always zero-terminated, even -** if there is no zero-terminator in the first n-1 characters of the source -** string. -*/ -static char *shell_strncpy(char *dest, const char *src, size_t n){ - size_t i; - for(i=0; iinParenLevel += ni; - if( ni==0 ) p->inParenLevel = 0; - p->zScannerAwaits = 0; -} - -/* Record that a lexeme is opened, or closed with args==0. */ -static void setLexemeOpen(struct DynaPrompt *p, char *s, char c){ - if( s!=0 || c==0 ){ - p->zScannerAwaits = s; - p->acAwait[0] = 0; - }else{ - p->acAwait[0] = c; - p->zScannerAwaits = p->acAwait; - } -} - -/* Upon demand, derive the continuation prompt to display. */ -static char *dynamicContinuePrompt(void){ - if( continuePrompt[0]==0 - || (dynPrompt.zScannerAwaits==0 && dynPrompt.inParenLevel == 0) ){ - return continuePrompt; - }else{ - if( dynPrompt.zScannerAwaits ){ - size_t ncp = strlen(continuePrompt); - size_t ndp = strlen(dynPrompt.zScannerAwaits); - if( ndp > ncp-3 ) return continuePrompt; - shell_strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits); - while( ndp<3 ) dynPrompt.dynamicPrompt[ndp++] = ' '; - shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, - PROMPT_LEN_MAX-4); - }else{ - if( dynPrompt.inParenLevel>9 ){ - shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4); - }else if( dynPrompt.inParenLevel<0 ){ - shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4); - }else{ - shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4); - dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel); - } - shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, - PROMPT_LEN_MAX-4); - } - } - return dynPrompt.dynamicPrompt; -} -#endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */ - /* Indicate out-of-memory and exit. */ static void shell_out_of_memory(void){ eputz("Error: out of memory\n"); @@ -1024,12 +907,252 @@ static char *local_getline(char *zLine, FILE *in){ return zLine; } +/* +** Return the raw (unexpanded) prompt string. This will be the +** first of the following that exist: +** +** * The prompt string specified by the ".prompt" command. +** * The value of the SQLITE_PS1 (or SQLITE_PS2) environment variable. +** * The default prompt string. +** +** The main prompt is returned if the argument is zero and the +** continuation prompt is returned if the argument is 1. +*/ +static const char *prompt_string(ShellState *p, int bContinue){ + const char *zPS; + assert( bContinue==0 || bContinue==1 ); + if( p->azPrompt[bContinue] ){ + return p->azPrompt[bContinue]; + } +#ifndef SQLITE_SHELL_FIDDLE + zPS = getenv(bContinue ? "SQLITE_PS2" : "SQLITE_PS1"); + if( zPS ) return zPS; +#endif + if( bContinue ){ + return "\\B...\\H> "; + }else{ + return "SQLite \\f> "; + } +} + +/* +** Return the name of the open database file, to be used for prompt +** expansion purposes. +*/ +static const char *prompt_filename(ShellState *p){ + sqlite3_filename pFN; + const char *zFN = 0; + if( p->pAuxDb->mFlgs & 0x01 ){ + zFN = p->pAuxDb->zDbFilename; + }else if( p->db && (pFN = sqlite3_db_filename(p->db,0))!=0 ){ + zFN = sqlite3_filename_database(pFN); + } + if( zFN==0 || zFN[0]==0 ){ + zFN = "in-memory"; + } + return zFN; +} + +/* +** Expand escapes in the given input prompt string. Return the +** expanded prompt in memory obtained from sqlite3_malloc(). The +** caller is responsible for freeing the memory. +*/ +static char *expand_prompt( + ShellState *p, /* The current shell state */ + const char *zPrior, /* Prior input lines not yet processed */ + const char *zPrompt /* Prompt string to be expanded */ +){ + sqlite3_str *pOut = sqlite3_str_new(0); + int i; + char c; + int onoff = 1; + int idxSpace = -1; + for(i=0; zPrompt[i]; i++){ + if( zPrompt[i]!='\\' ) continue; + if( i>0 ){ + if( onoff ) sqlite3_str_append(pOut, zPrompt, i); + zPrompt += i; + i = 0; + } + /* At this point zPrompt[0] is a \ character and all prior + ** characters have already been loaded into pOut. Process the + ** escape sequence that zPrompt points to. */ + c = zPrompt[1]; + if( c==0 ){ + /* \ at the end of a line is silently ignored */ + break; + } + if( c=='\\' ){ + /* \\ maps into a single \ */ + zPrompt++; + continue; + } + if( c>='0' && c<='7' ){ + /* \nnn becomes a single byte given by octal nnn */ + int v = c - '0'; + while( i<=2 && zPrompt[i+1]>='0' && zPrompt[i+1]<='7' ){ + v = v*8 + zPrompt[++i] - '0'; + } + if( onoff ) sqlite3_str_appendchar(pOut, 1, v); + zPrompt += i+1; + i = -1; + continue; + } + if( c=='e' ){ + /* \e is \033 "Escape" */ + if( onoff ) sqlite3_str_append(pOut, "\033", 1); + zPrompt += 2; + i = -1; + continue; + } + + /* The intent of the following codes it to provide alternative + ** text displays depending on whether or not the connection is + ** currently in a transaction. Example 1: Show a "*" before the ">" + ** like psql: + ** + ** .prompt 'sqlite\x*\;> ' + ** + ** Example 2: Show database filename is blue if not in a transaction, + ** or red if within a transaction: + ** + ** .prompt '\e[1;\x31\:34\;m\f>\e[0m ' + */ + if( c==':' ){ + /* toggle display on/off */ + onoff = !onoff; + zPrompt += 2; + i = -1; + continue; + } + if( c==';' ){ + /* Turn display on */ + onoff = 1; + zPrompt += 2; + i = -1; + continue; + } + if( c=='x' ){ + /* \x turns display off not in a transaction, on if in txn */ + onoff = p->db && !sqlite3_get_autocommit(p->db); + zPrompt += 2; + i = -1; + continue; + } + + if( c=='f' || c=='F' || c=='~' ){ + /* \f becomes the tail of the database filename */ + /* \F becomes the full pathname */ + /* \~ becomes the full pathname relative to $HOME */ + if( onoff ){ + const char *zFN = prompt_filename(p); + if( c=='f' ){ +#ifdef _WIN32 + const char *zTail = strrchr(zFN,'\\'); +#else + const char *zTail = strrchr(zFN,'/'); +#endif + if( zTail && zTail[1] ) zFN = &zTail[1]; + }else if( c=='~' ){ + const char *zHOME = getenv("HOME"); + size_t nHOME = zHOME ? strlen(zHOME) : 0; + if( nHOME>16)&0xff; + int nParen = R>>32; + int eSemi = (R>>8)&0xff; + if( cc==0 ){ + /* no-op */ + }else if( cc=='-' ){ + sqlite3_str_append(pOut,"\\n",3); + }else if( cc=='/' ){ + sqlite3_str_append(pOut,"*/",2); + }else{ + sqlite3_str_appendchar(pOut, 1, cc); + } + if( nParen>0 ){ + sqlite3_str_appendf(pOut, "%.*c",nParen,')'); + } + if( eSemi==1 ){ + sqlite3_str_append(pOut, ";", 1); + }else if( eSemi==2 ){ + sqlite3_str_append(pOut, "END;", 4); + }else if( eSemi==3 ){ + sqlite3_str_append(pOut, ";END;", 5); + } + } + zPrompt += 2; + i = -1; + continue; + } + + if( c=='B' ){ + /* \B is a no-op for the main prompt. For the continuation prompt, + ** \B expands to zero or more spaces to make the continuation prompt + ** at least as wide as the main prompt. */ + if( onoff ) idxSpace = sqlite3_str_length(pOut); + zPrompt += 2; + i = -1; + continue; + } + + /* No match to a known escape. Generate an error. */ + if( onoff ) sqlite3_str_appendf(pOut,"UNKNOWN(\"\\%c\")",c); + zPrompt += 2; + i = -1; + } + if( i>0 && onoff ){ + sqlite3_str_append(pOut, zPrompt, i); + } + + /* Expand the \B, if there is one and if this is a continuation prompt */ + if( idxSpace>=0 && zPrior!=0 && zPrior[0]!=0 ){ + char *zOther = expand_prompt(p, 0, prompt_string(p,0)); + size_t wOther = sqlite3_qrf_wcswidth(zOther); + size_t wThis = sqlite3_qrf_wcswidth(sqlite3_str_value(pOut)); + sqlite3_free(zOther); + if( wOther>wThis ){ + char *z; + int len; + size_t nNew = wOther - wThis; + assert( nNew>0 && nNew<0x7fffffff ); + sqlite3_str_appendchar(pOut, (int)nNew, ' '); + len = sqlite3_str_length(pOut); + if( len>idxSpace ){ + z = sqlite3_str_value(pOut); + memmove(z+idxSpace+nNew, z+idxSpace, len-nNew-idxSpace); + memset(z+idxSpace, ' ', nNew); + } + } + } + + return sqlite3_str_finish(pOut); +} + /* ** Retrieve a single line of input text. ** ** If in==0 then read from standard input and prompt before each line. -** If isContinuation is true, then a continuation prompt is appropriate. -** If isContinuation is zero, then the main prompt should be used. +** If bContinue is true, then a continuation prompt is appropriate. +** If bContinue is zero, then the main prompt should be used. ** ** If zPrior is not NULL then it is a buffer from a prior call to this ** routine that can be reused. @@ -1039,14 +1162,20 @@ static char *local_getline(char *zLine, FILE *in){ ** zPrior argument for reuse. */ #ifndef SQLITE_SHELL_FIDDLE -static char *one_input_line(ShellState *p, char *zPrior, int isContinuation){ - char *zPrompt; +static char *one_input_line( + ShellState *p, /* Shell state */ + char *zPrior, /* Just the previous line */ + const char *zAll, /* All statement text accumulated so far */ + int bContinue /* True for a continuation prompt */ +){ char *zResult; FILE *in = p->in; if( in!=0 ){ zResult = local_getline(zPrior, in); }else{ - zPrompt = isContinuation ? CONTINUATION_PROMPT : mainPrompt; + const char *zBase = prompt_string(p, bContinue!=0); + char *zPrompt = expand_prompt(p, zAll, zBase); + shell_check_oom(zPrompt); #if SHELL_USE_LOCAL_GETLINE sputz(stdout, zPrompt); fflush(stdout); @@ -1067,6 +1196,7 @@ static char *one_input_line(ShellState *p, char *zPrior, int isContinuation){ } if( zResult && *zResult ) shell_add_history(zResult); #endif + sqlite3_free(zPrompt); } return zResult; } @@ -1387,6 +1517,54 @@ static void shellAddSchemaName( sqlite3_result_value(pCtx, apVal[0]); } +/* +** SQL function: shell_prompt_test(PROMPT) +** shell_prompt_test(PROMPT,PRIOR) +** shell_prompt_test(PROMPT,PRIOR,FILENAME) +** +** Return the shell prompt, with escapes expanded, for testing purposes. +** The first argument is the raw (unexpanded) prompt string. Or if the +** first argument is NULL, then use whatever prompt string is currently +** 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. +*/ +static void shellExpandPrompt( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + ShellState *p = (ShellState*)sqlite3_user_data(pCtx); + const char *zPrompt; + const char *zPrior; + const char *zSavedDbFile; + int mSavedFlgs; + const char *zFName; + char *zRes; + + if( nVal<2 + || (zPrior = (const char*)sqlite3_value_text(apVal[1]))==0 + || zPrior[0]==0 + ){ + zPrior = 0; + } + zPrompt = (const char*)sqlite3_value_text(apVal[0]); + if( zPrompt==0 ){ + zPrompt = prompt_string(p, zPrior!=0); + } + zSavedDbFile = p->pAuxDb->zDbFilename; + mSavedFlgs = p->pAuxDb->mFlgs; + if( nVal>=3 && (zFName = (const char*)sqlite3_value_text(apVal[2]))!=0 ){ + p->pAuxDb->zDbFilename = zFName; + p->pAuxDb->mFlgs |= 0x001; + } + zRes = expand_prompt(p, zPrior, zPrompt); + p->pAuxDb->zDbFilename = zSavedDbFile; + p->pAuxDb->mFlgs = mSavedFlgs; + sqlite3_result_text(pCtx, zRes, -1, SQLITE_TRANSIENT); + sqlite3_free(zRes); +} /************************* BEGIN PERFORMANCE TIMER *****************************/ #if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) @@ -3864,7 +4042,10 @@ static const char *(azHelp[]) = { " --reset Reset the count for each input and interrupt", " --timeout S Halt after running for S seconds", #endif - ".prompt MAIN CONTINUE Replace the standard prompts", + ".prompt MAIN CONTINUE Replace the standard prompts", + " --reset Revert to default prompts", + " --show Show the current prompt strings", + " -- No more options. Subsequent args are prompts", #ifndef SQLITE_SHELL_FIDDLE ".quit Stop interpreting input stream, exit if primary.", ".read FILE Read input from FILE or command output", @@ -4476,6 +4657,31 @@ static void shellModuleSchema( static void open_db(ShellState *p, int openFlags){ if( p->db==0 ){ const char *zDbFilename = p->pAuxDb->zDbFilename; + const char *zHOME; /* Value of HOME environment variable */ + char *zToFree = 0; /* Filename with ~/ prefix expansion */ + + /* If the zDbFilename does not exist and begins with ~/ then replace + ** the ~ with the home directory. + */ + if( zDbFilename && zDbFilename[0]=='~' +#ifdef _WIN32 + && (zDbFilename[1]=='/' || zDbFilename[1]=='\\') +#else + && zDbFilename[1]=='/' +#endif + && access(zDbFilename,0)!=0 + && (zHOME = getenv("HOME"))!=0 && zHOME[0]!=0 + ){ + size_t nHOME = strlen(zHOME); + size_t nFN = strlen(zDbFilename); + zToFree = malloc(nHOME+nFN+1); + if( zToFree ){ + memcpy(zToFree, zHOME, nHOME); + memcpy(zToFree+nHOME, zDbFilename+1, nFN); + zDbFilename = zToFree; + } + } + if( p->openMode==SHELL_OPEN_UNSPEC ){ if( zDbFilename==0 || zDbFilename[0]==0 ){ p->openMode = SHELL_OPEN_NORMAL; @@ -4609,6 +4815,13 @@ 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, + p, shellExpandPrompt, 0, 0); + if( p->openMode==SHELL_OPEN_ZIPFILE ){ char *zSql = sqlite3_mprintf( @@ -4642,6 +4855,7 @@ static void open_db(ShellState *p, int openFlags){ } } #endif + free(zToFree); } if( p->db!=0 ){ #ifndef SQLITE_OMIT_AUTHORIZATION @@ -10436,12 +10650,39 @@ static int do_meta_command(const char *zLine, ShellState *p){ #endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ if( c=='p' && cli_strncmp(azArg[0], "prompt", n)==0 ){ - if( nArg >= 2) { - shell_strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1); - } - if( nArg >= 3) { - shell_strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1); - } + int i; + int cnt = 0; + int noOpt = 0; + for(i=1; iazPrompt[0]); + free(p->azPrompt[1]); + memset(p->azPrompt, 0, sizeof(p->azPrompt)); + }else + if( strcmp(z,"-show")==0 ){ + cli_printf(stdout,"Main prompt: '%s'\n", prompt_string(p, 0)); + cli_printf(stdout,"Continuation: '%s'\n", prompt_string(p, 1)); + }else + if( strcmp(z,"-")==0 ){ + noOpt = 1; + }else + { + dotCmdError(p, i, "unknown option", 0); + rc = 1; + goto meta_command_exit; + } + }else if( cnt>1 ){ + dotCmdError(p, i, "extra argument", 0); + rc = 1; + goto meta_command_exit; + }else{ + free(p->azPrompt[cnt]); + p->azPrompt[cnt] = strdup(z); + } + } }else #ifndef SQLITE_SHELL_FIDDLE @@ -12074,104 +12315,28 @@ meta_command_exit: return rc; } -/* Line scan result and intermediate states (supporting scan resumption) -*/ -#ifndef CHAR_BIT -# define CHAR_BIT 8 -#endif -typedef enum { - QSS_HasDark = 1<in; UNUSED_PARAMETER(in); - UNUSED_PARAMETER(isContinuation); + UNUSED_PARAMETER(bContinue); if(!z || !*z){ return 0; } @@ -12420,8 +12591,8 @@ static int process_input(ShellState *p, const char *zSrc){ i64 nAlloc = 0; /* Allocated zSql[] space */ int rc; /* Error code */ int errCnt = 0; /* Number of errors seen */ + int hasSemi = 0; /* Input line contains a semicolon */ i64 startline = 0; /* Line number for start of current input */ - QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */ const char *saved_zInFile; /* Prior value of p->zInFile */ i64 saved_lineno; /* Prior value of p->lineno */ @@ -12436,10 +12607,9 @@ static int process_input(ShellState *p, const char *zSrc){ p->zInFile = zSrc; saved_lineno = p->lineno; p->lineno = 0; - CONTINUE_PROMPT_RESET; while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ fflush(p->out); - zLine = one_input_line(p, zLine, nSql>0); + zLine = one_input_line(p, zLine, zSql, nSql>0); if( zLine==0 ){ /* End of input */ if( p->in==0 && stdin_is_interactive ) cli_puts("\n", p->out); @@ -12450,20 +12620,12 @@ static int process_input(ShellState *p, const char *zSrc){ seenInterrupt = 0; } p->lineno++; - if( QSS_INPLAIN(qss) - && line_is_command_terminator(zLine) - && line_is_complete(zSql, nSql) ){ - memcpy(zLine,";",2); - } - qss = quickscan(zLine, qss, CONTINUE_PROMPT_PSTATE); - if( QSS_PLAINWHITE(qss) && nSql==0 ){ + if( nSql==0 && line_is_all_whitespace(zLine) ){ /* Just swallow single-line whitespace */ echo_group_input(p, zLine); - qss = QSS_Start; continue; } if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){ - CONTINUE_PROMPT_RESET; echo_group_input(p, zLine); if( zLine[0]=='.' ){ rc = do_meta_command(zLine, p); @@ -12473,10 +12635,12 @@ static int process_input(ShellState *p, const char *zSrc){ errCnt++; } } - qss = QSS_Start; continue; } - /* No single-line dispositions remain; accumulate line(s). */ + if( line_is_command_terminator(zLine) && line_is_complete(zSql, nSql) ){ + memcpy(zLine,";",2); + } + hasSemi = strchr(zLine,';')!=0; nLine = strlen(zLine); if( nSql+nLine+2>=nAlloc ){ /* Grow buffer by half-again increments when big. */ @@ -12504,10 +12668,10 @@ static int process_input(ShellState *p, const char *zSrc){ nSql = 0; errCnt++; break; - }else if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){ + } + if( nSql && hasSemi && sqlite3_complete(zSql) ){ echo_group_input(p, zSql); errCnt += runOneSqlLine(p, zSql, p->zInFile, startline); - CONTINUE_PROMPT_RESET; nSql = 0; if( p->nPopOutput ){ output_reset(p); @@ -12520,18 +12684,15 @@ static int process_input(ShellState *p, const char *zSrc){ p->nPopMode = 0; } p->bSafeMode = p->bSafeModePersist; - qss = QSS_Start; - }else if( nSql && QSS_PLAINWHITE(qss) ){ + }else if( nSql && line_is_all_whitespace(zSql) ){ echo_group_input(p, zSql); nSql = 0; - qss = QSS_Start; } } if( nSql ){ /* This may be incomplete. Let the SQL parser deal with that. */ echo_group_input(p, zSql); errCnt += runOneSqlLine(p, zSql, p->zInFile, startline); - CONTINUE_PROMPT_RESET; } free(zSql); free(zLine); @@ -12819,8 +12980,6 @@ static void main_init(ShellState *p) { #endif sqlite3_config(SQLITE_CONFIG_URI, 1); sqlite3_config(SQLITE_CONFIG_MULTITHREAD); - sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> "); - sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> "); } /* @@ -13558,7 +13717,6 @@ int SQLITE_CDECL main(int argc, char **argv){ if( rc ){ goto shell_main_exit; } - } if( data.nPopOutput && azCmd[i][0]!='.' ){ output_reset(&data); @@ -13658,6 +13816,8 @@ int SQLITE_CDECL main(int argc, char **argv){ free(data.dot.azArg); free(data.dot.aiOfst); free(data.dot.abQuot); + free(data.azPrompt[0]); + free(data.azPrompt[1]); if( data.nTestRun ){ sqlite3_fprintf(stdout, "%d test%s run with %d error%s\n", data.nTestRun, data.nTestRun==1 ? "" : "s", @@ -13817,4 +13977,8 @@ void fiddle_exec(const char * zSql){ shellState.wasm.zInput = shellState.wasm.zPos = 0; } } + +char *fiddle_get_prompt(void){ + return expand_prompt(&shellState, 0, prompt_string(&shellState,0)); +} #endif /* SQLITE_SHELL_FIDDLE */ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 9323ce5eea..b4ed739a61 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -2960,8 +2960,9 @@ int sqlite3_is_interrupted(sqlite3*); ** These routines are useful during command-line input to determine if the ** currently entered text seems to form a complete SQL statement or ** if additional input is needed before sending the text into -** SQLite for parsing. ^These routines return 1 if the input string -** appears to be a complete SQL statement. ^A statement is judged to be +** SQLite for parsing. ^The sqlite3_complete(X) and sqlite3_complete16(X) +** routines return 1 if the input string X appears to be a complete SQL +** statement. ^A statement is judged to be ** complete if it ends with a semicolon token and is not a prefix of a ** well-formed CREATE TRIGGER statement. ^Semicolons that are embedded within ** string literals or quoted identifier names or comments are not @@ -2969,11 +2970,21 @@ int sqlite3_is_interrupted(sqlite3*); ** embedded) and thus do not count as a statement terminator. ^Whitespace ** and comments that follow the final semicolon are ignored. ** -** ^These routines return 0 if the statement is incomplete. ^If a -** memory allocation fails, then SQLITE_NOMEM is returned. +** ^The sqlite3_complete(X) and sqlite3_complete16(X) routines return 0 +** if the statement is incomplete. ^If a memory allocation fails, then +** SQLITE_NOMEM is returned. ** -** ^These routines do not parse the SQL statements and thus -** will not detect syntactically incorrect SQL. +** The [sqlite3_incomplete(X)] routine is similar to [sqlite3_complete(X)] +** except that sqlite3_incomplete(X) returns 0 if the input X is complete +** and non-zero if X is incomplete. The non-zero return from +** sqlite3_incomplete(X) contains additional information about what is +** needed to complete the input X. The sqlite3_incomplete(X) interface +** is only available for UTF-8 text. +** +** ^None of these routines do a full parse the SQL statements and thus +** will not detect syntactically incorrect SQL. They only determine if +** input text has properly terminated comments, string literals, and +** quoted identifiers, and if the statement ends with a semicolon. ** ** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior ** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked @@ -2981,14 +2992,15 @@ int sqlite3_is_interrupted(sqlite3*); ** then the return value from sqlite3_complete16() will be non-zero ** regardless of whether or not the input SQL is complete.)^ ** -** The input to [sqlite3_complete()] must be a zero-terminated -** UTF-8 string. +** The X input to [sqlite3_complete(X)] and [sqlite3_incomplete(X) +** must be a zero-terminated UTF-8 string. ** ** The input to [sqlite3_complete16()] must be a zero-terminated ** UTF-16 string in native byte order. */ int sqlite3_complete(const char *sql); int sqlite3_complete16(const void *sql); +sqlite3_int64 sqlite3_incomplete(const char *sql); /* ** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors diff --git a/test/shell-prompt.sql b/test/shell-prompt.sql new file mode 100644 index 0000000000..215b54485f --- /dev/null +++ b/test/shell-prompt.sql @@ -0,0 +1,84 @@ +#!sqlite3 +# +# 2026-04-11 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Tests for the .prompt command and prompt rendering. +# +# ./sqlite3 test/shell-prompt.sql +# + +.testcase setup +.open -new test.db +.mode list -quote off -escape ascii +.check '' + +.testcase 100 +.prompt +.check '' + +.testcase 110 +.prompt --show +.check < ' +Continuation: '\B...\H> ' +END + +.testcase 1000 +SELECT shell_prompt_test(NULL); +.check 'SQLite test.db> '; +.testcase 1001 +SELECT shell_prompt_test(NULL,'SELECT'); +.check ' ...;> '; +.testcase 1002 +SELECT shell_prompt_test(NULL,'SELECT ((("'); +.check ' ...")));> '; +.testcase 1003 +SELECT shell_prompt_test(NULL,'SELECT ((()['); +.check ' ...]));> '; +.testcase 1004 +SELECT shell_prompt_test(NULL,'SELECT '''); +.check " ...';> "; +.testcase 1005 +SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN'); +.check " ...;END;> "; +.testcase 1006 +SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN SELECT (((['); +.check " ...])));END;> "; +.testcase 1007 +SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN SELECT ((/*a(((''bc'); +.check " ...*/));END;> "; +.testcase 1008 +SELECT shell_prompt_test(NULL,'CREATE TRIGGER t1 BEGIN SELECT 1;'); +.check " ...END;> "; + +.testcase 2000 +.prompt 'SQLite\x-txn$\:>\; ' +SELECT shell_prompt_test(NULL); +.check 'SQLite> '; +.testcase 2001 +BEGIN; +SELECT shell_prompt_test(NULL); +.check 'SQLite-txn$ '; +.testcase 2002 +ROLLBACK; +SELECT shell_prompt_test(NULL); +.check 'SQLite> '; +.testcase 2003 +.prompt -- '--show ' +SELECT shell_prompt_test(NULL); +.check '--show '; +.testcase 2004 +.prompt --reset +SELECT shell_prompt_test(NULL); +.check 'SQLite test.db> '; + +.testcase diff --git a/test/shellB.test b/test/shellB.test index 5be2f7b6da..566a7bc19e 100644 --- a/test/shellB.test +++ b/test/shellB.test @@ -49,5 +49,6 @@ ifcapable vtab { do_clitest intck01.sql } do_clitest fptest01.sql +do_clitest shell-prompt.sql finish_test