diff --git a/changelog.md b/changelog.md index 6aa0157d..bb79cc6e 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,7 @@ Features * Offer filename completions on more special commands, such as `\edit`. * Allow styling of status, timing, and warnings text. * Set up customization of prompt/continuation colors in `~/.myclirc`. +* Allow customization of the toolbar with prompt format strings. Bug Fixes diff --git a/mycli/clitoolbar.py b/mycli/clitoolbar.py index 0ce5c3fe..1112d30a 100644 --- a/mycli/clitoolbar.py +++ b/mycli/clitoolbar.py @@ -2,18 +2,20 @@ from prompt_toolkit.application import get_app from prompt_toolkit.enums import EditingMode +from prompt_toolkit.formatted_text import to_formatted_text from prompt_toolkit.key_binding.vi_state import InputMode from mycli.packages import special -def create_toolbar_tokens_func(mycli, show_initial_toolbar_help: Callable) -> Callable: +def create_toolbar_tokens_func(mycli, show_initial_toolbar_help: Callable, format_string: str | None) -> Callable: """Return a function that generates the toolbar tokens.""" def get_toolbar_tokens() -> list[tuple[str, str]]: divider = ('class:bottom-toolbar', ' │ ') result = [("class:bottom-toolbar", "[Tab] Complete")] + dynamic = [] result.append(divider) result.append(("class:bottom-toolbar", "[F1] Help")) @@ -42,26 +44,39 @@ def get_toolbar_tokens() -> list[tuple[str, str]]: result.append(("class:bottom-toolbar.on", _get_vi_mode())) if mycli.toolbar_error_message: - result.append(divider) - result.append(("class:bottom-toolbar.transaction.failed", mycli.toolbar_error_message)) + dynamic.append(divider) + dynamic.append(("class:bottom-toolbar.transaction.failed", mycli.toolbar_error_message)) mycli.toolbar_error_message = None if mycli.multi_line: delimiter = special.get_current_delimiter() if delimiter != ';' or show_initial_toolbar_help(): - result.append(divider) - result.append(('class:bottom-toolbar', '"')) - result.append(('class:bottom-toolbar.on', delimiter)) - result.append(('class:bottom-toolbar', '" ends a statement')) + dynamic.append(divider) + dynamic.append(('class:bottom-toolbar', '"')) + dynamic.append(('class:bottom-toolbar.on', delimiter)) + dynamic.append(('class:bottom-toolbar', '" ends a statement')) if show_initial_toolbar_help(): - result.append(divider) - result.append(("class:bottom-toolbar", "right-arrow accepts full-line suggestion")) + dynamic.append(divider) + dynamic.append(("class:bottom-toolbar", "right-arrow accepts full-line suggestion")) if mycli.completion_refresher.is_refreshing(): - result.append(divider) - result.append(("class:bottom-toolbar", "Refreshing completions…")) - + dynamic.append(divider) + dynamic.append(("class:bottom-toolbar", "Refreshing completions…")) + + if format_string and format_string != r'\B': + if format_string.startswith(r'\B'): + amended_format = format_string[2:] + result.extend(dynamic) + dynamic = [] + result.append(('class:bottom-toolbar', '\n')) + else: + amended_format = format_string + result = [] + formatted = to_formatted_text(mycli.get_custom_toolbar(amended_format), style='class:bottom-toolbar') + result.extend([*formatted]) # coerce to list for mypy + + result.extend(dynamic) return result return get_toolbar_tokens diff --git a/mycli/main.py b/mycli/main.py index 3d0a5b0f..14000fa8 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -158,6 +158,7 @@ def __init__( self, sqlexecute: SQLExecute | None = None, prompt: str | None = None, + toolbar_format: str | None = None, logfile: TextIOWrapper | Literal[False] | None = None, defaults_suffix: str | None = None, defaults_file: str | None = None, @@ -279,6 +280,7 @@ def __init__( self.min_completion_trigger = c["main"].as_int("min_completion_trigger") MIN_COMPLETION_TRIGGER = self.min_completion_trigger self.last_prompt_message = ANSI('') + self.last_custom_toolbar_message = ANSI('') # Register custom special commands. self.register_special_commands() @@ -302,6 +304,7 @@ def __init__( prompt_cnf = self.read_my_cnf(self.my_cnf, ["prompt"])["prompt"] self.prompt_format = prompt or prompt_cnf or c["main"]["prompt"] or self.default_prompt self.multiline_continuation_char = c["main"]["prompt_continuation"] + self.toolbar_format = toolbar_format or c['main']['toolbar'] self.prompt_app = None self.destructive_keywords = [ keyword for keyword in c["main"].get("destructive_keywords", "DROP SHUTDOWN DELETE TRUNCATE ALTER UPDATE").split(' ') if keyword @@ -1257,7 +1260,11 @@ def one_iteration(text: str | None = None) -> None: query = Query(text, successful, mutating) self.query_history.append(query) - get_toolbar_tokens = create_toolbar_tokens_func(self, show_initial_toolbar_help) + get_toolbar_tokens = create_toolbar_tokens_func( + self, + show_initial_toolbar_help, + self.toolbar_format, + ) if self.wider_completion_menu: complete_style = CompleteStyle.MULTI_COLUMN else: @@ -1524,6 +1531,14 @@ def get_completions(self, text: str, cursor_position: int) -> Iterable[Completio with self._completer_lock: return self.completer.get_completions(Document(text=text, cursor_position=cursor_position), None) + def get_custom_toolbar(self, toolbar_format: str) -> ANSI: + if self.prompt_app and self.prompt_app.app.current_buffer.text: + return self.last_custom_toolbar_message + toolbar = self.get_prompt(toolbar_format) + toolbar = toolbar.replace("\\x1b", "\x1b") + self.last_custom_toolbar_message = ANSI(toolbar) + return self.last_custom_toolbar_message + # todo: time/uptime update on every character typed, instead of after every return def get_prompt(self, string: str) -> str: sqlexecute = self.sqlexecute @@ -1778,6 +1793,7 @@ def get_last_query(self) -> str | None: @click.option("--list-ssh-config", "list_ssh_config", is_flag=True, help="list ssh configurations in the ssh config (requires paramiko).") @click.option("--ssh-warning-off", is_flag=True, help="Suppress the SSH deprecation notice.") @click.option("-R", "--prompt", "prompt", help=f'Prompt format (Default: "{MyCli.default_prompt}").') +@click.option('--toolbar', 'toolbar_format', help='Toolbar format.') @click.option("-l", "--logfile", type=click.File(mode="a", encoding="utf-8"), help="Log every query and its results to a file.") @click.option( "--checkpoint", type=click.File(mode="a", encoding="utf-8"), help="In batch or --execute mode, log successful queries to a file." @@ -1838,6 +1854,7 @@ def cli( dbname: str | None, verbose: bool, prompt: str | None, + toolbar_format: str | None, logfile: TextIOWrapper | None, checkpoint: TextIOWrapper | None, defaults_group_suffix: str | None, @@ -1938,6 +1955,7 @@ def get_password_from_file(password_file: str | None) -> str | None: mycli = MyCli( prompt=prompt, + toolbar_format=toolbar_format, logfile=logfile, defaults_suffix=defaults_group_suffix, defaults_file=defaults_file, diff --git a/mycli/myclirc b/mycli/myclirc index 6fc37bbe..d2f3efb9 100644 --- a/mycli/myclirc +++ b/mycli/myclirc @@ -125,6 +125,17 @@ wider_completion_menu = False prompt = '\t \u@\h:\d> ' prompt_continuation = '->' +# Use the same prompt format strings to construct a status line in the toolbar, +# where \B in the first position refers to the default toolbar showing keystrokes +# and state. Example: +# +# toolbar = '\B\d \D' +# +# If \B is included, the additional content will begin on the next line. More +# lines can be added with \n. If \B is not included, the customized toolbar +# can be a single line. +toolbar = '' + # Skip intro info on startup and outro info on exit less_chatty = False diff --git a/test/myclirc b/test/myclirc index 383cdcef..82f8f870 100644 --- a/test/myclirc +++ b/test/myclirc @@ -123,6 +123,17 @@ wider_completion_menu = False prompt = "\t \u@\h:\d> " prompt_continuation = -> +# Use the same prompt format strings to construct a status line in the toolbar, +# where \B in the first position refers to the default toolbar showing keystrokes +# and state. Example: +# +# toolbar = '\B\d \D' +# +# If \B is included, the additional content will begin on the next line. More +# lines can be added with \n. If \B is not included, the customized toolbar +# can be a single line. +toolbar = '' + # Skip intro info on startup and outro info on exit less_chatty = True