diff --git a/changelog.md b/changelog.md index 76ad00d0..1546aba1 100644 --- a/changelog.md +++ b/changelog.md @@ -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) diff --git a/mycli/packages/parseutils.py b/mycli/packages/parseutils.py index b4ab4b8f..96c498a1 100644 --- a/mycli/packages/parseutils.py +++ b/mycli/packages/parseutils.py @@ -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 @@ -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. diff --git a/mycli/sqlcompleter.py b/mycli/sqlcompleter.py index 8595b008..c9f85162 100644 --- a/mycli/sqlcompleter.py +++ b/mycli/sqlcompleter.py @@ -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 @@ -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: @@ -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:] @@ -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. diff --git a/test/test_smart_completion_public_schema_only.py b/test/test_smart_completion_public_schema_only.py index ee6b27fc..3c6521ed 100644 --- a/test/test_smart_completion_public_schema_only.py +++ b/test/test_smart_completion_public_schema_only.py @@ -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): @@ -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)