Skip to content
Merged
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
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Bug Fixes
---------
* Watch command now returns correct time when ran as part of a multi-part query (#1565)
* Don't diagnose free-entry sections such as `[favorite_queries]` in `--checkup`.
* When accepting a filename completion, fill in leading `./` if given.


1.54.1 (2026/02/17)
Expand Down
12 changes: 10 additions & 2 deletions mycli/packages/parseutils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import re
from typing import Any, Generator
from typing import Any, Generator, Literal

import sqlglot
import sqlparse
Expand Down Expand Up @@ -34,7 +34,15 @@ def is_valid_connection_scheme(text: str) -> tuple[bool, str | None]:
return True, None


def last_word(text: str, include: str = "alphanum_underscore") -> str:
def last_word(
text: str,
include: Literal[
'alphanum_underscore',
'many_punctuations',
'most_punctuations',
'all_punctuations',
] = 'alphanum_underscore',
) -> str:
r"""
Find the last word in a sentence.

Expand Down
8 changes: 7 additions & 1 deletion mycli/sqlcompleter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,7 @@ def get_completions(
word_before_cursor = document.get_word_before_cursor(WORD=True)
last_for_len = last_word(word_before_cursor, include="most_punctuations")
text_for_len = last_for_len.lower()
last_for_len_paths = last_word(word_before_cursor, include='alphanum_underscore')

if smart_completion is None:
smart_completion = self.smart_completion
Expand All @@ -1088,6 +1089,7 @@ def get_completions(
completions: list[tuple[str, int, int]] = []
suggestions = suggest_type(document.text, document.text_before_cursor)
rigid_sort = False
length_based_on_path = False

rank = 0
for suggestion in suggestions:
Expand Down Expand Up @@ -1196,6 +1198,7 @@ def get_completions(
completions.extend([(*x, rank) for x in file_names_m])
# for filenames we _really_ want directories to go last
rigid_sort = True
length_based_on_path = True
elif suggestion["type"] == "llm":
if not word_before_cursor:
tokens = document.text.split()[1:]
Expand Down Expand Up @@ -1238,7 +1241,10 @@ def completion_sort_key(item: tuple[str, int, int], text_for_len: str):
sorted_completions = sorted(completions, key=lambda item: completion_sort_key(item, text_for_len.lower()))
uniq_completions_str = dict.fromkeys(x[0] for x in sorted_completions)

return (Completion(x, -len(text_for_len)) for x in uniq_completions_str)
if length_based_on_path:
return (Completion(x, -len(last_for_len_paths)) for x in uniq_completions_str)
else:
return (Completion(x, -len(text_for_len)) for x in uniq_completions_str)

def find_files(self, word: str) -> Generator[tuple[str, int], None, None]:
"""Yield matching directory or file names.
Expand Down
32 changes: 27 additions & 5 deletions test/test_smart_completion_public_schema_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,13 +631,11 @@ def dummy_list_path(dir_name):
@patch("mycli.packages.filepaths.list_path", new=dummy_list_path)
@pytest.mark.parametrize(
"text,expected",
# it may be that the cursor positions should be 0, but the position
# info is currently being dropped in find_files()
[
('source ', [('/', 0), ('~', 0), ('.', 0), ('..', 0)]),
("source /", [("dir1", -1), ("file1.sql", -1), ("file2.sql", -1)]),
("source /dir1/", [("subdir1", -6), ("subfile1.sql", -6), ("subfile2.sql", -6)]),
("source /dir1/subdir1/", [("lastfile.sql", -14)]),
("source /", [("dir1", 0), ("file1.sql", 0), ("file2.sql", 0)]),
("source /dir1/", [("subdir1", 0), ("subfile1.sql", 0), ("subfile2.sql", 0)]),
("source /dir1/subdir1/", [("lastfile.sql", 0)]),
],
)
def test_file_name_completion(completer, complete_event, text, expected):
Expand Down Expand Up @@ -697,6 +695,30 @@ def test_source_eager_completion(completer, complete_event):
raise AssertionError(error)


def test_source_leading_dot_suggestions_completion(completer, complete_event):
text = "source ./sc"
position = len(text)
script_filename = 'script_for_test_suite.sql'
f = open(script_filename, 'w')
f.close()
special.register_special_command(..., 'source', '\\. filename', 'Execute commands from file.', aliases=['\\.'])
result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event))
success = True
error = 'unknown'
try:
assert [x.text for x in result] == [
script_filename,
'screenshots/',
]
except AssertionError as e:
success = False
error = e
if os.path.exists(script_filename):
os.remove(script_filename)
if not success:
raise AssertionError(error)


def test_string_no_completion(completer, complete_event):
text = 'select "json'
position = len(text)
Expand Down