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 @@ -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
Expand Down
39 changes: 27 additions & 12 deletions mycli/clitoolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down Expand Up @@ -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
Expand Down
20 changes: 19 additions & 1 deletion mycli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions mycli/myclirc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 11 additions & 0 deletions test/myclirc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down