https://github.com/python/cpython/commit/745947cda0377bb44d791b7d8e58b327a9d84844
commit: 745947cda0377bb44d791b7d8e58b327a9d84844
branch: main
author: Tan Long <[email protected]>
committer: erlend-aasland <[email protected]>
date: 2026-03-03T19:48:03+01:00
summary:

gh-135883: Fix sqlite3 CLI history scrolling with colored prompts (#135884)

files:
A Misc/NEWS.d/next/Library/2025-06-24-19-07-18.gh-issue-135883.38cePA.rst
M Lib/sqlite3/__main__.py
M Lib/test/test_sqlite3/test_cli.py

diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py
index b3746ed757332f..8805442b69e080 100644
--- a/Lib/sqlite3/__main__.py
+++ b/Lib/sqlite3/__main__.py
@@ -133,8 +133,11 @@ def main(*args):
     theme = get_theme()
     s = theme.syntax
 
-    sys.ps1 = f"{s.prompt}sqlite> {s.reset}"
-    sys.ps2 = f"{s.prompt}    ... {s.reset}"
+    # Use RL_PROMPT_START_IGNORE (\001) and RL_PROMPT_END_IGNORE (\002) to
+    # bracket non-printing characters. This tells readline to ignore them
+    # when calculating screen space for redisplay during history scrolling.
+    sys.ps1 = f"\001{s.prompt}\002sqlite> \001{s.reset}\002"
+    sys.ps2 = f"\001{s.prompt}\002    ... \001{s.reset}\002"
 
     con = sqlite3.connect(args.filename, isolation_level=None)
     try:
diff --git a/Lib/test/test_sqlite3/test_cli.py 
b/Lib/test/test_sqlite3/test_cli.py
index 98aadaa829a969..1fc0236780fa8b 100644
--- a/Lib/test/test_sqlite3/test_cli.py
+++ b/Lib/test/test_sqlite3/test_cli.py
@@ -80,8 +80,8 @@ def test_cli_on_disk_db(self):
 @force_not_colorized_test_class
 class InteractiveSession(unittest.TestCase):
     MEMORY_DB_MSG = "Connected to a transient in-memory database"
-    PS1 = "sqlite> "
-    PS2 = "... "
+    PS1 = "\001\002sqlite> \001\002"
+    PS2 = "\001\002    ... \001\002"
 
     def run_cli(self, *args, commands=()):
         with (
@@ -202,8 +202,8 @@ def test_interact_on_disk_file(self):
     def test_color(self):
         with unittest.mock.patch("_colorize.can_colorize", return_value=True):
             out, err = self.run_cli(commands="TEXT\n")
-            self.assertIn("\x1b[1;35msqlite> \x1b[0m", out)
-            self.assertIn("\x1b[1;35m    ... \x1b[0m\x1b", out)
+            self.assertIn("\x01\x1b[1;35m\x02sqlite> \x01\x1b[0m\x02", out)
+            self.assertIn("\x01\x1b[1;35m\x02    ... \x01\x1b[0m\x02\x01\x1b", 
out)
             out, err = self.run_cli(commands=("sel;",))
             self.assertIn('\x1b[1;35mOperationalError (SQLITE_ERROR)\x1b[0m: '
                           '\x1b[35mnear "sel": syntax error\x1b[0m', err)
@@ -212,6 +212,10 @@ def test_color(self):
 @requires_subprocess()
 @force_not_colorized_test_class
 class Completion(unittest.TestCase):
+    # run_pty() creates a real terminal environment, where sqlite3 CLI
+    # SqliteInteractiveConsole invokes GNU Readline for input. Readline's
+    # _rl_strip_prompt() strips \001 and \002 from the output, so test
+    # assertions use the plain prompt.
     PS1 = "sqlite> "
 
     @classmethod
diff --git 
a/Misc/NEWS.d/next/Library/2025-06-24-19-07-18.gh-issue-135883.38cePA.rst 
b/Misc/NEWS.d/next/Library/2025-06-24-19-07-18.gh-issue-135883.38cePA.rst
new file mode 100644
index 00000000000000..8f3efceaae1d91
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-24-19-07-18.gh-issue-135883.38cePA.rst
@@ -0,0 +1,2 @@
+Fix :mod:`sqlite3`'s :ref:`interactive shell <sqlite3-cli>` keeping part of
+previous commands when scrolling history.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to