Skip to content

bpo-40529: rlcompleter with case insensitive #19957

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Doc/library/rlcompleter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
:synopsis: Python identifier completion, suitable for the GNU readline library.

.. sectionauthor:: Moshe Zadka <[email protected]>
.. sectionauthor:: Madhusudhan Kasula <[email protected]>

**Source code:** :source:`Lib/rlcompleter.py`

Expand Down Expand Up @@ -59,3 +60,19 @@ Completer objects have the following method:
:func:`dir` function. Any exception raised during the evaluation of the
expression is caught, silenced and :const:`None` is returned.


.. _case-sensitivity:

Case Sensitivity
-----------------

You can change the Completer's default case sensitive selection to case insensitive using the following method:


.. function:: rlcompleter.set_ignore_case(option)

Return the *None*.

If called with *True*, it will set rlcompleter for case insensitive completions.

If called with *False*, it will set to default case sensitive completions.
18 changes: 15 additions & 3 deletions Lib/rlcompleter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

readline.parse_and_bind("tab: complete")

And to make completion with case insensitive, call

rlcompleter.set_ignore_case(True)

Notes:

- Exceptions raised by the completer function are *ignored* (and generally cause
Expand Down Expand Up @@ -106,12 +110,13 @@ def global_matches(self, text):
defined in self.namespace that match.

"""
import re
import keyword
matches = []
seen = {"__builtins__"}
n = len(text)
for word in keyword.kwlist:
if word[:n] == text:
if re.match(text, word[:n], flags=_re_ignorecase_flags):
seen.add(word)
if word in {'finally', 'try'}:
word = word + ':'
Expand All @@ -122,7 +127,8 @@ def global_matches(self, text):
matches.append(word)
for nspace in [self.namespace, builtins.__dict__]:
for word, val in nspace.items():
if word[:n] == text and word not in seen:
if (re.match(text, word[:n], flags=_re_ignorecase_flags) and
word not in seen):
seen.add(word)
matches.append(self._callable_postfix(val, word))
return matches
Expand Down Expand Up @@ -166,7 +172,7 @@ def attr_matches(self, text):
noprefix = None
while True:
for word in words:
if (word[:n] == attr and
if (re.match(attr, word[:n], flags=_re_ignorecase_flags) and
not (noprefix and word[:n+1] == noprefix)):
match = "%s.%s" % (expr, word)
try:
Expand All @@ -185,6 +191,12 @@ def attr_matches(self, text):
matches.sort()
return matches

_re_ignorecase_flags = 0
def set_ignore_case(option):
import re
global _re_ignorecase_flags
_re_ignorecase_flags = re.IGNORECASE if option else 0

def get_class_members(klass):
ret = dir(klass)
if hasattr(klass,'__bases__'):
Expand Down
26 changes: 26 additions & 0 deletions Lib/test/test_rlcompleter.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,31 @@ def test_duplicate_globals(self):
self.assertEqual(completer.complete('Ellipsis', 0), 'Ellipsis(')
self.assertIsNone(completer.complete('Ellipsis', 1))

def test_case_insentive_matches(self):
# add an additional attr for testing
CompleteMe.SpAm = 2
# enable the case insensitive option
rlcompleter.set_ignore_case(True)
# test globals
self.assertEqual(self.completer.global_matches('completem'),
['CompleteMe('])
# test attr
self.assertNotEqual(self.completer.attr_matches('CompleteMe.spa'),
['CompleteMe.spam', 'CompleteMe.SpAm'])
# disable the case insensitive option
rlcompleter.set_ignore_case(False)
# test globals
self.assertNotEqual(self.completer.global_matches('completem'),
['CompleteMe('])
self.assertEqual(self.completer.global_matches('CompleteM'),
['CompleteMe('])
# test attr
self.assertNotEqual(self.completer.attr_matches('CompleteMe.spa'),
['CompleteMe.spam', 'CompleteMe.SpAm'])
self.assertEqual(self.completer.attr_matches('CompleteMe.sp'),
['CompleteMe.spam'])
# delete the additional attr
del(CompleteMe.SpAm)

if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added an option to the user to make rl completions with case insensitive / sensitive.