Skip to content
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
8 changes: 8 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
TBD
==============

Features
--------
* Smarter fuzzy completion matches.


1.44.2 (2026/01/13)
==============

Expand Down
29 changes: 23 additions & 6 deletions mycli/sqlcompleter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,7 @@ def reset_completions(self) -> None:

@staticmethod
def find_matches(
text: str,
orig_text: str,
collection: Collection,
start_only: bool = False,
fuzzy: bool = True,
Expand All @@ -1083,24 +1083,41 @@ def find_matches(
yields prompt_toolkit Completion instances for any matches found
in the collection of available completions.
"""
last = last_word(text, include="most_punctuations")
last = last_word(orig_text, include="most_punctuations")
text = last.lower()
# unicode support not possible without adding the regex dependency
case_change_pat = re.compile("(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])")

completions = []

if fuzzy:
regex = ".*?".join(map(re.escape, text))
regex = ".{0,3}?".join(map(re.escape, text))
pat = re.compile(f'({regex})')
under_words_text = [x for x in text.split('_') if x]
case_words_text = re.split(case_change_pat, text)

for item in collection:
r = pat.search(item.lower())
if r:
completions.append((len(r.group()), r.start(), item))
completions.append(item)
continue

under_words_item = [x for x in item.lower().split('_') if x]
if all(x in under_words_item for x in under_words_text):
completions.append(item)
continue

case_words_item = re.split(case_change_pat, item.lower())
if all(x in case_words_item for x in case_words_text):
completions.append(item)
continue

else:
match_end_limit = len(text) if start_only else None
for item in collection:
match_point = item.lower().find(text, 0, match_end_limit)
if match_point >= 0:
completions.append((len(text), match_point, item))
completions.append(item)

if casing == "auto":
casing = "lower" if last and last[-1].islower() else "upper"
Expand All @@ -1110,7 +1127,7 @@ def apply_case(kw: str) -> str:
return kw.upper()
return kw.lower()

return (Completion(z if casing is None else apply_case(z), -len(text)) for x, y, z in completions)
return (Completion(x if casing is None else apply_case(x), -len(text)) for x in completions)

def get_completions(
self,
Expand Down
37 changes: 37 additions & 0 deletions test/test_smart_completion_public_schema_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
"orders": ["id", "ordered_date", "status"],
"select": ["id", "insert", "ABC"],
"réveillé": ["id", "insert", "ABC"],
"time_zone": ["Time_zone_id"],
"time_zone_leap_second": ["Time_zone_id"],
"time_zone_name": ["Time_zone_id"],
"time_zone_transition": ["Time_zone_id"],
"time_zone_transition_type": ["Time_zone_id"],
}


Expand Down Expand Up @@ -82,6 +87,11 @@ def test_table_completion(completer, complete_event):
Completion(text="orders", start_position=0),
Completion(text="`select`", start_position=0),
Completion(text="`réveillé`", start_position=0),
Completion(text="time_zone", start_position=0),
Completion(text="time_zone_leap_second", start_position=0),
Completion(text="time_zone_name", start_position=0),
Completion(text="time_zone_transition", start_position=0),
Completion(text="time_zone_transition_type", start_position=0),
]


Expand Down Expand Up @@ -315,6 +325,33 @@ def test_table_names_after_from(completer, complete_event):
Completion(text="orders", start_position=0),
Completion(text="`select`", start_position=0),
Completion(text="`réveillé`", start_position=0),
Completion(text="time_zone", start_position=0),
Completion(text="time_zone_leap_second", start_position=0),
Completion(text="time_zone_name", start_position=0),
Completion(text="time_zone_transition", start_position=0),
Completion(text="time_zone_transition_type", start_position=0),
]


def test_table_names_leading_partial(completer, complete_event):
text = "SELECT * FROM time_zone"
position = len("SELECT * FROM time_zone")
result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event))
assert result == [
Completion(text="time_zone", start_position=-9),
Completion(text="time_zone_leap_second", start_position=-9),
Completion(text="time_zone_name", start_position=-9),
Completion(text="time_zone_transition", start_position=-9),
Completion(text="time_zone_transition_type", start_position=-9),
]


def test_table_names_inter_partial(completer, complete_event):
text = "SELECT * FROM time_leap"
position = len("SELECT * FROM time_leap")
result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event))
assert result == [
Completion(text="time_zone_leap_second", start_position=-9),
]


Expand Down