diff --git a/changelog.md b/changelog.md index ba2aca62..93d01baf 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,7 @@ Upcoming (TBD) Features -------- * Sort completion candidates by frecency from history. +* Add help snippets in special /command completions. Internal diff --git a/mycli/client_commands.py b/mycli/client_commands.py index cbac0f96..ce670a47 100644 --- a/mycli/client_commands.py +++ b/mycli/client_commands.py @@ -237,6 +237,7 @@ def register_special_commands(self) -> None: "/use ", "Change to a new database.", aliases=[SpecialCommandAlias("\\u", case_sensitive=False)], + completion_snippet='change databases', ) special.register_special_command( self.manual_reconnect, @@ -245,6 +246,7 @@ def register_special_commands(self) -> None: "Reconnect to the server, optionally switching databases.", case_sensitive=True, aliases=[SpecialCommandAlias("\\r", case_sensitive=True)], + completion_snippet='reconnect to server', ) special.register_special_command( self.rehash, @@ -253,6 +255,7 @@ def register_special_commands(self) -> None: "Refresh auto-completions.", arg_type=ArgType.NO_ARGUMENT, aliases=[SpecialCommandAlias("\\#", case_sensitive=False)], + completion_snippet='refresh completions', ) special.register_special_command( self.change_table_format, @@ -261,6 +264,7 @@ def register_special_commands(self) -> None: "Change the table format used to output interactive results.", case_sensitive=True, aliases=[SpecialCommandAlias("\\T", case_sensitive=True)], + completion_snippet='change interactive output format', ) special.register_special_command( self.change_redirect_format, @@ -269,6 +273,7 @@ def register_special_commands(self) -> None: "Change the table format used to output redirected results.", case_sensitive=True, aliases=[SpecialCommandAlias("\\Tr", case_sensitive=True)], + completion_snippet='change redirected output format', ) special.register_special_command( self.execute_from_file, @@ -276,6 +281,7 @@ def register_special_commands(self) -> None: "/source [--special|--show|--page] ", "Execute queries from a file.", aliases=[SpecialCommandAlias("\\.", case_sensitive=False)], + completion_snippet='execute queries from file', ) special.register_special_command( self.change_prompt_format, @@ -284,12 +290,14 @@ def register_special_commands(self) -> None: "Show or change prompt format.", case_sensitive=True, aliases=[SpecialCommandAlias("\\R", case_sensitive=True)], + completion_snippet='show or change prompt format', ) special.register_special_command( self.config_command, r'\config', '/config [key]', 'Inspect settings from config files.', + completion_snippet='inspect config file settings', ) def manual_reconnect(self, arg: str = "", **_) -> Generator[SQLResult, None, None]: diff --git a/mycli/completion_refresher.py b/mycli/completion_refresher.py index 305def95..62ab421b 100644 --- a/mycli/completion_refresher.py +++ b/mycli/completion_refresher.py @@ -267,7 +267,7 @@ def refresh_collations(completer: SQLCompleter, executor: SQLExecute) -> None: @refresher("special_commands") def refresh_special(completer: SQLCompleter, executor: SQLExecute) -> None: - completer.extend_special_commands(list(COMMANDS.keys())) + completer.extend_special_commands({command: details.completion_snippet or details.description for command, details in COMMANDS.items()}) @refresher("show_commands") diff --git a/mycli/packages/special/dbcommands.py b/mycli/packages/special/dbcommands.py index 592a3d47..5ac43b0b 100644 --- a/mycli/packages/special/dbcommands.py +++ b/mycli/packages/special/dbcommands.py @@ -26,6 +26,7 @@ "List or describe tables.", arg_type=ArgType.PARSED_QUERY, case_sensitive=True, + completion_snippet='list or describe tables', ) def list_tables( cur: Cursor, @@ -65,6 +66,7 @@ def list_tables( "List databases.", arg_type=ArgType.RAW_QUERY, case_sensitive=True, + completion_snippet='list databases', ) def list_databases(cur: Cursor, **_) -> list[SQLResult]: query = "SHOW DATABASES" @@ -85,6 +87,7 @@ def list_databases(cur: Cursor, **_) -> list[SQLResult]: arg_type=ArgType.RAW_QUERY, case_sensitive=True, aliases=[SpecialCommandAlias("\\s", case_sensitive=True)], + completion_snippet='get status from server', ) def status(cur: Cursor, **_) -> list[SQLResult]: query = "SHOW GLOBAL STATUS;" diff --git a/mycli/packages/special/iocommands.py b/mycli/packages/special/iocommands.py index 5cf440ce..b6def048 100644 --- a/mycli/packages/special/iocommands.py +++ b/mycli/packages/special/iocommands.py @@ -114,6 +114,7 @@ def is_show_warnings_enabled() -> bool: arg_type=ArgType.NO_ARGUMENT, case_sensitive=True, aliases=[SpecialCommandAlias('\\W', case_sensitive=True)], + completion_snippet='enable warnings display', ) def enable_show_warnings() -> Generator[SQLResult, None, None]: global SHOW_WARNINGS_ENABLED @@ -129,6 +130,7 @@ def enable_show_warnings() -> Generator[SQLResult, None, None]: arg_type=ArgType.NO_ARGUMENT, case_sensitive=True, aliases=[SpecialCommandAlias('\\w', case_sensitive=True)], + completion_snippet='disable warnings display', ) def disable_show_warnings() -> Generator[SQLResult, None, None]: global SHOW_WARNINGS_ENABLED @@ -144,6 +146,7 @@ def disable_show_warnings() -> Generator[SQLResult, None, None]: arg_type=ArgType.PARSED_QUERY, case_sensitive=True, aliases=[SpecialCommandAlias("\\P", case_sensitive=True)], + completion_snippet='set pager', ) def set_pager(arg: str, **_) -> list[SQLResult]: if arg: @@ -168,6 +171,7 @@ def set_pager(arg: str, **_) -> list[SQLResult]: arg_type=ArgType.NO_ARGUMENT, case_sensitive=True, aliases=[SpecialCommandAlias("\\n", case_sensitive=True)], + completion_snippet='disable pager', ) def disable_pager() -> list[SQLResult]: set_pager_enabled(False) @@ -181,6 +185,7 @@ def disable_pager() -> list[SQLResult]: arg_type=ArgType.NO_ARGUMENT, case_sensitive=True, aliases=[SpecialCommandAlias("\\t", case_sensitive=True)], + completion_snippet='toggle query timing', ) def toggle_timing() -> list[SQLResult]: global TIMING_ENABLED @@ -356,6 +361,7 @@ def set_redirect(command_part: str | None, file_operator_part: str | None, file_ 'Alternative favorite query interface. See /favorite help.', arg_type=ArgType.PARSED_QUERY, case_sensitive=False, + completion_snippet='manage favorite queries', ) def favorite(arg: str, cur: Cursor | None = None, **_) -> Iterable[SQLResult]: args = arg.strip().split(maxsplit=1) @@ -411,6 +417,7 @@ def favorite(arg: str, cur: Cursor | None = None, **_) -> Iterable[SQLResult]: "List or execute favorite queries.", arg_type=ArgType.PARSED_QUERY, case_sensitive=True, + completion_snippet='list or run favorite queries', ) def execute_favorite_query(cur: Cursor, arg: str, **_) -> Generator[SQLResult, None, None]: if arg == "": @@ -582,6 +589,7 @@ def subst_favorite_query_args(query: str, args: list[str]) -> list[str | None]: "\\fs", "/fs ", "Save a favorite query.", + completion_snippet='save favorite queries', ) def save_favorite_query(arg: str, **_) -> list[SQLResult]: """Save a new favorite query.""" @@ -636,6 +644,7 @@ def is_favorite_save_command(statement: str) -> bool: "\\fd", "/fd ", "Delete a favorite query.", + completion_snippet='delete favorite queries', ) def delete_favorite_query(arg: str, **_) -> list[SQLResult]: """Delete an existing favorite query.""" @@ -658,6 +667,7 @@ def _delete_favorite_query(arg: str, usage: str) -> list[SQLResult]: 'Manage saved DSNs. See /dsn help.', arg_type=ArgType.PARSED_QUERY, case_sensitive=False, + completion_snippet='manage saved DSNs', ) def dsn( cur: Cursor, @@ -734,6 +744,7 @@ def _edit_dsn_alias(alias: str) -> list[SQLResult]: "system", "/system [-r] ", "Execute a system shell command (raw mode with -r).", + completion_snippet='execute system command', ) def execute_system_command(arg: str, **_) -> list[SQLResult]: """Execute a system shell command.""" @@ -815,6 +826,7 @@ def parseargfile(arg: str) -> tuple[str, str]: "tee", "/tee [-o] ", "Append all results to an output file (overwrite using -o).", + completion_snippet='append all results to file', ) def set_tee(arg: str, **_) -> list[SQLResult]: global tee_file @@ -838,6 +850,7 @@ def close_tee() -> None: "notee", "/notee", "Stop writing results to an output file.", + completion_snippet='stop writing to tee file', ) def no_tee(arg: str, **_) -> list[SQLResult]: close_tee() @@ -859,6 +872,7 @@ def write_tee(output: str | ANSI | FormattedText, nl: bool = True) -> None: "/once [-o] ", "Append next result to an output file (overwrite using -o).", aliases=[SpecialCommandAlias("\\o", case_sensitive=False)], + completion_snippet='append one result to file', ) def set_once(arg: str, **_) -> list[SQLResult]: global once_file, written_to_once_file @@ -922,6 +936,7 @@ def _run_post_redirect_hook(post_redirect_command: str, filename: str) -> None: "/pipe_once ", "Send next result to a subprocess.", aliases=[SpecialCommandAlias("\\|", case_sensitive=False)], + completion_snippet='send one result to subprocess', ) def set_pipe_once(arg: str, **_) -> list[SQLResult]: if not arg: @@ -985,6 +1000,7 @@ def flush_pipe_once_if_written(post_redirect_command: str) -> None: "watch", "/watch [seconds] [-c] ", "Execute query every [seconds] seconds (5 by default).", + completion_snippet='run query every N seconds', ) def watch_query(arg: str, **kwargs) -> Generator[SQLResult, None, None]: usage = """Syntax: watch [seconds] [-c] query. @@ -1056,6 +1072,7 @@ def watch_query(arg: str, **kwargs) -> Generator[SQLResult, None, None]: "delimiter", "/delimiter ", "Change end-of-statement delimiter.", + completion_snippet='change end-of-statement delimiter', ) def set_delimiter(arg: str, **_) -> list[SQLResult]: return delimiter_command.set(arg) diff --git a/mycli/packages/special/main.py b/mycli/packages/special/main.py index 1d95fbc7..471f0434 100644 --- a/mycli/packages/special/main.py +++ b/mycli/packages/special/main.py @@ -49,6 +49,7 @@ class SpecialCommand: case_sensitive: bool | None aliases: list[SpecialCommandAlias] | None backslash_only: bool + completion_snippet: str | None = None class CommandNotFound(Exception): @@ -86,6 +87,7 @@ def special_command( case_sensitive: bool = False, aliases: list[SpecialCommandAlias] | None = None, backslash_only: bool = False, + completion_snippet: str | None = None, ) -> Callable: def wrapper(wrapped): register_special_command( @@ -98,6 +100,7 @@ def wrapper(wrapped): case_sensitive=case_sensitive, aliases=aliases, backslash_only=backslash_only, + completion_snippet=completion_snippet, ) return wrapped @@ -114,6 +117,7 @@ def register_special_command( case_sensitive: bool = False, aliases: list[SpecialCommandAlias] | None = None, backslash_only: bool = False, + completion_snippet: str | None = None, ) -> None: if command.startswith('\\'): forwardslash_command = '/' + command.removeprefix('\\') @@ -131,6 +135,7 @@ def register_special_command( case_sensitive=case_sensitive, aliases=aliases, backslash_only=backslash_only, + completion_snippet=completion_snippet, ) if not backslash_only: COMMANDS[fcmd] = SpecialCommand( @@ -143,6 +148,7 @@ def register_special_command( case_sensitive=case_sensitive, aliases=aliases, backslash_only=backslash_only, + completion_snippet=completion_snippet, ) if case_sensitive: CASE_SENSITIVE_COMMANDS.add(command) @@ -174,6 +180,7 @@ def register_special_command( hidden=True, aliases=None, backslash_only=backslash_only, + completion_snippet=completion_snippet, ) if not backslash_only: COMMANDS[fcmd] = SpecialCommand( @@ -186,6 +193,7 @@ def register_special_command( hidden=True, aliases=None, backslash_only=backslash_only, + completion_snippet=completion_snippet, ) @@ -226,6 +234,7 @@ def execute(cur: Cursor, sql: str) -> list[SQLResult]: "Show this table, or search for help on a term.", arg_type=ArgType.NO_ARGUMENT, aliases=[SpecialCommandAlias("\\?", case_sensitive=False), SpecialCommandAlias("?", case_sensitive=False)], + completion_snippet='show help or search', ) def show_help(*_args) -> list[SQLResult]: header = ["Command", "Shortcut", "Usage", "Description"] @@ -288,7 +297,13 @@ def show_keyword_help(cur: Cursor, arg: str) -> list[SQLResult]: return _show_mysql_help(cur, keyword) -@special_command('\\bug', '/bug', 'File a bug on GitHub.', arg_type=ArgType.NO_ARGUMENT) +@special_command( + '\\bug', + '/bug', + 'File a bug on GitHub.', + arg_type=ArgType.NO_ARGUMENT, + completion_snippet='file a bug on GitHub', +) def file_bug(*_args) -> list[SQLResult]: webbrowser.open_new_tab(ISSUES_URL) return [SQLResult(status=f'{ISSUES_URL} — press "New Issue"')] @@ -300,6 +315,7 @@ def file_bug(*_args) -> list[SQLResult]: "Exit.", arg_type=ArgType.NO_ARGUMENT, aliases=[SpecialCommandAlias("\\q", case_sensitive=False)], + completion_snippet='exit', ) @special_command( "quit", @@ -307,6 +323,7 @@ def file_bug(*_args) -> list[SQLResult]: "Quit.", arg_type=ArgType.NO_ARGUMENT, aliases=[SpecialCommandAlias("\\q", case_sensitive=False)], + completion_snippet='exit', ) def quit_(*_args): raise EOFError @@ -319,6 +336,7 @@ def quit_(*_args): arg_type=ArgType.NO_ARGUMENT, case_sensitive=True, aliases=[SpecialCommandAlias("\\e", case_sensitive=True)], + completion_snippet='edit query with editor', ) @special_command( "\\clip", @@ -326,6 +344,7 @@ def quit_(*_args): "Copy query to the system clipboard.", arg_type=ArgType.NO_ARGUMENT, case_sensitive=True, + completion_snippet='copy query to clipboard', ) @special_command( "\\G", @@ -364,6 +383,7 @@ def stub(): arg_type=ArgType.RAW_QUERY, case_sensitive=True, aliases=[SpecialCommandAlias("\\ai", case_sensitive=True)], + completion_snippet='interrogate an LLM', ) def llm_stub(): raise NotImplementedError diff --git a/mycli/sqlcompleter.py b/mycli/sqlcompleter.py index 3ff0f207..52679fb6 100644 --- a/mycli/sqlcompleter.py +++ b/mycli/sqlcompleter.py @@ -965,6 +965,7 @@ def __init__( self.name_pattern = re.compile(r"^[_a-zA-Z][_a-zA-Z0-9\$]*$") self.special_commands: list[str] = [] + self.special_command_snippets: dict[str, str] = {} self.table_formats = supported_formats if keyword_casing not in ("upper", "lower", "auto"): keyword_casing = "auto" @@ -980,10 +981,11 @@ def escape_name(self, name: str) -> str: def escaped_names(self, names: Collection[str]) -> list[str]: return [self.escape_name(name) for name in names] - def extend_special_commands(self, special_commands: list[str]) -> None: + def extend_special_commands(self, special_commands: Mapping[str, str]) -> None: # Special commands are not part of all_completions since they can only # be at the beginning of a line. self.special_commands.extend(special_commands) + self.special_command_snippets.update(special_commands) def extend_database_names(self, databases: list[str]) -> None: self.databases.extend([self.escape_name(db) for db in databases]) @@ -1462,6 +1464,7 @@ def get_completions( completions: list[tuple[str, int, int]] = [] indexed_column_candidates: set[str] = set() + special_command_candidates: set[str] = set() suggestions = suggest_type(document.text, document.text_before_cursor) rigid_sort = False length_based_on_path = False @@ -1680,15 +1683,18 @@ def get_completions( completions.extend([(*x, rank) for x in users_m]) elif suggestion["type"] == "special": - special_m = self.find_matches( - word_before_cursor, - self.special_commands, - start_only=True, - fuzzy=False, - text_before_cursor=document.text_before_cursor, + special_m = list( + self.find_matches( + word_before_cursor, + self.special_commands, + start_only=True, + fuzzy=False, + text_before_cursor=document.text_before_cursor, + ) ) # specials are special, and go early in the candidates, first if possible completions.extend([(*x, 0) for x in special_m]) + special_command_candidates.update(x[0] for x in special_m) elif suggestion["type"] == "favoritequery": if hasattr(FavoriteQueries, 'instance') and hasattr(FavoriteQueries.instance, 'list'): @@ -1845,6 +1851,7 @@ def completion_sort_key(item: tuple[str, int, int], text_for_len: str): x, -len(last_for_len_paths), display=f'{x}{self.indexed_column_suffix}' if x in indexed_column_candidates else None, + display_meta=self.special_command_snippets.get(x) if x in special_command_candidates else None, style=_INDEXED_COLUMN_STYLE if x in indexed_column_candidates else '', ) for x in uniq_completions_str @@ -1855,6 +1862,7 @@ def completion_sort_key(item: tuple[str, int, int], text_for_len: str): x, -len(text_for_len), display=f'{x}{self.indexed_column_suffix}' if x in indexed_column_candidates else None, + display_meta=self.special_command_snippets.get(x) if x in special_command_candidates else None, style=_INDEXED_COLUMN_STYLE if x in indexed_column_candidates else '', ) for x in uniq_completions_str diff --git a/test/pytests/test_completion_refresher.py b/test/pytests/test_completion_refresher.py index 03795c02..eff703a8 100644 --- a/test/pytests/test_completion_refresher.py +++ b/test/pytests/test_completion_refresher.py @@ -705,7 +705,11 @@ def test_refresh_helpers_delegate_to_completer_and_executor(monkeypatch) -> None executor.collations.return_value = iter([('utf8mb4_unicode_ci',)]) executor.show_candidates.return_value = iter([('FULL TABLES',)]) - monkeypatch.setattr(completion_refresher, 'COMMANDS', {'\\x': object(), 'help': object()}) + commands = { + '\\x': SimpleNamespace(description='Expanded output.', completion_snippet=None), + 'help': SimpleNamespace(description='Show help.', completion_snippet='Find help.'), + } + monkeypatch.setattr(completion_refresher, 'COMMANDS', commands) completion_refresher.refresh_databases(completer, executor) completion_refresher.refresh_schemata(completer, executor) @@ -732,7 +736,7 @@ def test_refresh_helpers_delegate_to_completer_and_executor(monkeypatch) -> None completer.extend_procedures.assert_called_once_with(executor.procedures.return_value) completer.extend_character_sets.assert_called_once_with(executor.character_sets.return_value) completer.extend_collations.assert_called_once_with(executor.collations.return_value) - completer.extend_special_commands.assert_called_once_with(['\\x', 'help']) + completer.extend_special_commands.assert_called_once_with({'\\x': 'Expanded output.', 'help': 'Find help.'}) completer.extend_show_items.assert_called_once_with(executor.show_candidates.return_value) diff --git a/test/pytests/test_smart_completion_public_schema_only.py b/test/pytests/test_smart_completion_public_schema_only.py index 84423b94..5630a243 100644 --- a/test/pytests/test_smart_completion_public_schema_only.py +++ b/test/pytests/test_smart_completion_public_schema_only.py @@ -24,6 +24,10 @@ } +def special_command_snippets() -> dict[str, str]: + return {name: command.completion_snippet or command.description for name, command in special.COMMANDS.items()} + + @pytest.fixture def completer(): import mycli.sqlcompleter as sqlcompleter @@ -45,7 +49,7 @@ def completer(): comp.extend_relations(tables, kind="tables") comp.extend_columns(columns, kind="tables") comp.extend_enum_values([("orders", "status", ["pending", "shipped"])]) - comp.extend_special_commands(special.COMMANDS) + comp.extend_special_commands(special_command_snippets()) return comp @@ -67,7 +71,7 @@ def empty_completer(): comp.extend_schemata(db) comp.extend_database_names([db]) comp.set_dbname(db) - comp.extend_special_commands(special.COMMANDS) + comp.extend_special_commands(special_command_snippets()) return comp @@ -101,8 +105,8 @@ def test_special_name_completion(completer, complete_event): position = len("\\d") result = completer.get_completions(Document(text=text, cursor_position=position), complete_event) assert list(result) == [ - Completion(text="\\dt", start_position=-2), - Completion(text="\\dsn", start_position=-2), + Completion(text="\\dt", start_position=-2, display_meta='list or describe tables'), + Completion(text="\\dsn", start_position=-2, display_meta='manage saved DSNs'), ] @@ -175,7 +179,10 @@ def test_empty_string_completion(completer, complete_event): text = "" position = 0 result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) - assert list(map(Completion, completer.special_commands + completer.keywords)) == result + expected_special = [ + Completion(command, display_meta=completer.special_command_snippets[command]) for command in completer.special_commands + ] + assert expected_special + list(map(Completion, completer.keywords)) == result def test_select_keyword_completion(completer, complete_event): @@ -683,7 +690,7 @@ def test_deleted_keyword_completion(completer, complete_event): position = len("exi") result = list(completer.get_completions(Document(text=text, cursor_position=position), complete_event)) assert result == [ - Completion(text="exit", start_position=-3), + Completion(text="exit", start_position=-3, display_meta='exit'), Completion(text='exists', start_position=-3), Completion(text='explain', start_position=-3), Completion(text='expire', start_position=-3), @@ -1295,7 +1302,6 @@ def fk_completer(): users (id, email, first_name) tags (id, name) no FK """ - import mycli.packages.special.main as special import mycli.sqlcompleter as sqlcompleter comp = sqlcompleter.SQLCompleter(smart_completion=True) @@ -1320,7 +1326,7 @@ def fk_completer(): comp.extend_relations(tables, kind="tables") comp.extend_columns(columns, kind="tables") comp.extend_foreign_keys(fk_data) - comp.extend_special_commands(special.COMMANDS) + comp.extend_special_commands(special_command_snippets()) return comp diff --git a/test/pytests/test_special_main.py b/test/pytests/test_special_main.py index ab17c8e9..cd1b371a 100644 --- a/test/pytests/test_special_main.py +++ b/test/pytests/test_special_main.py @@ -88,6 +88,7 @@ def handler() -> None: 'demo', 'Description', aliases=[special_main.SpecialCommandAlias('\\d', case_sensitive=False)], + completion_snippet='Manage demos.', ) assert special_main.COMMANDS['demo'] == special_main.SpecialCommand( @@ -100,6 +101,7 @@ def handler() -> None: case_sensitive=False, aliases=[special_main.SpecialCommandAlias('\\d', case_sensitive=False)], backslash_only=False, + completion_snippet='Manage demos.', ) assert special_main.COMMANDS['\\d'] == special_main.SpecialCommand( handler, @@ -111,7 +113,10 @@ def handler() -> None: case_sensitive=False, aliases=None, backslash_only=False, + completion_snippet='Manage demos.', ) + assert special_main.COMMANDS['/demo'].completion_snippet == 'Manage demos.' + assert special_main.COMMANDS['/d'].completion_snippet == 'Manage demos.' def test_register_special_command_tracks_case_insensitive_commands(restore_commands: None) -> None: @@ -136,11 +141,18 @@ def test_special_command_decorator_registers_case_sensitive_command(restore_comm special_main.CASE_SENSITIVE_COMMANDS.clear() special_main.CASE_INSENSITIVE_COMMANDS.clear() - @special_main.special_command('Camel', 'Camel', 'Description', case_sensitive=True) + @special_main.special_command( + 'Camel', + 'Camel', + 'Description', + case_sensitive=True, + completion_snippet='Run Camel.', + ) def handler() -> None: return None assert special_main.COMMANDS['Camel'].handler is handler + assert special_main.COMMANDS['Camel'].completion_snippet == 'Run Camel.' assert 'Camel' in special_main.CASE_SENSITIVE_COMMANDS assert '/Camel' in special_main.CASE_SENSITIVE_COMMANDS assert special_main.CASE_INSENSITIVE_COMMANDS == set() @@ -337,6 +349,7 @@ def test_show_help_lists_only_visible_commands(restore_commands: None) -> None: '/visible', 'Visible command', aliases=[special_main.SpecialCommandAlias('\\v', case_sensitive=False)], + completion_snippet='Complete visible.', ) special_main.register_special_command(lambda: None, 'hidden', 'hidden', 'Hidden command', hidden=True) diff --git a/test/pytests/test_sqlcompleter.py b/test/pytests/test_sqlcompleter.py index c1e409c2..1a30569a 100644 --- a/test/pytests/test_sqlcompleter.py +++ b/test/pytests/test_sqlcompleter.py @@ -389,6 +389,33 @@ def provider() -> dict[str, float]: assert completer.frecency_provider is provider +def test_special_command_completion_displays_snippet(monkeypatch) -> None: + completer = make_completer() + favorite = mycli.sqlcompleter.SPECIAL_COMMANDS['/favorite'] + assert favorite.completion_snippet == 'manage favorite queries' + completer.extend_special_commands({'/favorite': favorite.completion_snippet}) + monkeypatch.setattr(mycli.sqlcompleter, 'suggest_type', lambda text, before: [{'type': 'special'}]) + + result = list(completer.get_completions(Document(text='/fav'), None)) + + assert len(result) == 1 + assert result[0].text == '/favorite' + assert result[0].display_meta_text == 'manage favorite queries' + + +def test_sql_keyword_completion_does_not_display_special_command_snippet(monkeypatch) -> None: + completer = make_completer() + completer.keywords = ['exit'] + completer.extend_special_commands({'exit': 'Exit.'}) + monkeypatch.setattr(mycli.sqlcompleter, 'suggest_type', lambda text, before: [{'type': 'keyword'}]) + + result = list(completer.get_completions(Document(text='SELECT exi'), None)) + + assert len(result) == 1 + assert result[0].text == 'exit' + assert result[0].display_meta_text == '' + + def test_get_completions_uses_frecency_before_prefix_length(monkeypatch) -> None: completer = make_completer(frecency_provider=lambda: {'alphabet': 10.0}) monkeypatch.setattr(mycli.sqlcompleter, 'suggest_type', lambda text, before: [{'type': 'column', 'tables': []}])