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]