Skip to content

Commit 3117c19

Browse files
committed
Support basic completion for sqlite3 command-line interface
1 parent 95d2a81 commit 3117c19

File tree

1 file changed

+57
-6
lines changed

1 file changed

+57
-6
lines changed

Lib/sqlite3/__main__.py

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from argparse import ArgumentParser
1111
from code import InteractiveConsole
12+
from contextlib import contextmanager
1213
from textwrap import dedent
1314

1415

@@ -62,6 +63,59 @@ def runsource(self, source, filename="<input>", symbol="single"):
6263
return False
6364

6465

66+
def _complete(text, state):
67+
keywords = ["ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ALWAYS",
68+
"ANALYZE", "AND", "AS", "ASC", "ATTACH", "AUTOINCREMENT",
69+
"BEFORE", "BEGIN", "BETWEEN", "BY", "CASCADE", "CASE", "CAST",
70+
"CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT",
71+
"CONSTRAINT", "CREATE", "CROSS", "CURRENT", "CURRENT_DATE",
72+
"CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT",
73+
"DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DETACH",
74+
"DISTINCT", "DO", "DROP", "EACH", "ELSE", "END", "ESCAPE",
75+
"EXCEPT", "EXCLUDE", "EXCLUSIVE", "EXISTS", "EXPLAIN", "FAIL",
76+
"FILTER", "FIRST", "FOLLOWING", "FOR", "FOREIGN", "FROM",
77+
"FULL", "GENERATED", "GLOB", "GROUP", "GROUPS", "HAVING", "IF",
78+
"IGNORE", "IMMEDIATE", "IN", "INDEX", "INDEXED", "INITIALLY",
79+
"INNER", "INSERT", "INSTEAD", "INTERSECT", "INTO", "IS",
80+
"ISNULL", "JOIN", "KEY", "LAST", "LEFT", "LIKE", "LIMIT",
81+
"MATCH", "MATERIALIZED", "NATURAL", "NO", "NOT", "NOTHING",
82+
"NOTNULL", "NULL", "NULLS", "OF", "OFFSET", "ON", "OR",
83+
"ORDER", "OTHERS", "OUTER", "OVER", "PARTITION", "PLAN",
84+
"PRAGMA", "PRECEDING", "PRIMARY", "QUERY", "RAISE", "RANGE",
85+
"RECURSIVE", "REFERENCES", "REGEXP", "REINDEX", "RELEASE",
86+
"RENAME", "REPLACE", "RESTRICT", "RETURNING", "RIGHT",
87+
"ROLLBACK", "ROW", "ROWS", "SAVEPOINT", "SELECT", "SET",
88+
"TABLE", "TEMP", "TEMPORARY", "THEN", "TIES", "TO",
89+
"TRANSACTION", "TRIGGER", "UNBOUNDED", "UNION", "UNIQUE",
90+
"UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL",
91+
"WHEN", "WHERE", "WINDOW", "WITH", "WITHOUT"]
92+
options = [c + " " for c in keywords if c.startswith(text.upper())]
93+
try:
94+
return options[state]
95+
except IndexError:
96+
return None
97+
98+
@contextmanager
99+
def _enable_completer():
100+
try:
101+
import readline
102+
except ImportError:
103+
yield
104+
return
105+
106+
old_completer = readline.get_completer()
107+
try:
108+
readline.set_completer(_complete)
109+
if readline.backend == "editline":
110+
# libedit uses "^I" instead of "tab"
111+
command_string = "bind ^I rl_complete"
112+
else:
113+
command_string = "tab: complete"
114+
readline.parse_and_bind(command_string)
115+
yield
116+
finally:
117+
readline.set_completer(old_completer)
118+
65119
def main(*args):
66120
parser = ArgumentParser(
67121
description="Python sqlite3 CLI",
@@ -114,12 +168,9 @@ def main(*args):
114168
execute(con, args.sql, suppress_errors=False)
115169
else:
116170
# No SQL provided; start the REPL.
117-
console = SqliteInteractiveConsole(con)
118-
try:
119-
import readline # noqa: F401
120-
except ImportError:
121-
pass
122-
console.interact(banner, exitmsg="")
171+
with _enable_completer():
172+
console = SqliteInteractiveConsole(con)
173+
console.interact(banner, exitmsg="")
123174
finally:
124175
con.close()
125176

0 commit comments

Comments
 (0)